merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-setup-reserve.c (12394B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2020 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file merchant-tools/taler-merchant-setup-reserve.c
     18  * @brief Create reserve for tipping
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include <taler/taler_util.h>
     23 #include <microhttpd.h>
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include "taler_merchant_service.h"
     26 
     27 /**
     28  * How often do we try before giving up?
     29  */
     30 #define MAX_TRIES 30
     31 
     32 /**
     33  * Return value from main().
     34  */
     35 static int global_ret;
     36 
     37 /**
     38  * Initial amount the reserve will be filled with.
     39  */
     40 static struct TALER_Amount initial_amount;
     41 
     42 /**
     43  * Base URL of the merchant (with instance) to create the reserve for.
     44  */
     45 static char *merchant_base_url;
     46 
     47 /**
     48  * Base URL of the exchange to create the reserve at.
     49  */
     50 static char *exchange_base_url;
     51 
     52 /**
     53  * Wire method to use.
     54  */
     55 static char *wire_method;
     56 
     57 /**
     58  * Operation handle.
     59  */
     60 static struct TALER_MERCHANT_PostReservesHandle *prh;
     61 
     62 /**
     63  * Our context for making HTTP requests.
     64  */
     65 static struct GNUNET_CURL_Context *ctx;
     66 
     67 /**
     68  * Reschedule context.
     69  */
     70 static struct GNUNET_CURL_RescheduleContext *rc;
     71 
     72 /**
     73  * Username and password to use for client authentication
     74  * (optional).
     75  */
     76 static char *userpass;
     77 
     78 /**
     79  * Type of the client's TLS certificate (optional).
     80  */
     81 static char *certtype;
     82 
     83 /**
     84  * File with the client's TLS certificate (optional).
     85  */
     86 static char *certfile;
     87 
     88 /**
     89  * File with the client's TLS private key (optional).
     90  */
     91 static char *keyfile;
     92 
     93 /**
     94  * This value goes in the Authorization:-header.
     95  */
     96 static char *apikey;
     97 
     98 /**
     99  * Passphrase to decrypt client's TLS private key file (optional).
    100  */
    101 static char *keypass;
    102 
    103 /**
    104  * How often have we tried?
    105  */
    106 static unsigned int tries;
    107 
    108 /**
    109  * Task to do the main work.
    110  */
    111 static struct GNUNET_SCHEDULER_Task *task;
    112 
    113 
    114 /**
    115  * Shutdown task (invoked when the process is being terminated)
    116  *
    117  * @param cls NULL
    118  */
    119 static void
    120 do_shutdown (void *cls)
    121 {
    122   if (NULL != task)
    123   {
    124     GNUNET_SCHEDULER_cancel (task);
    125     task = NULL;
    126   }
    127   if (NULL != ctx)
    128   {
    129     GNUNET_CURL_fini (ctx);
    130     ctx = NULL;
    131   }
    132   if (NULL != rc)
    133   {
    134     GNUNET_CURL_gnunet_rc_destroy (rc);
    135     rc = NULL;
    136   }
    137   if (NULL != prh)
    138   {
    139     TALER_MERCHANT_reserves_post_cancel (prh);
    140     prh = NULL;
    141   }
    142 }
    143 
    144 
    145 /**
    146  * Function that makes the call to setup the reserve.
    147  *
    148  * @param cls NULL
    149  */
    150 static void
    151 do_request (void *cls);
    152 
    153 
    154 /**
    155  * Callbacks of this type are used to work the result of submitting a
    156  * POST /reserves request to a merchant
    157  *
    158  * @param cls closure
    159  * @param prr response details
    160  */
    161 static void
    162 result_cb (void *cls,
    163            const struct TALER_MERCHANT_PostReservesResponse *prr)
    164 {
    165   (void) cls;
    166   prh = NULL;
    167   switch (prr->hr.http_status)
    168   {
    169   case MHD_HTTP_OK:
    170     {
    171       char res_str[sizeof (prr->details.ok.reserve_pub) * 2 + 1];
    172 
    173       GNUNET_STRINGS_data_to_string (&prr->details.ok.reserve_pub,
    174                                      sizeof (prr->details.ok.reserve_pub),
    175                                      res_str,
    176                                      sizeof (res_str));
    177       for (unsigned int i = 0; i<prr->details.ok.accounts_len; i++)
    178       {
    179         const struct TALER_EXCHANGE_WireAccount *wa
    180           = &prr->details.ok.accounts[i];
    181         const char *payto_uri = wa->payto_uri;
    182         bool skip = false;
    183 
    184         for (unsigned int j = 0; j<wa->credit_restrictions_length; j++)
    185           if (TALER_EXCHANGE_AR_DENY ==
    186               wa->credit_restrictions[j].type)
    187             skip = true;
    188         if (skip)
    189           continue;
    190         if (NULL != strchr (payto_uri, '?'))
    191           fprintf (stdout,
    192                    "%s&message=%s\n",
    193                    payto_uri,
    194                    res_str);
    195         else
    196           fprintf (stdout,
    197                    "%s?message=%s\n",
    198                    payto_uri,
    199                    res_str);
    200         if (NULL != wa->conversion_url)
    201           fprintf (stdout,
    202                    "\tConversion needed: %s\n",
    203                    wa->conversion_url);
    204         for (unsigned int j = 0; j<wa->credit_restrictions_length; j++)
    205         {
    206           const struct TALER_EXCHANGE_AccountRestriction *cr
    207             = &wa->credit_restrictions[j];
    208 
    209           switch (cr->type)
    210           {
    211           case TALER_EXCHANGE_AR_INVALID:
    212             GNUNET_assert (0);
    213             break;
    214           case TALER_EXCHANGE_AR_DENY:
    215             GNUNET_assert (0);
    216             break;
    217           case TALER_EXCHANGE_AR_REGEX:
    218             fprintf (stdout,
    219                      "\tCredit restriction: %s (%s)\n",
    220                      cr->details.regex.human_hint,
    221                      cr->details.regex.posix_egrep);
    222             break;
    223           }
    224         }
    225       }
    226     }
    227     break;
    228   case MHD_HTTP_CONFLICT:
    229     fprintf (stderr,
    230              "Conflict trying to setup reserve: %u/%d\nHint: %s\n",
    231              prr->hr.http_status,
    232              (int) prr->hr.ec,
    233              prr->hr.hint);
    234     global_ret = 1;
    235     break;
    236   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    237   case MHD_HTTP_BAD_GATEWAY:
    238     tries++;
    239     if (tries < MAX_TRIES)
    240     {
    241       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    242                   "Merchant failed, will try again.\n");
    243       task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
    244                                            &do_request,
    245                                            NULL);
    246       return;
    247     }
    248     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    249                 "Merchant failed too often (%u/%d), giving up\n",
    250                 prr->hr.http_status,
    251                 prr->hr.ec);
    252     global_ret = 1;
    253     break;
    254   default:
    255     fprintf (stderr,
    256              "Unexpected backend failure: %u/%d\nHint: %s\n",
    257              prr->hr.http_status,
    258              (int) prr->hr.ec,
    259              prr->hr.hint);
    260     global_ret = 1;
    261     break;
    262   }
    263   GNUNET_SCHEDULER_shutdown ();
    264 }
    265 
    266 
    267 /**
    268  * Main function that will be run.
    269  *
    270  * @param cls closure
    271  * @param args remaining command-line arguments
    272  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    273  * @param config configuration
    274  */
    275 static void
    276 run (void *cls,
    277      char *const *args,
    278      const char *cfgfile,
    279      const struct GNUNET_CONFIGURATION_Handle *config)
    280 {
    281   /* setup HTTP client event loop */
    282   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    283                           &rc);
    284   rc = GNUNET_CURL_gnunet_rc_create (ctx);
    285   if (NULL != userpass)
    286     GNUNET_CURL_set_userpass (ctx,
    287                               userpass);
    288   if (NULL != keyfile)
    289     GNUNET_CURL_set_tlscert (ctx,
    290                              certtype,
    291                              certfile,
    292                              keyfile,
    293                              keypass);
    294   if (NULL != apikey)
    295   {
    296     char *auth_header;
    297 
    298     GNUNET_asprintf (&auth_header,
    299                      "%s: %s",
    300                      MHD_HTTP_HEADER_AUTHORIZATION,
    301                      apikey);
    302     if (GNUNET_OK !=
    303         GNUNET_CURL_append_header (ctx,
    304                                    auth_header))
    305     {
    306       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    307                   "Failed so set %s header, trying without\n",
    308                   MHD_HTTP_HEADER_AUTHORIZATION);
    309     }
    310     GNUNET_free (auth_header);
    311   }
    312   /* setup termination logic */
    313   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    314                                  NULL);
    315   task = GNUNET_SCHEDULER_add_now (&do_request,
    316                                    NULL);
    317 }
    318 
    319 
    320 static void
    321 do_request (void *cls)
    322 {
    323   (void) cls;
    324   task = NULL;
    325   /* run actual (async) operation */
    326   prh = TALER_MERCHANT_reserves_post (ctx,
    327                                       merchant_base_url,
    328                                       &initial_amount,
    329                                       exchange_base_url,
    330                                       wire_method,
    331                                       &result_cb,
    332                                       NULL);
    333   if (NULL == prh)
    334   {
    335     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    336                 "Failed to begin operation with merchant backend!\n");
    337     global_ret = 2;
    338     GNUNET_SCHEDULER_shutdown ();
    339     return;
    340   }
    341 }
    342 
    343 
    344 /**
    345  * The main function for setting up reserves for tipping.
    346  *
    347  * @param argc number of arguments from the command line
    348  * @param argv command line arguments
    349  * @return 0 ok, 1 on error
    350  */
    351 int
    352 main (int argc,
    353       char *const *argv)
    354 {
    355   struct GNUNET_GETOPT_CommandLineOption options[] = {
    356     GNUNET_GETOPT_option_mandatory (
    357       TALER_getopt_get_amount ('a',
    358                                "amount",
    359                                "VALUE",
    360                                "amount to be transferred into the reserve",
    361                                &initial_amount)),
    362     GNUNET_GETOPT_option_string ('A',
    363                                  "auth",
    364                                  "USERNAME:PASSWORD",
    365                                  "use the given USERNAME and PASSWORD for client authentication",
    366                                  &userpass),
    367     GNUNET_GETOPT_option_string ('C',
    368                                  "cert",
    369                                  "CERTFILE",
    370                                  "name of the TLS client certificate file",
    371                                  &certfile),
    372     GNUNET_GETOPT_option_mandatory (
    373       GNUNET_GETOPT_option_string ('e',
    374                                    "exchange-url",
    375                                    "URL",
    376                                    "base URL of the exchange to create the reserve at",
    377                                    &exchange_base_url)),
    378     GNUNET_GETOPT_option_string ('k',
    379                                  "key",
    380                                  "KEYFILE",
    381                                  "file with the private TLS key for TLS client authentication",
    382                                  &keyfile),
    383     GNUNET_GETOPT_option_mandatory (
    384       GNUNET_GETOPT_option_string ('m',
    385                                    "merchant-url",
    386                                    "URL",
    387                                    "base URL of the merchant backend's REST API",
    388                                    &merchant_base_url)),
    389     GNUNET_GETOPT_option_string ('p',
    390                                  "pass",
    391                                  "KEYFILEPASSPHRASE",
    392                                  "passphrase needed to decrypt the TLS client private key file",
    393                                  &keypass),
    394     GNUNET_GETOPT_option_string ('K',
    395                                  "apikey",
    396                                  "APIKEY",
    397                                  "API key to use in the HTTP request",
    398                                  &apikey),
    399     GNUNET_GETOPT_option_string ('t',
    400                                  "type",
    401                                  "CERTTYPE",
    402                                  "type of the TLS client certificate, defaults to PEM if not specified",
    403                                  &certtype),
    404     GNUNET_GETOPT_option_version (PACKAGE_VERSION "-" VCS_VERSION),
    405     GNUNET_GETOPT_option_mandatory (
    406       GNUNET_GETOPT_option_string ('w',
    407                                    "wire-method",
    408                                    "METHOD",
    409                                    "wire method to use for the wire transfer (i.e. IBAN)",
    410                                    &wire_method)),
    411     GNUNET_GETOPT_OPTION_END
    412   };
    413   enum GNUNET_GenericReturnValue ret;
    414 
    415   /* force linker to link against libtalerutil; if we do
    416      not do this, the linker may "optimize" libtalerutil
    417      away and skip #TALER_OS_init(), which we do need */
    418   (void) TALER_project_data_default ();
    419   ret = GNUNET_PROGRAM_run (
    420     argc, argv,
    421     "taler-merchant-setup-reserve",
    422     gettext_noop ("Setup reserve for tipping"),
    423     options,
    424     &run, NULL);
    425   if (GNUNET_SYSERR == ret)
    426     return 3;
    427   if (GNUNET_NO == ret)
    428     return 0;
    429   return global_ret;
    430 }
    431 
    432 
    433 /* end of taler-merchant-setup-reserve.c */