#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 month[10];
  char year[10];
  snprintf(digits, sizeof(digits), "\"%s\"", ccdigits);
  snprintf(month, sizeof(month), "%d", atoi(ccexpm));
  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 || vccexpm[i].compare(month)==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, "Check 1: %s", vccdigits[i].compare(digits)==0 ? "Yes" : "No");
	logfmt( logINFO, "Check 2: %s", (vccexpm[i].compare(ccexpm)==0 || vccexpm[i].compare(month)==0) ? "Yes" : "No");
	logfmt( logINFO, "Check 3: %s", (vccexpy[i].compare(ccexpy)==0 || vccexpy[i].compare(year)==0) ? "Yes" : "No");
        logfmt( logINFO, "No match - skipping:\n'%s'<>'%s'\n'%s'<>'%s' or '%s'<>'%s'\n'%s'<>'%s' or '%s'<>'%s'\n",
          vccdigits[i].c_str(), digits,
          vccexpm[i].c_str(), ccexpm, vccexpm[i].c_str(), month,
          vccexpy[i].c_str(), ccexpy, vccexpy[i].c_str(), 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

