Files
WrenchBoradWeb/wrenchboard/src/shared_tool/stripe.cc
T
2019-05-31 11:26:35 -04:00

1517 lines
44 KiB
C++

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <curl/curl.h>
#include <pcrecpp.h>
#include <json-c/json.h>
#include "clog.h"
static void getit(struct json_object *new_obj, const char *field);
#include "stripe.h"
struct MemoryStruct {
char *memory;
size_t size;
};
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)userp;
mem->memory = static_cast<char*>(realloc(mem->memory, mem->size + realsize + 1));
if(mem->memory == NULL) {
/* out of memory! */
logfmt( logDEBUG, "not enough memory (realloc returned NULL)\n");
return 0;
}
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
void remove_all_chars(char* str, char c) {
char *pr = str, *pw = str;
while (*pr) {
*pw = *pr++;
pw += (*pw != c);
}
*pw = '\0';
}
long stripe_tokenize_card_parse(char *json, char *token, size_t token_size)
{
long res = -1L;
json_object *new_obj;
struct json_object *o = NULL;
new_obj = json_tokener_parse(json);
if (!json_object_object_get_ex(new_obj, "id", &o)) {
logfmt( logINFO, "Field id does not exist" );
return res;
}
enum json_type o_type = json_object_get_type(o);
if (strcmp(json_type_to_name(o_type), "string")==0) {
snprintf(token, token_size, "%s", json_object_to_json_string(o));
res = 0L;
} else {
logfmt( logINFO, "Result is not string O__o" );
}
/*
logfmt(logINFO, "new_obj.to_string()=%s\n", json_object_to_json_string(new_obj));
getit(new_obj, "id");
getit(new_obj, "object");
getit(new_obj, "client_ip");
getit(new_obj, "created");
getit(new_obj, "livemode");
getit(new_obj, "type");
getit(new_obj, "used");
*/
json_object_put(new_obj);
return res;
}
// https://github.com/json-c/json-c/blob/master/tests/test_cast.c
static void getit(struct json_object *new_obj, const char *field)
{
struct json_object *o = NULL;
if (!json_object_object_get_ex(new_obj, field, &o))
printf("Field %s does not exist\n", field);
enum json_type o_type = json_object_get_type(o);
printf("new_obj.%s json_object_get_type()=%s\n", field,
json_type_to_name(o_type));
printf("new_obj.to_string()=%s\n",
json_object_to_json_string(o));
printf("new_obj.%s json_object_get_int()=%d\n", field,
json_object_get_int(o));
// printf("new_obj.%s json_object_get_int64()=%" PRId64 "\n", field,
// json_object_get_int64(o));
printf("new_obj.%s json_object_get_boolean()=%d\n", field,
json_object_get_boolean(o));
printf("new_obj.%s json_object_get_double()=%f\n", field,
json_object_get_double(o));
}
CURL *prepare_curl(char *url, void *chunk)
{
CURL *curl;
/* In windows, this will init the winsock stuff */
curl_global_init(CURL_GLOBAL_ALL);
/* get a curl handle */
curl = curl_easy_init();
if(curl) {
/* First set the URL that is about to receive our POST. This URL can
just as well be a https:// URL if that is what should receive the
data. */
curl_easy_setopt(curl, CURLOPT_URL, url);
/* send all data to this function */
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
/* we pass our 'chunk' struct to the callback function */
curl_easy_setopt(curl, CURLOPT_WRITEDATA, chunk);
}
return curl;
}
/*
curl https://api.stripe.com/v1/tokens \
-u sk_test_MJYII1vrGGr7RrGw1lhYMiTq: \
-d card[number]=4242424242424242 \
-d card[exp_month]=12 \
-d card[exp_year]=2018 \
-d card[cvc]=123
*/
long stripe_tokenize_card(const char *key, const char *ccnum, const char *ccexpm, const char *ccexpy, const char *cccvc, char *token, size_t token_size)
{
CURL *curl;
CURLcode res;
long ret = -1L;
char post_fields[512];
char post_auth[256];
struct MemoryStruct chunk;
chunk.memory = static_cast<char*>(malloc(1)); /* will be grown as needed by the realloc above */
chunk.size = 0; /* no data at this point */
curl = prepare_curl("https://api.stripe.com/v1/tokens", (void *)&chunk);
if(curl) {
bzero(post_fields, sizeof(post_fields));
snprintf(post_fields, sizeof(post_fields),
"card[number]=%s&card[exp_month]=%s&card[exp_year]=%s&card[cvc]=%s",
ccnum, ccexpm, ccexpy, cccvc);
/* Now specify the POST data */
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields);
bzero(post_auth, sizeof(post_auth));
snprintf(post_auth, sizeof(post_auth), "%s:", key);
/* set user name and password for the authentication */
curl_easy_setopt(curl, CURLOPT_USERPWD, post_auth);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res == CURLE_OK) {
logfmt( logINFO, "%lu bytes retrieved\n", (long)chunk.size);
logfmt( logDEBUG, "%s\n", chunk.memory);
bzero(token, token_size);
ret = stripe_tokenize_card_parse(chunk.memory, token, token_size);
if (ret != 0L) {
logfmt( logINFO, "Failed to get token");
}
} else {
logfmt( logINFO, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
/* always cleanup */
curl_easy_cleanup(curl);
free(chunk.memory);
}
curl_global_cleanup();
return ret;
}
/*
{
\"id\": \"card_1BRNdFF5FERRcWDyUdOaIBzd\",
\"object\": \"card\",
\"address_city\": null,
\"address_country\": null,
\"address_line1\": null,
\"address_line1_check\": null,
\"address_line2\": null,
\"address_state\": null,
\"address_zip\": null,
\"address_zip_check\": null,
\"brand\": \"American Express\",
\"country\": \"US\",
\"customer\": \"cus_BoeZzLBOGUiRzw\",
\"cvc_check\": null,
\"dynamic_last4\": null,
\"exp_month\": 8,
\"exp_year\": 2019,
\"fingerprint\": \"9KROBb1pqZA216C4\",
\"funding\": \"credit\",
\"last4\": \"8431\",
\"metadata\": {},
\"name\": null,
\"tokenization_method\": null
}
*/
long stripe_create_card_parse(char *json, char *card, size_t card_size)
{
long res = -1L;
json_object *new_obj;
struct json_object *o = NULL;
new_obj = json_tokener_parse(json);
if (!json_object_object_get_ex(new_obj, "id", &o)) {
logfmt( logINFO, "Field ID does not exist\n" );
return res;
}
enum json_type o_type = json_object_get_type(o);
if (strcmp(json_type_to_name(o_type), "string")==0) {
snprintf(card, card_size, "%s", json_object_to_json_string(o));
res = 0L;
} else {
logfmt( logINFO, "ID is not string\n" );
res = -2L;
}
json_object_put(new_obj);
return res;
}
/*
curl https://api.stripe.com/v1/customers/cus_BoeZzLBOGUiRzw/sources \
-u sk_test_MJYII1vrGGr7RrGw1lhYMiTq: \
-d source=tok_amex
*/
long stripe_create_card(const char *key, char *customer, char *token, char *card, size_t card_size)
{
CURL *curl;
CURLcode res;
long ret = -1L;
char post_fields[512];
char post_auth[256];
char url[256];
struct MemoryStruct chunk;
chunk.memory = static_cast<char*>(malloc(1)); /* will be grown as needed by the realloc above */
chunk.size = 0; /* no data at this point */
snprintf(url, sizeof(url), "https://api.stripe.com/v1/customers/%s/sources", customer);
curl = prepare_curl(url, (void *)&chunk);
if(curl) {
bzero(post_fields, sizeof(post_fields));
snprintf(post_fields, sizeof(post_fields),
"source=%s",
token);
/* Now specify the POST data */
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields);
bzero(post_auth, sizeof(post_auth));
snprintf(post_auth, sizeof(post_auth), "%s:", key);
/* set user name and password for the authentication */
curl_easy_setopt(curl, CURLOPT_USERPWD, post_auth);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res == CURLE_OK) {
logfmt( logINFO, "%lu bytes retrieved\n", (long)chunk.size);
logfmt( logDEBUG, "%s\n", chunk.memory);
bzero(card, card_size);
ret = stripe_create_card_parse(chunk.memory, card, card_size);
if (ret != 0L) {
logfmt( logINFO, "Failed to get stripe card ID\n");
}
} else {
logfmt( logINFO, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
/* always cleanup */
curl_easy_cleanup(curl);
free(chunk.memory);
}
curl_global_cleanup();
return ret;
}
/*
{
\"object\": \"list\",
\"data\": [
{
\"id\": \"card_1BR1GQF5FERRcWDyp8ncqhzS\",
\"object\": \"card\",
\"address_city\": null,
\"address_country\": null,
\"address_line1\": null,
\"address_line1_check\": null,
\"address_line2\": null,
\"address_state\": null,
\"address_zip\": null,
\"address_zip_check\": null,
\"brand\": \"Visa\",
\"country\": \"US\",
\"customer\": \"cus_BoeZzLBOGUiRzw\",
\"cvc_check\": \"pass\",
\"dynamic_last4\": null,
\"exp_month\": 12,
\"exp_year\": 2018,
\"fingerprint\": \"VsTJEtWimRlWw8lh\",
\"funding\": \"credit\",
\"last4\": \"4242\",
\"metadata\": {},
\"name\": null,
\"tokenization_method\": null
}
],
\"has_more\": false,
\"url\": \"/v1/customers/cus_BoeZzLBOGUiRzw/sources\"
}
*/
long stripe_get_all_cards_parse(char *json, vector <string> *ccids, vector <string> *ccdigits, vector <string> *ccexpm, vector <string> *ccexpy)
{
long res = -1L;
json_object *new_obj;
struct json_object *o = NULL;
struct json_object *d = NULL;
new_obj = json_tokener_parse(json);
if (!json_object_object_get_ex(new_obj, "data", &o)) {
logfmt( logINFO, "Field data does not exist" );
return res;
}
enum json_type o_type = json_object_get_type(o);
if (strcmp(json_type_to_name(o_type), "array")==0) {
//snprintf(token, token_size, "%s", json_object_to_json_string(o));
size_t orig_array_len = json_object_array_length(o);
for(size_t i = 0; i < orig_array_len; i++) {
json_object *obj = json_object_array_get_idx(o, i);
if (json_object_object_get_ex(obj, "id", &d)) {
o_type = json_object_get_type(d);
if (strcmp(json_type_to_name(o_type), "string")==0) {
res = 0L;
ccids->push_back(string(json_object_to_json_string(d)));
} else {
logfmt( logINFO, "Id is not string\n" );
if (res<0L) res = -4L;
}
} else {
logfmt( logINFO, "Id is not found\n" );
if (res<0L) res = -3L;
}
if (res==0L) {
if (json_object_object_get_ex(obj, "exp_month", &d)) {
ccexpm->push_back(string(json_object_to_json_string(d)));
}
if (json_object_object_get_ex(obj, "exp_year", &d)) {
ccexpy->push_back(string(json_object_to_json_string(d)));
}
if (json_object_object_get_ex(obj, "last4", &d)) {
ccdigits->push_back(string(json_object_to_json_string(d)));
}
}
}
} else {
logfmt( logINFO, "Data is not array\n" );
res = -2L;
}
json_object_put(new_obj);
return res;
}
/*
curl "https://api.stripe.com/v1/customers/cus_BoeZzLBOGUiRzw/sources?object=card&limit=3" \
-u sk_test_MJYII1vrGGr7RrGw1lhYMiTq: \
-G
*/
long stripe_get_all_cards(const char *key, char *customer, vector <string> *ccids, vector <string> *ccdigits, vector <string> *ccexpm, vector <string> *ccexpy)
{
CURL *curl;
CURLcode res;
long ret = -1L;
//char post_fields[512];
char post_auth[256];
char url[256];
struct MemoryStruct chunk;
chunk.memory = static_cast<char*>(malloc(1)); /* will be grown as needed by the realloc above */
chunk.size = 0; /* no data at this point */
snprintf(url, sizeof(url), "https://api.stripe.com/v1/customers/%s/sources?object=card&limit=100", customer);
curl = prepare_curl(url, (void *)&chunk);
if(curl) {
/*
bzero(post_fields, sizeof(post_fields));
snprintf(post_fields, sizeof(post_fields),
"amount=%d&currency=%s&description=%s&metadata[payment_id]=%s&%s=%s",
amount, currency, description, metadata, entity_name, entity);
*/
/* Now specify the POST data */
// curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields);
bzero(post_auth, sizeof(post_auth));
snprintf(post_auth, sizeof(post_auth), "%s:", key);
/* set user name and password for the authentication */
curl_easy_setopt(curl, CURLOPT_USERPWD, post_auth);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res == CURLE_OK) {
logfmt( logINFO, "%lu bytes retrieved\n", (long)chunk.size);
logfmt( logDEBUG, "%s\n", chunk.memory);
//bzero(token2, token2_size);
ret = stripe_get_all_cards_parse(chunk.memory, ccids, ccdigits, ccexpm, ccexpy);
if (ret != 0L) {
logfmt( logINFO, "Failed to load cards for %s\n", customer);
}
} else {
logfmt( logINFO, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
/* always cleanup */
curl_easy_cleanup(curl);
free(chunk.memory);
}
curl_global_cleanup();
return ret;
}
long stripe_get_card(const char *key, char *customer, const char *ccdigits, const char *ccexpm, const char *ccexpy, char *card, size_t card_size)
{
vector <string> vccids;
vector <string> vccdigits;
vector <string> vccexpm;
vector <string> vccexpy;
char digits[10];
char year[10];
snprintf(digits, sizeof(digits), "\"%s\"", ccdigits);
snprintf(year, sizeof(year), "20%s", ccexpy);
long res = stripe_get_all_cards(key, customer, &vccids, &vccdigits, &vccexpm, &vccexpy);
size_t cnt = vccids.size();
logfmt( logINFO, "Found %lu card(s)\n", cnt );
if (cnt>0 && vccdigits.size()==cnt && vccexpm.size()==cnt && vccexpy.size()==cnt) {
for (int i=0; i<cnt; i++) {
if (vccdigits[i].compare(digits)==0
&& vccexpm[i].compare(ccexpm)==0
&& (vccexpy[i].compare(ccexpy)==0 || vccexpy[i].compare(year)==0)) {
snprintf(card, card_size, "%s", vccids[i].c_str());
return 0L;
} else {
logfmt( logINFO, "No match - skipping:\n'%s'<>'%s'\n'%s'<>'%s'\n'%s'<>'%s'/'%s'\n",
vccdigits[i].c_str(), digits,
vccexpm[i].c_str(), ccexpm,
vccexpy[i].c_str(), ccexpy, year);
}
}
} else {
logfmt( logINFO, "No cards loaded\n" );
res = -1L;
}
return res;
}
/*
\"id\": \"ch_1AwE1GF5FERRcWDyrQ9yxFHO\",\
\"object\": \"charge\",\
\"amount\": 1000,\
\"amount_refunded\": 0,\
\"application\": null,\
\"application_fee\": null,\
\"balance_transaction\": \"txn_1AwE1GF5FERRcWDys1SwbPfO\",\
\"captured\": true,\
\"status\": \"succeeded\"
*/
long stripe_charge_entity_parse(char *json, char *id, size_t id_size)
{
long res = -1L;
json_object *new_obj;
struct json_object *o = NULL;
new_obj = json_tokener_parse(json);
if (!json_object_object_get_ex(new_obj, "id", &o)) {
logfmt( logINFO, "Field id does not exist" );
return res;
}
enum json_type o_type = json_object_get_type(o);
if (strcmp(json_type_to_name(o_type), "string")==0) {
snprintf(id, id_size, "%s", json_object_to_json_string(o));
res = 0L;
} else {
logfmt( logINFO, "Id is not string O__o" );
}
if (res==0L) {
res = -2L; // Status is not "succeeded"
if (!json_object_object_get_ex(new_obj, "status", &o)) {
logfmt( logINFO, "Field status does not exist" );
return -3L;
}
enum json_type status_type = json_object_get_type(o);
if (strcmp(json_type_to_name(status_type), "string")==0) {
if (strcmp(json_object_to_json_string(o),"\"succeeded\"")==0) {
res = 0L;
}
} else {
logfmt( logINFO, "Status is not string O__o" );
res = -4L;
}
}
json_object_put(new_obj);
return res;
}
/*
curl https://api.stripe.com/v1/charges \
-u sk_test_MJYII1vrGGr7RrGw1lhYMiTq: \
-d amount=1000 \
-d currency=usd \
-d description="Example charge" \
-d metadata[order_id]=6735 \
-d source=tok_Mk1gcKiXo5zzgR3U9yUmx8jJ
*/
long stripe_charge_token(const char *key, char *token, int amount, const char *currency, const char *description, const char *metadata, char *id, size_t id_size)
{
return stripe_charge_real(key, "source", token, amount, currency, description, metadata, id, id_size);
}
/*
curl https://api.stripe.com/v1/charges \
-u sk_test_MJYII1vrGGr7RrGw1lhYMiTq: \
-d amount=1000 \
-d currency=usd \
-d description="Example charge" \
-d metadata[order_id]=6735 \
-d customer=cus_0c0ieRnlG77qkw
*/
long stripe_charge_customer(const char *key, char *customer, int amount, const char *currency, const char *description, const char *metadata, char *id, size_t id_size)
{
return stripe_charge_real(key, "customer", customer, amount, currency, description, metadata, id, id_size);
}
long stripe_charge_real(const char *key, const char *entity_name, char *entity, int amount, const char *currency, const char *description, const char *metadata, char *id, size_t id_size)
{
CURL *curl;
CURLcode res;
long ret = -1L;
char post_fields[512];
char post_auth[256];
struct MemoryStruct chunk;
chunk.memory = static_cast<char*>(malloc(1)); /* will be grown as needed by the realloc above */
chunk.size = 0; /* no data at this point */
curl = prepare_curl("https://api.stripe.com/v1/charges", (void *)&chunk);
if(curl) {
bzero(post_fields, sizeof(post_fields));
snprintf(post_fields, sizeof(post_fields),
"amount=%d&currency=%s&description=%s&metadata[payment_id]=%s&%s=%s",
amount, currency, description, metadata, entity_name, entity);
/* Now specify the POST data */
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields);
bzero(post_auth, sizeof(post_auth));
snprintf(post_auth, sizeof(post_auth), "%s:", key);
/* set user name and password for the authentication */
curl_easy_setopt(curl, CURLOPT_USERPWD, post_auth);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res == CURLE_OK) {
logfmt( logINFO, "%lu bytes retrieved\n", (long)chunk.size);
logfmt( logDEBUG, "%s\n", chunk.memory);
//bzero(token2, token2_size);
ret = stripe_charge_entity_parse(chunk.memory, id, id_size);
if (ret != 0L) {
logfmt( logINFO, "Failed to charge %s: %s", entity_name, entity);
}
} else {
logfmt( logINFO, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
/* always cleanup */
curl_easy_cleanup(curl);
free(chunk.memory);
}
curl_global_cleanup();
return ret;
}
/*
"id": "cus_BRNA3itMe3K2qP",
"object": "customer",
"account_balance": 0,
"created": 1505999327,
"currency": "usd",
"default_source": null,
"delinquent": false,
"description": null,
"discount": null,
"email": null,
"livemode": false,
"metadata": {
},
"shipping": null,
"sources": {
"object": "list",
"data": [
],
"has_more": false,
"total_count": 0,
"url": "/v1/customers/cus_BRNA3itMe3K2qP/sources"
},
"subscriptions": {
"object": "list",
"data": [
],
"has_more": false,
"total_count": 0,
"url": "/v1/customers/cus_BRNA3itMe3K2qP/subscriptions"
*/
long stripe_create_customer_parse(char *json, char *id, size_t id_size)
{
long res = -1L;
json_object *new_obj;
struct json_object *o = NULL;
new_obj = json_tokener_parse(json);
if (!json_object_object_get_ex(new_obj, "id", &o)) {
logfmt( logINFO, "Field id does not exist" );
return res;
}
enum json_type o_type = json_object_get_type(o);
if (strcmp(json_type_to_name(o_type), "string")==0) {
snprintf(id, id_size, "%s", json_object_to_json_string(o));
res = 0L;
} else {
logfmt( logINFO, "Id is not string O__o" );
}
if (res==0L) {
res = -2L; // extra check waht we got - customer?
if (!json_object_object_get_ex(new_obj, "object", &o)) {
logfmt( logINFO, "Field object does not exist" );
return -3L;
}
enum json_type object_type = json_object_get_type(o);
if (strcmp(json_type_to_name(object_type), "string")==0) {
if (strcmp(json_object_to_json_string(o),"\"customer\"")==0) {
res = 0L;
}
} else {
logfmt( logINFO, "Object is not string O__o" );
res = -4L;
}
}
json_object_put(new_obj);
return res;
}
/*
curl https://api.stripe.com/v1/customers \
-u sk_test_MJYII1vrGGr7RrGw1lhYMiTq: \
-d description="Olusesan Ameye" \
-d email="paying.user@example.com" \
-d metadata[customer_id]=6735 \
-d source=tok_udFYcI8zrJp4isLhiWPybdpe
*/
long stripe_create_customer(const char *key, char *token, const char *email, const char *description, const char *metadata, char *id, size_t id_size)
{
CURL *curl;
CURLcode res;
long ret = -1L;
char post_fields[512];
char post_auth[256];
struct MemoryStruct chunk;
chunk.memory = static_cast<char*>(malloc(1)); /* will be grown as needed by the realloc above */
chunk.size = 0; /* no data at this point */
curl = prepare_curl("https://api.stripe.com/v1/customers", (void *)&chunk);
if(curl) {
bzero(post_fields, sizeof(post_fields));
snprintf(post_fields, sizeof(post_fields),
"email=%s&description=%s&metadata[customer_id]=%s&source=%s",
email, description, metadata, token);
/* Now specify the POST data */
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields);
bzero(post_auth, sizeof(post_auth));
snprintf(post_auth, sizeof(post_auth), "%s:", key);
/* set user name and password for the authentication */
curl_easy_setopt(curl, CURLOPT_USERPWD, post_auth);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res == CURLE_OK) {
logfmt( logINFO, "%lu bytes retrieved\n", (long)chunk.size);
logfmt( logDEBUG, "%s\n", chunk.memory);
//bzero(token2, token2_size);
ret = stripe_create_customer_parse(chunk.memory, id, id_size);
if (ret != 0L) {
logfmt( logINFO, "Failed to create customer with token: %s", token);
}
} else {
logfmt( logINFO, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
/* always cleanup */
curl_easy_cleanup(curl);
free(chunk.memory);
}
curl_global_cleanup();
return ret;
}
/*
See the test_stripe_update_customer_parse() below
*/
long stripe_update_customer_parse(char *json, const char *result, char *data, size_t data_size)
{
long res = -1L;
json_object *new_obj;
struct json_object *o = NULL;
new_obj = json_tokener_parse(json);
if (!json_object_object_get_ex(new_obj, result, &o)) {
logfmt( logINFO, "Field %s does not exist", result );
return res;
}
res = 0L;
enum json_type o_type = json_object_get_type(o);
if (strcmp(json_type_to_name(o_type), "string")==0) {
snprintf(data, data_size, "%s", json_object_to_json_string(o));
} else if (strcmp(json_type_to_name(o_type), "int")==0) {
snprintf(data, data_size, "%d", json_object_get_int(o));
} else if (strcmp(json_type_to_name(o_type), "int64")==0) {
snprintf(data, data_size, "%lld", json_object_get_int64(o));
} else if (strcmp(json_type_to_name(o_type), "boolean")==0) {
snprintf(data, data_size, "%s", json_object_get_boolean(o) ? "true" : "false");
} else if (strcmp(json_type_to_name(o_type), "double")==0) {
snprintf(data, data_size, "%f", json_object_get_double(o));
} else {
logfmt( logINFO, "Result is not either string, int, int64, boolean or double: %s", json_type_to_name(o_type) );
res = -2L;
}
json_object_put(new_obj);
return res;
}
/*
curl https://api.stripe.com/v1/customers/cus_BoeZzLBOGUiRzw \
-u sk_test_MJYII1vrGGr7RrGw1lhYMiTq:
-d default_source=card_1BR1GQF5FERRcWDyp8ncqhzS
*/
long stripe_update_customer(const char *key, char *customer, const char *entity, const char *entity_value, const char *result, char *data, size_t data_size)
{
CURL *curl;
CURLcode res;
long ret = -1L;
char post_fields[512];
char post_auth[256];
char url[256];
struct MemoryStruct chunk;
chunk.memory = static_cast<char*>(malloc(1)); /* will be grown as needed by the realloc above */
chunk.size = 0; /* no data at this point */
snprintf(url, sizeof(url), "https://api.stripe.com/v1/customers/%s", customer);
curl = prepare_curl(url, (void *)&chunk);
if(curl) {
bzero(post_fields, sizeof(post_fields));
snprintf(post_fields, sizeof(post_fields),
"%s=%s",
entity, entity_value);
/* Now specify the POST data */
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields);
bzero(post_auth, sizeof(post_auth));
snprintf(post_auth, sizeof(post_auth), "%s:", key);
/* set user name and password for the authentication */
curl_easy_setopt(curl, CURLOPT_USERPWD, post_auth);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res == CURLE_OK) {
logfmt( logINFO, "%lu bytes retrieved\n", (long)chunk.size);
logfmt( logDEBUG, "%s\n", chunk.memory);
//bzero(token2, token2_size);
ret = stripe_update_customer_parse(chunk.memory, result, data, data_size);
if (ret != 0L) {
logfmt( logINFO, "Failed to update %s: %s", entity, entity_value);
}
} else {
logfmt( logINFO, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
/* always cleanup */
curl_easy_cleanup(curl);
free(chunk.memory);
}
curl_global_cleanup();
return ret;
}
/*
American Express 34, 37
China UnionPay 62, 88
Diners ClubCarte Blanche 300-305
Diners Club International 300-305, 309, 36, 38-39
Diners Club US & Canada 54, 55
Discover Card 6011, 622126-622925, 644-649, 65
JCB 3528-3589
Laser 6304, 6706, 6771, 6709
Maestro 5018, 5020, 5038, 5612, 5893, 6304, 6759, 6761, 6762, 6763, 0604, 6390
Dankort 5019
MasterCard 50-55
Visa 4
Visa Electron 4026, 417500, 4405, 4508, 4844, 4913, 4917
*/
string stripe_get_card_type(const char *card)
{
string subject(card);
string result;
string cards[17] = {
"Amex",
"BCGlobal",
"Carte Blanche",
"Diners Club",
"Discover",
"Insta Payment",
"JCB",
"KoreanLocalCard",
"Laser",
"Maestro",
"Mastercard",
"Solo",
"Switch",
"Union Pay",
"Visa",
"Visa Master",
"TEST"
};
string regexs[17] = {
"^3[47][0-9]{13}$",
"^(6541|6556)[0-9]{12}$",
"^389[0-9]{11}$",
"^3(?:0[0-5]|[68][0-9])[0-9]{11}$",
"^65[4-9][0-9]{13}|64[4-9][0-9]{13}|6011[0-9]{12}|(622(?:12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9[01][0-9]|92[0-5])[0-9]{10})$",
"^63[7-9][0-9]{13}$",
"^(?:2131|1800|35[[:digit:]]{3})[[:digit:]]{11}$",
"^9[0-9]{15}$",
"^(6304|6706|6709|6771)[0-9]{12,15}$",
"^(5018|5020|5038|6304|6759|6761|6763)[0-9]{8,15}$",
"^5[1-5][0-9]{14}$",
"^(6334|6767)[0-9]{12}|(6334|6767)[0-9]{14}|(6334|6767)[0-9]{15}$",
"^(4903|4905|4911|4936|6333|6759)[0-9]{12}|(4903|4905|4911|4936|6333|6759)[0-9]{14}|(4903|4905|4911|4936|6333|6759)[0-9]{15}|564182[0-9]{10}|564182[0-9]{12}|564182[0-9]{13}|633110[0-9]{10}|633110[0-9]{12}|633110[0-9]{13}$",
"^(62[0-9]{14,17})$",
"^4[0-9]{12}(?:[0-9]{3})?$",
"^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14})$",
"\\.*"
};
try {
for (int i = 0; i < 17; i++) {
//logfmt(logDEBUG, "%s -> %d\n", regexs[i].c_str(), i);
pcrecpp::RE r(regexs[i].c_str());
if (r.FullMatch(subject)) {
result = cards[i];
break;
}
}
} catch (exception& e) {
// Syntax error in the regular expression
logfmt(logDEBUG, "regex_error caught: %s\n", e.what());
}
return result;
}
/*****************************************************************************/
#ifdef TEST
#warning "Test build with main() entry point"
#define STRIPE_KEY "sk_test_MJYII1vrGGr7RrGw1lhYMiTq"
long test_stripe_tokenize_card(char *token, size_t token_size)
{
char *ccnum = "4242424242424242";
char *ccexpm = "12";
char *ccexpy = "18";
char *cccvc = "123";
long res = -1L;
res = stripe_tokenize_card(STRIPE_KEY, ccnum, ccexpm, ccexpy, cccvc, token, token_size);
logfmt( logINFO, "stripe_tokenize_card(%ld)=%s\n", res, token);
return res;
}
long test_stripe_tokenize_card_parse(void)
{
char token[256];
long res = -1L;
char *json = "{\
\"id\": \"tok_1AwCwAF5FERRcWDyTObhpckX\",\
\"object\": \"token\",\
\"card\": {\
\"id\": \"card_1AwCwAF5FERRcWDyP5OGybH4\",\
\"object\": \"card\",\
\"address_city\": null,\
\"address_country\": null,\
\"address_line1\": null,\
\"address_line1_check\": null,\
\"address_line2\": null,\
\"address_state\": null,\
\"address_zip\": null,\
\"address_zip_check\": null,\
\"brand\": \"Visa\",\
\"country\": \"US\",\
\"cvc_check\": \"unchecked\",\
\"dynamic_last4\": null,\
\"exp_month\": 12,\
\"exp_year\": 2018,\
\"fingerprint\": \"VsTJEtWimRlWw8lh\",\
\"funding\": \"credit\",\
\"last4\": \"4242\",\
\"metadata\": {},\
\"name\": null,\
\"tokenization_method\": null\
},\
\"client_ip\": \"176.117.172.40\",\
\"created\": 1504025498,\
\"livemode\": false,\
\"type\": \"card\",\
\"used\": false\
}";
bzero(token, sizeof(token));
res = stripe_tokenize_card_parse(json, token, sizeof(token));
logfmt( logINFO, "stripe_tokenize_card_parse(%ld)=%s\n", res, token);
return res;
}
long test_stripe_charge_token(char *token, char *id, size_t id_size)
{
long res = -1L;
int amount = 1000;
const char *currency = "usd";
const char *description = "Example charge";
const char *metadata = "6735";
res = stripe_charge_token(STRIPE_KEY, token, amount, currency, description, metadata, id, id_size);
logfmt( logINFO, "stripe_charge_token(%ld)=%s\n", res, id);
return res;
}
long test_stripe_charge_customer(char *customer, char *id, size_t id_size)
{
long res = -1L;
int amount = 1000;
const char *currency = "usd";
const char *description = "Example charge";
const char *metadata = "6735";
res = stripe_charge_customer(STRIPE_KEY, customer, amount, currency, description, metadata, id, id_size);
logfmt( logINFO, "stripe_charge_customer(%ld)=%s\n", res, id);
return res;
}
long test_stripe_charge_entity_parse(void)
{
long res = -1L;
char *json = "{\
\"id\": \"ch_1AwE1GF5FERRcWDyrQ9yxFHO\",\
\"object\": \"charge\",\
\"amount\": 1000,\
\"amount_refunded\": 0,\
\"application\": null,\
\"application_fee\": null,\
\"balance_transaction\": \"txn_1AwE1GF5FERRcWDys1SwbPfO\",\
\"captured\": true,\
\"created\": 1504029658,\
\"currency\": \"usd\",\
\"customer\": null,\
\"description\": \"Example charge\",\
\"destination\": null,\
\"dispute\": null,\
\"failure_code\": null,\
\"failure_message\": null,\
\"fraud_details\": {},\
\"invoice\": null,\
\"livemode\": false,\
\"metadata\": {\
\"payment_id\": \"6735\"\
},\
\"on_behalf_of\": null,\
\"order\": null,\
\"outcome\": {\
\"network_status\": \"approved_by_network\",\
\"reason\": null,\
\"risk_level\": \"normal\",\
\"seller_message\": \"Payment complete.\",\
\"type\": \"authorized\"\
},\
\"paid\": true,\
\"receipt_email\": null,\
\"receipt_number\": null,\
\"refunded\": false,\
\"refunds\": {\
\"object\": \"list\",\
\"data\": [],\
\"has_more\": false,\
\"total_count\": 0,\
\"url\": \"/v1/charges/ch_1AwE1GF5FERRcWDyrQ9yxFHO/refunds\"\
},\
\"review\": null,\
\"shipping\": null,\
\"source\": {\
\"id\": \"card_1AwE1GF5FERRcWDyBrNIFHFj\",\
\"object\": \"card\",\
\"address_city\": null,\
\"address_country\": null,\
\"address_line1\": null,\
\"address_line1_check\": null,\
\"address_line2\": null,\
\"address_state\": null,\
\"address_zip\": null,\
\"address_zip_check\": null,\
\"brand\": \"Visa\",\
\"country\": \"US\",\
\"customer\": null,\
\"cvc_check\": null,\
\"dynamic_last4\": null,\
\"exp_month\": 8,\
\"exp_year\": 2019,\
\"fingerprint\": \"VsTJEtWimRlWw8lh\",\
\"funding\": \"credit\",\
\"last4\": \"4242\",\
\"metadata\": {},\
\"name\": null,\
\"tokenization_method\": null\
},\
\"source_transfer\": null,\
\"statement_descriptor\": null,\
\"status\": \"succeeded\",\
\"transfer_group\": null\
}";
char id[256];
bzero(id, sizeof(id));
res = stripe_charge_entity_parse(json, id, sizeof(id));
logfmt( logINFO, "stripe_charge_token_parse(%ld)=%s\n", res, id);
return res;
}
long test_stripe_create_customer(char *token, char *id, size_t id_size)
{
long res = -1L;
char *email = "ameye@chiefsoft";
char *description = "Example Customer";
char *metadata = "6735";
res = stripe_create_customer(STRIPE_KEY, token, email, description, metadata, id, id_size);
logfmt( logINFO, "stripe_create_customer(%ld)=%s\n", res, id);
return res;
}
long test_stripe_create_customer_parse()
{
long res = -1L;
char *json = "{\
\"id\": \"cus_BRNA3itMe3K2qP\",\
\"object\": \"customer\",\
\"account_balance\": 0,\
\"created\": 1505999327,\
\"currency\": \"usd\",\
\"default_source\": null,\
\"delinquent\": false,\
\"description\": null,\
\"discount\": null,\
\"email\": null,\
\"livemode\": false,\
\"metadata\": {\
},\
\"shipping\": null,\
\"sources\": {\
\"object\":\"list\",\
\"data\": [\
],\
\"has_more\": false,\
\"total_count\": 0,\
\"url\": \"/v1/customers/cus_BRNA3itMe3K2qP/sources\"\
},\
\"subscriptions\": {\
\"object\": \"list\",\
\"data\": [\
],\
\"has_more\": false,\
\"total_count\": 0,\
\"url\": \"/v1/customers/cus_BRNA3itMe3K2qP/subscriptions\"\
}\
}";
char id[256];
bzero(id, sizeof(id));
res = stripe_create_customer_parse(json, id, sizeof(id));
logfmt( logINFO, "\nstripe_create_customer_parse(%ld)=%s\n", res, id);
return res;
}
long test_stripe_get_card(char *customer, char *card, size_t card_size)
{
char *ccdigits = "4242";
char *ccexpm = "12";
char *ccexpy = "18";
long res = -1L;
res = stripe_get_card(STRIPE_KEY, customer, ccdigits, ccexpm, ccexpy, card, card_size);
logfmt( logINFO, "stripe_get_card(%ld)=%s\n", res, card);
return res;
}
long test_stripe_get_all_cards_parse()
{
long res = -1L;
char *json = "{\
\"object\": \"list\",\
\"data\": [\
{\
\"id\": \"card_1BR1GQF5FERRcWDyp8ncqhzS\",\
\"object\": \"card\",\
\"address_city\": null,\
\"address_country\": null,\
\"address_line1\": null,\
\"address_line1_check\": null,\
\"address_line2\": null,\
\"address_state\": null,\
\"address_zip\": null,\
\"address_zip_check\": null,\
\"brand\": \"Visa\",\
\"country\": \"US\",\
\"customer\": \"cus_BoeZzLBOGUiRzw\",\
\"cvc_check\": \"pass\",\
\"dynamic_last4\": null,\
\"exp_month\": 12,\
\"exp_year\": 2018,\
\"fingerprint\": \"VsTJEtWimRlWw8lh\",\
\"funding\": \"credit\",\
\"last4\": \"4242\",\
\"metadata\": {},\
\"name\": null,\
\"tokenization_method\": null\
}\
],\
\"has_more\": false,\
\"url\": \"/v1/customers/cus_BoeZzLBOGUiRzw/sources\"\
}";
vector <string> vccids;
vector <string> vccdigits;
vector <string> vccexpm;
vector <string> vccexpy;
res = stripe_get_all_cards_parse(json, &vccids, &vccdigits, &vccexpm, &vccexpy);
logfmt( logINFO, "\nstripe_get_all_cards_parse(%ld):\n", res);
if (vccids.size()>0) {
for (int i=0; i<vccids.size(); i++) {
logfmt( logINFO, "\nid=%s\n", vccids[i].c_str() );
logfmt( logINFO, "\nlast4=%s\n", vccdigits[i].c_str() );
logfmt( logINFO, "\nexp_month=%s\n", vccexpm[i].c_str() );
logfmt( logINFO, "\nexp_year=%s\n", vccexpy[i].c_str() );
}
}
return res;
}
long test_stripe_create_card(char *customer, char *token, char *card, size_t card_size)
{
long res = -1L;
res = stripe_create_card(STRIPE_KEY, customer, token, card, card_size);
logfmt( logINFO, "stripe_create_card(%ld)=%s\n", res, card);
return res;
}
long test_stripe_create_card_parse()
{
long res = -1L;
char *json = "{\
\"id\": \"card_1BRNdFF5FERRcWDyUdOaIBzd\",\
\"object\": \"card\",\
\"address_city\": null,\
\"address_country\": null,\
\"address_line1\": null,\
\"address_line1_check\": null,\
\"address_line2\": null,\
\"address_state\": null,\
\"address_zip\": null,\
\"address_zip_check\": null,\
\"brand\": \"American Express\",\
\"country\": \"US\",\
\"customer\": \"cus_BoeZzLBOGUiRzw\",\
\"cvc_check\": null,\
\"dynamic_last4\": null,\
\"exp_month\": 8,\
\"exp_year\": 2019,\
\"fingerprint\": \"9KROBb1pqZA216C4\",\
\"funding\": \"credit\",\
\"last4\": \"8431\",\
\"metadata\": {},\
\"name\": null,\
\"tokenization_method\": null\
}";
char card[32];
bzero(card, sizeof(card));
res = stripe_create_card_parse(json, card, sizeof(card));
logfmt( logINFO, "\nstripe_create_card_parse(%ld)=%s\n", res, card);
return res;
}
long test_stripe_update_customer(char *customer, char *data, size_t data_size)
{
long res = -1L;
const char *entity = "default_source";
const char *entity_value = "card_1BRNdFF5FERRcWDyUdOaIBzd"; // "card_1BR1GQF5FERRcWDyp8ncqhzS";
const char *result = "default_source";
res = stripe_update_customer(STRIPE_KEY, customer, entity, entity_value, result, data, data_size);
logfmt( logINFO, "stripe_update_customer(%ld): %s=%s\n", res, result, data);
return res;
}
long test_stripe_update_customer_parse(void)
{
char data[64];
long res = -1L;
char *json = "{\
\"id\": \"cus_BoeZzLBOGUiRzw\",\
\"object\": \"customer\",\
\"account_balance\": 0,\
\"created\": 1511368795,\
\"currency\": null,\
\"default_source\": \"card_1BR1GQF5FERRcWDyp8ncqhzS\",\
\"delinquent\": false,\
\"description\": \"This is a test\",\
\"discount\": null,\
\"email\": \"ameye@chiefsoft\",\
\"livemode\": false,\
\"metadata\": {\
\"customer_id\": \"6735\"\
},\
\"shipping\": null,\
\"sources\": {\
\"object\": \"list\",\
\"data\": [\
{\
\"id\": \"card_1BR1GQF5FERRcWDyp8ncqhzS\",\
\"object\": \"card\",\
\"address_city\": null,\
\"address_country\": null,\
\"address_line1\": null,\
\"address_line1_check\": null,\
\"address_line2\": null,\
\"address_state\": null,\
\"address_zip\": null,\
\"address_zip_check\": null,\
\"brand\": \"Visa\",\
\"country\": \"US\",\
\"customer\": \"cus_BoeZzLBOGUiRzw\",\
\"cvc_check\": \"pass\",\
\"dynamic_last4\": null,\
\"exp_month\": 12,\
\"exp_year\": 2018,\
\"fingerprint\": \"VsTJEtWimRlWw8lh\",\
\"funding\": \"credit\",\
\"last4\": \"4242\",\
\"metadata\": {},\
\"name\": null,\
\"tokenization_method\": null\
},\
{\
\"id\": \"card_1BRO8qF5FERRcWDyPx5g5Tmu\",\
\"object\": \"card\",\
\"address_city\": null,\
\"address_country\": null,\
\"address_line1\": null,\
\"address_line1_check\": null,\
\"address_line2\": null,\
\"address_state\": null,\
\"address_zip\": null,\
\"address_zip_check\": null,\
\"brand\": \"Visa\",\
\"country\": \"US\",\
\"customer\": \"cus_BoeZzLBOGUiRzw\",\
\"cvc_check\": null,\
\"dynamic_last4\": null,\
\"exp_month\": 8,\
\"exp_year\": 2019,\
\"fingerprint\": \"VsTJEtWimRlWw8lh\",\
\"funding\": \"credit\",\
\"last4\": \"4242\",\
\"metadata\": {},\
\"name\": null,\
\"tokenization_method\": null\
},\
{\
\"id\": \"card_1BRO7xF5FERRcWDy8CUE72GH\",\
\"object\": \"card\",\
\"address_city\": null,\
\"address_country\": null,\
\"address_line1\": null,\
\"address_line1_check\": null,\
\"address_line2\": null,\
\"address_state\": null,\
\"address_zip\": null,\
\"address_zip_check\": null,\
\"brand\": \"Visa\",\
\"country\": \"US\",\
\"customer\": \"cus_BoeZzLBOGUiRzw\",\
\"cvc_check\": null,\
\"dynamic_last4\": null,\
\"exp_month\": 8,\
\"exp_year\": 2019,\
\"fingerprint\": \"VsTJEtWimRlWw8lh\",\
\"funding\": \"credit\",\
\"last4\": \"4242\",\
\"metadata\": {},\
\"name\": null,\
\"tokenization_method\": null\
},\
{\
\"id\": \"card_1BRNdFF5FERRcWDyUdOaIBzd\",\
\"object\": \"card\",\
\"address_city\": null,\
\"address_country\": null,\
\"address_line1\": null,\
\"address_line1_check\": null,\
\"address_line2\": null,\
\"address_state\": null,\
\"address_zip\": null,\
\"address_zip_check\": null,\
\"brand\": \"American Express\",\
\"country\": \"US\",\
\"customer\": \"cus_BoeZzLBOGUiRzw\",\
\"cvc_check\": null,\
\"dynamic_last4\": null,\
\"exp_month\": 8,\
\"exp_year\": 2019,\
\"fingerprint\": \"9KROBb1pqZA216C4\",\
\"funding\": \"credit\",\
\"last4\": \"8431\",\
\"metadata\": {},\
\"name\": null,\
\"tokenization_method\": null\
}\
],\
\"has_more\": false,\
\"total_count\": 4,\
\"url\": \"/v1/customers/cus_BoeZzLBOGUiRzw/sources\"\
},\
\"subscriptions\": {\
\"object\": \"list\",\
\"data\": [],\
\"has_more\": false,\
\"total_count\": 0,\
\"url\": \"/v1/customers/cus_BoeZzLBOGUiRzw/subscriptions\"\
}\
}";
const char *result = "default_source";
bzero(data, sizeof(data));
res = stripe_update_customer_parse(json, result, data, sizeof(data));
logfmt( logINFO, "stripe_update_customer_parse(%ld): %s=%s\n", res, result, data);
return res;
}
int main(void)
{
// https://stripe.com/docs/testing#cards
char token[64];
char customer[64];
char id[64];
char card[64];
//test_stripe_tokenize_card_parse();
//test_stripe_charge_entity_parse();
//test_stripe_create_customer_parse();
//test_stripe_get_all_cards_parse();
//test_stripe_create_card_parse();
//test_stripe_update_customer_parse();
/*
string cards[] = {
"4242424242424242", // Visa
"5555555555554444", // Mastercard
"378282246310005", // AmEx
"6011111111111117", // Discover
"30569309025904", // Diners Club
"3530111333300000", // JCB
"6250946000000016", // Union Pay
"630490017740292441", // Laser
"6759649826438453", // Maestro
"6334589898000001", // Solo
"122000000000003", // Airplus
"5555555555554444", // Cartebleue
"5019717010103742", // Dankort
"6331101999990016" // Switch
};
int n = (sizeof(cards)/sizeof(*cards));
for (int i=0; i<n; i++) {
logfmt(logINFO, "Card %s is %s\n", cards[i].c_str(), stripe_get_card_type(cards[i].c_str()).c_str());
}
//*/
/*
bzero(token, sizeof(token));
if (test_stripe_tokenize_card(token, sizeof(token)) == 0L) {
logfmt(logINFO, "Loaded token: %s\n", token);
}
remove_all_chars(token, '"');
//*/
/*
bzero(id, sizeof(id));
if (test_stripe_charge_token(token, id, sizeof(id)) == 0L) {
logfmt(logINFO, "Charged token: %s\n", id);
}
//*/
/*
bzero(customer, sizeof(customer));
if (test_stripe_create_customer(token, customer, sizeof(customer)) == 0L) {
logfmt(logINFO, "Created customer: %s\n", customer);
}
remove_all_chars(customer, '"');
//*/
/*
bzero(id, sizeof(id));
if (test_stripe_charge_customer(customer, id, sizeof(id)) == 0L) {
logfmt(logINFO, "Charged customer: %s\n", id);
}
//*/
/*
snprintf(customer, sizeof(customer), "cus_BoeZzLBOGUiRzw");
bzero(card, sizeof(card));
if (test_stripe_get_card(customer, card, sizeof(card)) == 0L) {
logfmt(logINFO, "Loaded card: %s\n", card);
}
//*/
/*
snprintf(customer, sizeof(customer), "cus_BoeZzLBOGUiRzw");
snprintf(token, sizeof(token), "tok_visa");
bzero(card, sizeof(card));
if (test_stripe_create_card(customer, token, card, sizeof(card)) == 0L) {
logfmt(logINFO, "Created card: %s\n", card);
}
//*/
/*
snprintf(customer, sizeof(customer), "cus_BoeZzLBOGUiRzw");
bzero(card, sizeof(card));
if (test_stripe_update_customer(customer, card, sizeof(card)) == 0L) {
logfmt(logINFO, "Customer updated: %s\n", card);
}
//*/
return 0;
}
#endif