Line data    Source code 
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2014-2024 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU Affero General Public License as published by the Free
       7              :   Software 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 Lesser General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU Lesser General Public License along with
      14              :   TALER; see the file COPYING.LIB.  If not, see <http://www.gnu.org/licenses/>
      15              : */
      16              : /**
      17              :  * @file taler_merchant_pay_service.c
      18              :  * @brief Implementation of the the ideology
      19              :  *        from the pay_service as copy of
      20              :  *        merchant_api_post_order_pay.c
      21              :  * @author Bohdan Potuzhnyi
      22              :  */
      23              : #include "platform.h"
      24              : #include <curl/curl.h>
      25              : #include <gnunet/gnunet_common.h>
      26              : #include <gnunet/gnunet_json_lib.h>
      27              : #include <jansson.h>
      28              : #include <microhttpd.h>
      29              : #include <gnunet/gnunet_util_lib.h>
      30              : #include <gnunet/gnunet_curl_lib.h>
      31              : #include "taler_merchant_service.h"
      32              : #include "taler_merchant_pay_service.h"
      33              : #include "merchant_api_common.h"
      34              : #include "merchant_api_curl_defaults.h"
      35              : #include <stdio.h>
      36              : #include <taler/taler_json_lib.h>
      37              : #include <taler/taler_signatures.h>
      38              : #include <taler/taler_exchange_service.h>
      39              : #include <taler/taler_curl_lib.h>
      40              : 
      41              : /**
      42              :  * @brief A Pay Handle
      43              :  */
      44              : struct TALER_MERCHANT_OrderPayHandle
      45              : {
      46              :   /**
      47              :    * Reference to the GNUNET CURL execution context.
      48              :    */
      49              :   struct GNUNET_CURL_Context   *ctx;
      50              : 
      51              :   /**
      52              :    * Callback to invoke with the payment result ("pay" mode).
      53              :    */
      54              :   TALER_MERCHANT_OrderPayCallback cb;
      55              : 
      56              :   /**
      57              :    * Closure data for @a cb.
      58              :    */
      59              :   TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE *cb_cls;
      60              : 
      61              :   /* Mandatory scalars: */
      62              : 
      63              :   /**
      64              :    * Base URL of the merchant service.
      65              :    */
      66              :   char *merchant_url;
      67              : 
      68              :   /**
      69              :    * Identifier of the order being paid.
      70              :    */
      71              :   char *order_id;
      72              : 
      73              :   /**
      74              :    * Session identifier for this payment attempt.
      75              :    */
      76              :   char *session_id;
      77              : 
      78              :   /**
      79              :    * Timestamp when the payment request was created.
      80              :    */
      81              :   struct GNUNET_TIME_Timestamp timestamp;
      82              : 
      83              :   /**
      84              :    * Deadline after which refunds are no longer allowed.
      85              :    */
      86              :   struct GNUNET_TIME_Timestamp refund_deadline;
      87              : 
      88              :   /**
      89              :    * Wire hash for communicating payment details.
      90              :    */
      91              :   struct TALER_MerchantWireHashP h_wire;
      92              : 
      93              :   /**
      94              :    * Indicates whether @a h_wire has been set.
      95              :    */
      96              :   bool has_h_wire;
      97              : 
      98              :   /* Wallet mode fields: */
      99              : 
     100              :   /**
     101              :    * Indicates whether a contract hash was provided.
     102              :    */
     103              :   bool has_h_contract;
     104              : 
     105              :   /**
     106              :    * Hash of the private contract terms (wallet mode only).
     107              :    */
     108              :   struct TALER_PrivateContractHashP h_contract_terms;
     109              : 
     110              :   /**
     111              :    * Indicates whether the merchant public key was provided.
     112              :    */
     113              :   bool has_merchant_pub;
     114              : 
     115              :   /**
     116              :    * Merchant’s public key for verifying signatures (wallet mode).
     117              :    */
     118              :   struct TALER_MerchantPublicKeyP merchant_pub;
     119              : 
     120              :   /**
     121              :    * Indicates whether a choice index was provided.
     122              :    */
     123              :   bool has_choice_index;
     124              : 
     125              :   /**
     126              :    * Selected index of the contract choice (for token operations).
     127              :    */
     128              :   int choice_index;
     129              : 
     130              :   /**
     131              :    * Legacy: pointer to the amount structure for strcmp checks.
     132              :    */
     133              :   const struct TALER_Amount *amount;
     134              : 
     135              :   /**
     136              :    * Legacy: pointer to the maximum fee structure for strcmp checks.
     137              :    */
     138              :   const struct TALER_Amount *max_fee;
     139              : 
     140              :   /* Raw arrays as passed in via set_options(): */
     141              : 
     142              :   /**
     143              :    * Coins used for payment.
     144              :    */
     145              :   struct
     146              :   {
     147              :     /**
     148              :      * Number of coins provided.
     149              :      */
     150              :     unsigned int num_coins;
     151              :     /**
     152              :      * Array of coins to spend.
     153              :      */
     154              :     const struct TALER_MERCHANT_PayCoin *coins;
     155              :   } coins;
     156              : 
     157              :   /**
     158              :    * Input tokens to use (wallet mode).
     159              :    */
     160              :   struct
     161              :   {
     162              :     /**
     163              :      * Number of tokens provided.
     164              :      */
     165              :     unsigned int num_tokens;
     166              :     /**
     167              :      * Array of tokens to redeem.
     168              :      */
     169              :     const struct TALER_MERCHANT_UseToken *tokens;
     170              :   } input_tokens;
     171              : 
     172              :   /**
     173              :    * Output tokens expected from the merchant.
     174              :    */
     175              :   struct
     176              :   {
     177              :     /**
     178              :      * Number of output tokens expected.
     179              :      */
     180              :     unsigned int num_output_tokens;
     181              :     /**
     182              :      * Array of expected output tokens.
     183              :      */
     184              :     const struct TALER_MERCHANT_OutputToken *output_tokens;
     185              :   } output_tokens;
     186              : 
     187              :   /**
     188              :    * JSON array of token envelope events (from Donau).
     189              :    */
     190              :   json_t *tokens_evs;
     191              : 
     192              :   /* Computed once both choice_index and tokens_evs are available: */
     193              : 
     194              :   /**
     195              :    * JSON object containing wallet-specific data payload.
     196              :    */
     197              :   json_t *wallet_data;
     198              : 
     199              :   /**
     200              :    * Hash code of @a wallet_data for integrity checks.
     201              :    */
     202              :   struct GNUNET_HashCode wallet_data_hash;
     203              : 
     204              :   /**
     205              :    * JSON body being constructed for the HTTP POST.
     206              :    */
     207              :   json_t *body;
     208              : 
     209              :   /* Final URL and CURL plumbing: */
     210              : 
     211              :   /**
     212              :    * Fully formed URL for the POST /order/$ID/pay request.
     213              :    */
     214              :   char *url;
     215              : 
     216              :   /**
     217              :    * CURL post context managing headers and body.
     218              :    */
     219              :   struct TALER_CURL_PostContext post_ctx;
     220              : 
     221              :   /**
     222              :    * Handle for the asynchronous CURL job.
     223              :    */
     224              :   struct GNUNET_CURL_Job *job;
     225              : 
     226              :   /**
     227              :    * Flags indicating which payment options have been set.
     228              :    */
     229              :   bool field_seen[TALER_MERCHANT_OrderPayOptionType_LENGTH];
     230              : 
     231              :   /**
     232              :    * True if operating in wallet mode (using tokens/contracts).
     233              :    */
     234              :   bool am_wallet;
     235              : 
     236              :   /**
     237              :    * Raw JSON data of `donau` for `wallet_data`.
     238              :    */
     239              :   json_t *donau_data;
     240              : };
     241              : 
     242              : /**
     243              :  * Parse blindly signed output tokens from response.
     244              :  *
     245              :  * @param token_sigs the JSON array with the token signatures. Can be NULL.
     246              :  * @param tokens where to store the parsed tokens.
     247              :  * @param num_tokens where to store the length of the @a tokens array.
     248              :  */
     249              : static enum GNUNET_GenericReturnValue
     250           23 : parse_tokens (const json_t *token_sigs,
     251              :               struct TALER_MERCHANT_OutputToken **tokens,
     252              :               unsigned int *num_tokens)
     253              : {
     254           23 :   GNUNET_array_grow (*tokens,
     255              :                      *num_tokens,
     256              :                      json_array_size (token_sigs));
     257              : 
     258           27 :   for (unsigned int i = 0; i<(*num_tokens); i++)
     259              :   {
     260            4 :     struct TALER_MERCHANT_OutputToken *token = &(*tokens)[i];
     261              :     struct GNUNET_JSON_Specification spec[] = {
     262            4 :       TALER_JSON_spec_blinded_token_issue_sig ("blind_sig",
     263              :                                                &token->blinded_sig),
     264            4 :       GNUNET_JSON_spec_end ()
     265              :     };
     266              :     const json_t *jtoken
     267            4 :       = json_array_get (token_sigs,
     268              :                         i);
     269              : 
     270            4 :     if (NULL == jtoken)
     271              :     {
     272            0 :       GNUNET_break (0);
     273            0 :       return GNUNET_SYSERR;
     274              :     }
     275            4 :     if (GNUNET_OK !=
     276            4 :         GNUNET_JSON_parse (jtoken,
     277              :                            spec,
     278              :                            NULL, NULL))
     279              :     {
     280            0 :       GNUNET_break (0);
     281            0 :       return GNUNET_SYSERR;
     282              :     }
     283              :   }
     284              : 
     285           23 :   return GNUNET_YES;
     286              : }
     287              : 
     288              : 
     289              : /**
     290              :  * Function called when we're done processing the
     291              :  * HTTP /pay request.
     292              :  *
     293              :  * @param cls the `struct TALER_MERCHANT_Pay`
     294              :  * @param response_code HTTP response code, 0 on error
     295              :  * @param resp response body, NULL if not in JSON
     296              :  */
     297              : static void
     298           35 : handle_finished (void *cls,
     299              :                  long response_code,
     300              :                  const void *resp)
     301              : {
     302           35 :   struct TALER_MERCHANT_OrderPayHandle *oph = cls;
     303           35 :   const json_t *json = resp;
     304           35 :   struct TALER_MERCHANT_PayResponse pr = {
     305           35 :     .hr.http_status = (unsigned int) response_code,
     306              :     .hr.reply = json
     307              :   };
     308              : 
     309           35 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     310              :               "Received /pay response with status code %u\n",
     311              :               (unsigned int) response_code);
     312              : 
     313           35 :   json_dumpf (json,
     314              :               stderr,
     315              :               JSON_INDENT (2));
     316              : 
     317           35 :   oph->job = NULL;
     318           35 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     319              :               "/pay completed with response code %u\n",
     320              :               (unsigned int) response_code);
     321           35 :   switch (response_code)
     322              :   {
     323            0 :   case 0:
     324            0 :     pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     325            0 :     break;
     326           23 :   case MHD_HTTP_OK:
     327           23 :     if (oph->am_wallet)
     328              :     {
     329           23 :       const json_t *token_sigs = NULL;
     330              :       struct GNUNET_JSON_Specification spec[] = {
     331           23 :         GNUNET_JSON_spec_fixed_auto ("sig",
     332              :                                      &pr.details.ok.merchant_sig),
     333           23 :         GNUNET_JSON_spec_mark_optional (
     334              :           GNUNET_JSON_spec_string ("pos_confirmation",
     335              :                                    &pr.details.ok.pos_confirmation),
     336              :           NULL),
     337           23 :         GNUNET_JSON_spec_mark_optional (
     338              :           GNUNET_JSON_spec_array_const ("token_sigs",
     339              :                                         &token_sigs),
     340              :           NULL),
     341           23 :         GNUNET_JSON_spec_end ()
     342              :       };
     343              : 
     344           23 :       if (GNUNET_OK !=
     345           23 :           GNUNET_JSON_parse (json,
     346              :                              spec,
     347              :                              NULL, NULL))
     348              :       {
     349            0 :         GNUNET_break_op (0);
     350            0 :         pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     351            0 :         pr.hr.http_status = 0;
     352            0 :         pr.hr.hint = "sig field missing in response";
     353            0 :         break;
     354              :       }
     355              : 
     356           23 :       if (GNUNET_OK !=
     357           23 :           parse_tokens (token_sigs,
     358              :                         &pr.details.ok.tokens,
     359              :                         &pr.details.ok.num_tokens))
     360              :       {
     361            0 :         GNUNET_break_op (0);
     362            0 :         pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     363            0 :         pr.hr.http_status = 0;
     364            0 :         pr.hr.hint = "failed to parse token_sigs field in response";
     365            0 :         break;
     366              :       }
     367              : 
     368           23 :       if (GNUNET_OK !=
     369           23 :           TALER_merchant_pay_verify (&oph->h_contract_terms,
     370           23 :                                      &oph->merchant_pub,
     371              :                                      &pr.details.ok.merchant_sig))
     372              :       {
     373            0 :         GNUNET_break_op (0);
     374            0 :         pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     375            0 :         pr.hr.http_status = 0;
     376            0 :         pr.hr.hint = "signature invalid";
     377              :       }
     378              :     }
     379           23 :     break;
     380              :   /* Tolerating Not Acceptable because sometimes
     381              :      * - especially in tests - we might want to POST
     382              :      * coins one at a time.  */
     383            0 :   case MHD_HTTP_NOT_ACCEPTABLE:
     384            0 :     pr.hr.ec = TALER_JSON_get_error_code (json);
     385            0 :     pr.hr.hint = TALER_JSON_get_error_hint (json);
     386            0 :     break;
     387            2 :   case MHD_HTTP_BAD_REQUEST:
     388            2 :     pr.hr.ec = TALER_JSON_get_error_code (json);
     389            2 :     pr.hr.hint = TALER_JSON_get_error_hint (json);
     390              :     /* This should never happen, either us
     391              :      * or the merchant is buggy (or API version conflict);
     392              :      * just pass JSON reply to the application */
     393            2 :     break;
     394            0 :   case MHD_HTTP_PAYMENT_REQUIRED:
     395              :     /* was originally paid, but then refunded */
     396            0 :     pr.hr.ec = TALER_JSON_get_error_code (json);
     397            0 :     pr.hr.hint = TALER_JSON_get_error_hint (json);
     398            0 :     break;
     399            0 :   case MHD_HTTP_FORBIDDEN:
     400            0 :     pr.hr.ec = TALER_JSON_get_error_code (json);
     401            0 :     pr.hr.hint = TALER_JSON_get_error_hint (json);
     402            0 :     break;
     403            0 :   case MHD_HTTP_NOT_FOUND:
     404            0 :     pr.hr.ec = TALER_JSON_get_error_code (json);
     405            0 :     pr.hr.hint = TALER_JSON_get_error_hint (json);
     406              :     /* Nothing really to verify, this should never
     407              :        happen, we should pass the JSON reply to the
     408              :        application */
     409            0 :     break;
     410            0 :   case MHD_HTTP_REQUEST_TIMEOUT:
     411            0 :     pr.hr.ec = TALER_JSON_get_error_code (json);
     412            0 :     pr.hr.hint = TALER_JSON_get_error_hint (json);
     413              :     /* The merchant couldn't generate a timely response, likely because
     414              :        it itself waited too long on the exchange.
     415              :        Pass on to application. */
     416            0 :     break;
     417           10 :   case MHD_HTTP_CONFLICT:
     418           10 :     TALER_MERCHANT_parse_error_details_ (json,
     419              :                                          MHD_HTTP_CONFLICT,
     420              :                                          &pr.hr);
     421           10 :     break;
     422            0 :   case MHD_HTTP_GONE:
     423            0 :     TALER_MERCHANT_parse_error_details_ (json,
     424              :                                          response_code,
     425              :                                          &pr.hr);
     426              :     /* The merchant says we are too late, the offer has expired or some
     427              :        denomination key of a coin involved has expired.
     428              :        Might be a disagreement in timestamps? Still, pass on to application. */
     429            0 :     break;
     430            0 :   case MHD_HTTP_PRECONDITION_FAILED:
     431            0 :     TALER_MERCHANT_parse_error_details_ (json,
     432              :                                          response_code,
     433              :                                          &pr.hr);
     434              :     /* Nothing really to verify, the merchant is blaming us for failing to
     435              :        satisfy some constraint (likely it does not like our exchange because
     436              :        of some disagreement on the PKI).  We should pass the JSON reply to the
     437              :        application */
     438            0 :     break;
     439            0 :   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
     440              :     {
     441            0 :       json_t *ebus = json_object_get (json,
     442              :                                       "exchange_base_urls");
     443            0 :       if (NULL == ebus)
     444              :       {
     445            0 :         GNUNET_break_op (0);
     446            0 :         pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     447            0 :         pr.hr.http_status = 0;
     448            0 :         pr.hr.hint = "failed to parse exchange_base_urls field in response";
     449            0 :         break;
     450              :       }
     451            0 :       {
     452            0 :         size_t alen = json_array_size (ebus);
     453            0 :         const char *ebua[GNUNET_NZL (alen)];
     454              :         size_t idx;
     455              :         json_t *jebu;
     456            0 :         bool ok = true;
     457              : 
     458            0 :         GNUNET_assert (alen <= UINT_MAX);
     459            0 :         json_array_foreach (ebus, idx, jebu)
     460              :         {
     461            0 :           ebua[idx] = json_string_value (jebu);
     462            0 :           if (NULL == ebua[idx])
     463              :           {
     464            0 :             GNUNET_break_op (0);
     465            0 :             pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     466            0 :             pr.hr.http_status = 0;
     467            0 :             pr.hr.hint = "non-string value in exchange_base_urls in response";
     468            0 :             ok = false;
     469            0 :             break;
     470              :           }
     471              :         }
     472            0 :         if (! ok)
     473            0 :           break;
     474              :         pr.details.unavailable_for_legal_reasons.num_exchanges
     475            0 :           = (unsigned int) alen;
     476              :         pr.details.unavailable_for_legal_reasons.exchanges
     477            0 :           = ebua;
     478            0 :         oph->cb (oph->cb_cls,
     479              :                  &pr);
     480            0 :         TALER_MERCHANT_order_pay_cancel1 (oph);
     481            0 :         return;
     482              :       }
     483              :     }
     484              :     break;
     485            0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     486            0 :     TALER_MERCHANT_parse_error_details_ (json,
     487              :                                          response_code,
     488              :                                          &pr.hr);
     489              :     /* Server had an internal issue; we should retry,
     490              :        but this API leaves this to the application */
     491            0 :     break;
     492            0 :   case MHD_HTTP_BAD_GATEWAY:
     493              :     /* Nothing really to verify, the merchant is blaming the exchange.
     494              :        We should pass the JSON reply to the application */
     495            0 :     TALER_MERCHANT_parse_error_details_ (json,
     496              :                                          response_code,
     497              :                                          &pr.hr);
     498            0 :     break;
     499            0 :   case MHD_HTTP_SERVICE_UNAVAILABLE:
     500            0 :     TALER_MERCHANT_parse_error_details_ (json,
     501              :                                          response_code,
     502              :                                          &pr.hr);
     503              :     /* Exchange couldn't respond properly; the retry is
     504              :        left to the application */
     505            0 :     break;
     506            0 :   case MHD_HTTP_GATEWAY_TIMEOUT:
     507            0 :     TALER_MERCHANT_parse_error_details_ (json,
     508              :                                          response_code,
     509              :                                          &pr.hr);
     510              :     /* Exchange couldn't respond in a timely fashion;
     511              :        the retry is left to the application */
     512            0 :     break;
     513            0 :   default:
     514            0 :     TALER_MERCHANT_parse_error_details_ (json,
     515              :                                          response_code,
     516              :                                          &pr.hr);
     517              :     /* unexpected response code */
     518            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     519              :                 "Unexpected response code %u/%d\n",
     520              :                 (unsigned int) response_code,
     521              :                 (int) pr.hr.ec);
     522            0 :     GNUNET_break_op (0);
     523            0 :     break;
     524              :   }
     525           35 :   oph->cb (oph->cb_cls,
     526              :            &pr);
     527              : 
     528           35 :   if (pr.details.ok.tokens)
     529              :   {
     530            4 :     GNUNET_free (pr.details.ok.tokens);
     531            4 :     pr.details.ok.tokens = NULL;
     532            4 :     pr.details.ok.num_tokens = 0;
     533              :   }
     534              : 
     535           35 :   TALER_MERCHANT_order_pay_cancel1 (oph);
     536              : }
     537              : 
     538              : 
     539              : /**
     540              :  * @brief Create and initialize a new payment handle
     541              :  *
     542              :  * Allocates a TALER_MERCHANT_OrderPayHandle, sets up its context and
     543              :  * prepares an empty JSON body for the /orders/$ID/pay request.
     544              :  */
     545              : struct TALER_MERCHANT_OrderPayHandle *
     546           35 : TALER_MERCHANT_order_pay_create (struct GNUNET_CURL_Context *ctx,
     547              :                                  TALER_MERCHANT_OrderPayCallback cb,
     548              :                                  TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE
     549              :                                  *cb_cls)
     550              : {
     551              :   struct TALER_MERCHANT_OrderPayHandle *ph =
     552           35 :     GNUNET_new (struct TALER_MERCHANT_OrderPayHandle);
     553           35 :   ph->ctx             = ctx;
     554           35 :   ph->cb              = cb;
     555           35 :   ph->cb_cls          = cb_cls;
     556           35 :   ph->body            = json_object ();
     557           35 :   GNUNET_assert (ph->body);
     558           35 :   return ph;
     559              : }
     560              : 
     561              : 
     562              : /**
     563              :  * @brief Cancel and free a payment handle
     564              :  *
     565              :  * Aborts any in-flight CURL job, releases all JSON objects and internal
     566              :  * buffers, and frees the handle structure itself.
     567              :  */
     568              : void
     569           35 : TALER_MERCHANT_order_pay_cancel1 (struct TALER_MERCHANT_OrderPayHandle *ph)
     570              : {
     571           35 :   if (ph->job)
     572            0 :     GNUNET_CURL_job_cancel (ph->job);
     573           35 :   ph->job = NULL;
     574              : 
     575           35 :   TALER_curl_easy_post_finished (&ph->post_ctx);
     576              : 
     577           35 :   if (ph->body)
     578           35 :     json_decref (ph->body);
     579           35 :   ph->body = NULL;
     580              : 
     581           35 :   if (ph->wallet_data)
     582            6 :     json_decref (ph->wallet_data);
     583           35 :   ph->wallet_data = NULL;
     584              : 
     585           35 :   if (ph->tokens_evs)
     586            6 :     json_decref (ph->tokens_evs);
     587           35 :   ph->tokens_evs = NULL;
     588              : 
     589           35 :   if (ph->donau_data)
     590            0 :     json_decref (ph->donau_data);
     591              : 
     592           35 :   GNUNET_free (ph->url);
     593           35 :   GNUNET_free (ph->merchant_url);
     594           35 :   GNUNET_free (ph->session_id);
     595           35 :   GNUNET_free (ph->order_id);
     596           35 :   GNUNET_free (ph);
     597           35 : }
     598              : 
     599              : 
     600              : /**
     601              :  * @brief Store a JSON snippet under a payment option key
     602              :  *
     603              :  * Ensures that an option of type @a ot has not already been set,
     604              :  * then merges @a snippet into the handle's JSON @c body. Marks the
     605              :  * field_seen flag and frees @a snippet.
     606              :  *
     607              :  * @param ph   payment handle receiving the snippet
     608              :  * @param ot   option type under which to store @a snippet
     609              :  * @param snippet JSON object representing the option payload
     610              :  * @return #TALER_MERCHANT_OPOEC_OK if stored; appropriate error code otherwise
     611              :  */
     612              : static enum TALER_MERCHANT_OrderPayErrorCode
     613           54 : store_json_option (struct TALER_MERCHANT_OrderPayHandle *ph,
     614              :                    enum TALER_MERCHANT_OrderPayOptionType ot,
     615              :                    json_t *snippet)
     616              : {
     617           54 :   if (ph->field_seen[ot])
     618              :   {
     619            0 :     json_decref (snippet);
     620            0 :     return TALER_MERCHANT_OPOEC_DUPLICATE_OPTION;
     621              :   }
     622           54 :   ph->field_seen[ot] = true;
     623           54 :   GNUNET_assert (0 == json_object_update (ph->body,
     624              :                                           snippet));
     625           54 :   json_decref (snippet);
     626           54 :   return TALER_MERCHANT_OPOEC_OK;
     627              : }
     628              : 
     629              : 
     630              : /**
     631              :  * @brief Apply user-supplied options to a payment handle
     632              :  *
     633              :  * Iterates through a NULL-terminated array of #TALER_MERCHANT_OrderPayOption
     634              :  * entries, validates and stores each into @a ph. Handles JSON packing and
     635              :  * internal state updates for coins, tokens, deadlines, Donau data, etc.
     636              :  */
     637              : enum TALER_MERCHANT_OrderPayErrorCode
     638           35 : TALER_MERCHANT_order_pay_set_options (
     639              :   struct TALER_MERCHANT_OrderPayHandle *ph,
     640              :   const struct TALER_MERCHANT_OrderPayOption options[],
     641              :   size_t max_options)
     642              : {
     643           35 :   for (size_t i = 0; i < max_options
     644          445 :        && options[i].ot != TALER_MERCHANT_OrderPayOptionType_END; i++)
     645              :   {
     646          410 :     const struct TALER_MERCHANT_OrderPayOption *o = &options[i];
     647              : 
     648          410 :     switch (o->ot)
     649              :     {
     650           35 :     case TALER_MERCHANT_OrderPayOptionType_MERCHANT_URL:
     651           35 :       ph->merchant_url = GNUNET_strdup (o->details.merchant_url);
     652           35 :       break;
     653              : 
     654            9 :     case TALER_MERCHANT_OrderPayOptionType_SESSION_ID:
     655            9 :       ph->session_id = GNUNET_strdup (o->details.session_id);
     656              :       /* add straight into JSON body */
     657              :       {
     658            9 :         json_t *js = GNUNET_JSON_PACK (GNUNET_JSON_pack_string ("session_id",
     659              :                                                                 o->details.
     660              :                                                                 session_id));
     661              :         enum TALER_MERCHANT_OrderPayErrorCode ec =
     662            9 :           store_json_option (ph,
     663            9 :                              o->ot,
     664              :                              js);
     665            9 :         if (TALER_MERCHANT_OPOEC_OK != ec)
     666            0 :           return ec;
     667            9 :         break;
     668              :       }
     669              : 
     670           35 :     case TALER_MERCHANT_OrderPayOptionType_ORDER_ID:
     671              :       {
     672           35 :         ph->order_id = GNUNET_strdup (o->details.order_id);
     673           35 :         break;
     674              :       }
     675              : 
     676           35 :     case TALER_MERCHANT_OrderPayOptionType_H_CONTRACT:
     677              :       {
     678           35 :         ph->h_contract_terms = *o->details.h_contract;
     679           35 :         ph->has_h_contract = true;
     680              : 
     681           35 :         break;
     682              :       }
     683              : 
     684            6 :     case TALER_MERCHANT_OrderPayOptionType_CHOICE_INDEX:
     685            6 :       ph->choice_index = o->details.choice_index;
     686            6 :       ph->has_choice_index = true;
     687            6 :       break;
     688              : 
     689           35 :     case TALER_MERCHANT_OrderPayOptionType_AMOUNT:
     690              :       {
     691           35 :         ph->amount = &o->details.amount;
     692           35 :         break;
     693              :       }
     694              : 
     695           35 :     case TALER_MERCHANT_OrderPayOptionType_MAX_FEE:
     696              :       {
     697           35 :         ph->max_fee = &o->details.max_fee;
     698           35 :         break;
     699              :       }
     700              : 
     701           35 :     case TALER_MERCHANT_OrderPayOptionType_MERCHANT_PUB:
     702              :       {
     703           35 :         ph->merchant_pub = o->details.merchant_pub;
     704           35 :         ph->has_merchant_pub = true;
     705              : 
     706           35 :         break;
     707              :       }
     708              : 
     709           35 :     case TALER_MERCHANT_OrderPayOptionType_TIMESTAMP:
     710              :       {
     711           35 :         ph->timestamp = o->details.timestamp;
     712           35 :         break;
     713              :       }
     714              : 
     715           35 :     case TALER_MERCHANT_OrderPayOptionType_REFUND_DEADLINE:
     716              :       {
     717           35 :         ph->refund_deadline = o->details.refund_deadline;
     718           35 :         break;
     719              :       }
     720              : 
     721           35 :     case TALER_MERCHANT_OrderPayOptionType_PAY_DEADLINE:
     722              :       {
     723              :         /* FIXME: This one comes from the merchant_api_post_order_pay
     724              :          no idea do we still need it or not? */
     725           35 :         break;
     726              :       }
     727              : 
     728           35 :     case TALER_MERCHANT_OrderPayOptionType_H_WIRE:
     729              :       {
     730           35 :         ph->h_wire = o->details.h_wire;
     731           35 :         ph->has_h_wire = true;
     732           35 :         break;
     733              :       }
     734              : 
     735           35 :     case TALER_MERCHANT_OrderPayOptionType_COINS:
     736              :       /* stash for later signing */
     737           35 :       ph->coins.num_coins = o->details.coins.num_coins;
     738           35 :       ph->coins.coins = o->details.coins.coins;
     739           35 :       break;
     740              : 
     741            4 :     case TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS:
     742              :       /* stash for later signing */
     743            4 :       ph->input_tokens.num_tokens = o->details.input_tokens.num_tokens;
     744            4 :       ph->input_tokens.tokens = o->details.input_tokens.tokens;
     745            4 :       break;
     746              : 
     747            6 :     case TALER_MERCHANT_OrderPayOptionType_OUTPUT_TOKENS:
     748              :       /* store JSON array directly, *and* stash for hash */
     749            6 :       ph->output_tokens.num_output_tokens =
     750            6 :         o->details.output_tokens.num_output_tokens;
     751            6 :       ph->output_tokens.output_tokens = o->details.output_tokens.output_tokens;
     752              :       {
     753              :         /* build and store tokens_evs */
     754            6 :         json_t *arr = json_array ();
     755              : 
     756            6 :         GNUNET_assert (NULL != arr);
     757           12 :         for (unsigned j = 0; j < ph->output_tokens.num_output_tokens; j++)
     758              :         {
     759            6 :           const struct TALER_MERCHANT_OutputToken *otk =
     760            6 :             &ph->output_tokens.output_tokens[j];
     761            6 :           json_t *je = GNUNET_JSON_PACK (TALER_JSON_pack_token_envelope (NULL,
     762              :                                                                          &otk->
     763              :                                                                          envelope));
     764            6 :           GNUNET_assert (0 ==
     765              :                          json_array_append_new (arr,
     766              :                                                 je));
     767              :         }
     768              : 
     769            6 :         ph->tokens_evs = arr;
     770              : 
     771            6 :         ph->field_seen[o->ot] = true;
     772              :       }
     773            6 :       break;
     774              : 
     775            0 :     case TALER_MERCHANT_OrderPayOptionType_DONAU_URL:
     776            0 :       if (NULL == ph->donau_data)
     777            0 :         ph->donau_data = json_object ();
     778            0 :       GNUNET_assert (0 ==
     779              :                      json_object_set_new (
     780              :                        ph->donau_data,
     781              :                        "url",
     782              :                        json_string (o->details.donau_url)));
     783            0 :       break;
     784              : 
     785            0 :     case TALER_MERCHANT_OrderPayOptionType_DONAU_YEAR:
     786              :       {
     787            0 :         if (ph->donau_data == NULL)
     788            0 :           ph->donau_data = json_object ();
     789            0 :         GNUNET_assert (0 == json_object_set_new (
     790              :                          ph->donau_data,
     791              :                          "year",
     792              :                          json_integer ((json_int_t) o->details.donau_year)));
     793            0 :         break;
     794              :       }
     795              : 
     796            0 :     case TALER_MERCHANT_OrderPayOptionType_DONAU_BUDIS:
     797              :       {
     798            0 :         if (ph->donau_data == NULL)
     799            0 :           ph->donau_data = json_object ();
     800            0 :         GNUNET_assert (0 == json_object_set_new (
     801              :                          ph->donau_data,
     802              :                          "budikeypairs",
     803              :                          json_incref (o->details.donau_budis_json)));
     804            0 :         break;
     805              :       }
     806              : 
     807            0 :     default:
     808            0 :       return TALER_MERCHANT_OPOEC_UNKNOWN_OPTION;
     809              :     }
     810              :   }
     811           35 :   return TALER_MERCHANT_OPOEC_OK;
     812              : }
     813              : 
     814              : 
     815              : /**
     816              :  * @brief Dispatch the /orders/$ID/pay request
     817              :  *
     818              :  * Validates that all mandatory parameters (merchant_url, order_id, coins)
     819              :  * have been set, builds the final JSON payload, constructs the URL,
     820              :  * and issues an asynchronous HTTP POST. The payment handle's callback
     821              :  * will receive completion notifications.
     822              :  */
     823              : enum TALER_MERCHANT_OrderPayErrorCode
     824           35 : TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph)
     825              : {
     826              :   /* all the old mandatory checks */
     827           35 :   if ( (! ph->merchant_url) ||
     828           35 :        (! ph->order_id) )
     829              :   {
     830            0 :     GNUNET_break (0);
     831            0 :     return TALER_MERCHANT_OPOEC_MISSING_MANDATORY;
     832              :   }
     833           35 :   if (GNUNET_YES !=
     834           35 :       TALER_amount_cmp_currency (ph->amount,
     835              :                                  ph->max_fee))
     836              :   {
     837            0 :     GNUNET_break (0);
     838            0 :     return TALER_MERCHANT_OPOEC_INVALID_VALUE;
     839              :   }
     840              : 
     841              :   /* build wallet_data hash for signing coins & tokens */
     842           35 :   if (ph->has_choice_index)
     843              :   {
     844              :     /* base fields */
     845            6 :     json_t *wd = GNUNET_JSON_PACK (
     846              :       GNUNET_JSON_pack_int64 ("choice_index",
     847              :                               ph->choice_index),
     848              :       GNUNET_JSON_pack_allow_null (
     849              :         GNUNET_JSON_pack_array_incref ("tokens_evs",
     850              :                                        ph->tokens_evs))
     851              :       );
     852              : 
     853              :     /* Putting prepared donau_data into the wallet_data */
     854            6 :     if (ph->donau_data)
     855            0 :       GNUNET_assert (0 == json_object_set_new (
     856              :                        wd,
     857              :                        "donau",
     858              :                        json_incref (ph->donau_data)));
     859              : 
     860            6 :     ph->wallet_data = wd;
     861              : 
     862            6 :     TALER_json_hash (ph->wallet_data,
     863              :                      &ph->wallet_data_hash);
     864              : 
     865            6 :     store_json_option (ph,
     866              :                        TALER_MERCHANT_OrderPayOptionType_WALLET_DATA,
     867            6 :                        GNUNET_JSON_PACK (
     868              :                          GNUNET_JSON_pack_object_incref ("wallet_data",
     869              :                                                          ph->wallet_data)));
     870              :   }
     871              : 
     872              :   /* sign coins AND build the “coins” JSON in one pass */
     873              :   {
     874              :     struct TALER_Amount total_fee;
     875              :     struct TALER_Amount total_amount;
     876           35 :     json_t *arr = json_array ();
     877              : 
     878           35 :     GNUNET_assert (NULL != arr);
     879           76 :     for (unsigned i = 0; i < ph->coins.num_coins; i++)
     880              :     {
     881           41 :       const struct TALER_MERCHANT_PayCoin *c = &ph->coins.coins[i];
     882              :       struct TALER_MERCHANT_PaidCoin pc;
     883              :       json_t *je;
     884              : 
     885              :       /* sign  */
     886              :       struct TALER_Amount fee;
     887              :       struct TALER_DenominationHashP h_denom_pub;
     888              : 
     889           41 :       TALER_denom_pub_hash (&c->denom_pub,
     890              :                             &h_denom_pub);
     891           41 :       if (0 > TALER_amount_subtract (&fee,
     892              :                                      &c->amount_with_fee,
     893              :                                      &c->amount_without_fee))
     894            0 :         return TALER_MERCHANT_OPOEC_INVALID_VALUE;
     895              : 
     896              : 
     897           41 :       TALER_wallet_deposit_sign (&c->amount_with_fee,
     898              :                                  &fee,
     899           41 :                                  &ph->h_wire,
     900           41 :                                  &ph->h_contract_terms,
     901           41 :                                  (NULL != ph->wallet_data)
     902              :                                 ? &ph->wallet_data_hash
     903              :                                 : NULL,
     904           41 :                                  c->h_age_commitment,
     905              :                                  NULL,
     906              :                                  &h_denom_pub,
     907              :                                  ph->timestamp,
     908           41 :                                  &ph->merchant_pub,
     909              :                                  ph->refund_deadline,
     910              :                                  &c->coin_priv,
     911              :                                  &pc.coin_sig);
     912              : 
     913           41 :       pc.denom_pub = c->denom_pub;
     914           41 :       pc.denom_sig = c->denom_sig;
     915           41 :       pc.denom_value = c->denom_value;
     916           41 :       pc.amount_with_fee = c->amount_with_fee;
     917           41 :       pc.amount_without_fee = c->amount_without_fee;
     918           41 :       pc.exchange_url = c->exchange_url;
     919           41 :       GNUNET_CRYPTO_eddsa_key_get_public (&c->coin_priv.eddsa_priv,
     920              :                                           &pc.coin_pub.eddsa_pub);
     921              : 
     922              :       /* JSON  */
     923           41 :       je = GNUNET_JSON_PACK (TALER_JSON_pack_amount ("contribution",
     924              :                                                      &pc.amount_with_fee),
     925              :                              GNUNET_JSON_pack_data_auto ("coin_pub",
     926              :                                                          &pc.coin_pub),
     927              :                              GNUNET_JSON_pack_string ("exchange_url",
     928              :                                                       pc.exchange_url),
     929              :                              GNUNET_JSON_pack_data_auto ("h_denom",
     930              :                                                          &h_denom_pub),
     931              :                              TALER_JSON_pack_denom_sig ("ub_sig",
     932              :                                                         &pc.denom_sig),
     933              :                              GNUNET_JSON_pack_data_auto ("coin_sig",
     934              :                                                          &pc.coin_sig));
     935           41 :       GNUNET_assert (0 ==
     936              :                      json_array_append_new (arr,
     937              :                                             je));
     938              : 
     939              :       /* optional totals if you need them later
     940              :        (kept here because they existed in the legacy code) */
     941           41 :       if (0 == i)
     942              :       {
     943           31 :         total_fee = fee;
     944           31 :         total_amount = pc.amount_with_fee;
     945              :       }
     946              :       else
     947              :       {
     948           10 :         if ( (0 >
     949           10 :               TALER_amount_add (&total_fee,
     950              :                                 &total_fee,
     951           10 :                                 &fee)) ||
     952              :              (0 >
     953           10 :               TALER_amount_add (&total_amount,
     954              :                                 &total_amount,
     955              :                                 &pc.amount_with_fee)) )
     956              :         {
     957            0 :           return TALER_MERCHANT_OPOEC_INVALID_VALUE;
     958              :         }
     959              :       }
     960              :     }
     961              : 
     962              :     /* Putting coins to the body*/
     963              :     {
     964              :       enum TALER_MERCHANT_OrderPayErrorCode ec =
     965           35 :         store_json_option (ph,
     966              :                            TALER_MERCHANT_OrderPayOptionType_COINS,
     967           35 :                            GNUNET_JSON_PACK (
     968              :                              GNUNET_JSON_pack_array_steal ("coins",
     969              :                                                            arr)
     970              :                              ));
     971           35 :       if (TALER_MERCHANT_OPOEC_OK != ec)
     972              :       {
     973            0 :         return ec;
     974              :       }
     975              :     }
     976              :   }
     977              : 
     978              :   /* sign & pack input_tokens into used_tokens array in body */
     979           35 :   if (ph->input_tokens.num_tokens > 0)
     980            4 :   {
     981            4 :     struct TALER_MERCHANT_UsedToken ut[ph->input_tokens.num_tokens];
     982            4 :     json_t *arr = json_array ();
     983              : 
     984            4 :     GNUNET_assert (NULL != arr);
     985            8 :     for (unsigned i = 0; i < ph->input_tokens.num_tokens; i++)
     986              :     {
     987              :       json_t *je;
     988            4 :       const struct TALER_MERCHANT_UseToken *in = &ph->input_tokens.tokens[i];
     989            4 :       struct TALER_MERCHANT_UsedToken *t = &ut[i];
     990              : 
     991            4 :       TALER_wallet_token_use_sign (&ph->h_contract_terms,
     992            4 :                                    &ph->wallet_data_hash,
     993              :                                    &in->token_priv,
     994              :                                    &t->token_sig);
     995              : 
     996            4 :       t->ub_sig = in->ub_sig;
     997            4 :       t->issue_pub = in->issue_pub;
     998              : 
     999            4 :       GNUNET_CRYPTO_eddsa_key_get_public (&in->token_priv.private_key,
    1000              :                                           &t->token_pub.public_key);
    1001              : 
    1002            4 :       je = GNUNET_JSON_PACK (
    1003              :         GNUNET_JSON_pack_data_auto ("token_sig",
    1004              :                                     &t->token_sig),
    1005              :         TALER_JSON_pack_token_issue_sig ("ub_sig",
    1006              :                                          &t->ub_sig),
    1007              :         GNUNET_JSON_pack_data_auto ("h_issue",
    1008              :                                     &t->issue_pub.public_key->pub_key_hash),
    1009              :         GNUNET_JSON_pack_data_auto ("token_pub",
    1010              :                                     &t->token_pub)
    1011              :         );
    1012            4 :       GNUNET_assert (0 ==
    1013              :                      json_array_append_new (arr,
    1014              :                                             je));
    1015              :     }
    1016              : 
    1017            4 :     store_json_option (ph,
    1018              :                        TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS,
    1019            4 :                        GNUNET_JSON_PACK (
    1020              :                          GNUNET_JSON_pack_array_steal ("tokens",
    1021              :                                                        arr)
    1022              :                          )
    1023              :                        );
    1024              :   }
    1025              : 
    1026              : 
    1027              :   /* post the request */
    1028              :   {
    1029              :     char *path;
    1030              :     CURL *eh;
    1031           35 :     GNUNET_asprintf (&path,
    1032              :                      "orders/%s/pay",
    1033              :                      ph->order_id);
    1034           35 :     ph->url = TALER_url_join (ph->merchant_url,
    1035              :                               path,
    1036              :                               NULL);
    1037           35 :     GNUNET_free (path);
    1038              : 
    1039           35 :     if (NULL == ph->url)
    1040              :     {
    1041            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1042              :                   "Could not construct request URL.\n");
    1043            0 :       json_decref (ph->body);
    1044            0 :       GNUNET_free (ph);
    1045            0 :       return TALER_MERCHANT_OPOEC_URL_FAILURE;
    1046              :     }
    1047              : 
    1048           35 :     eh = TALER_MERCHANT_curl_easy_get_ (ph->url);
    1049           35 :     if (GNUNET_OK !=
    1050           35 :         TALER_curl_easy_post (&ph->post_ctx,
    1051              :                               eh,
    1052           35 :                               ph->body))
    1053              :     {
    1054            0 :       GNUNET_break (0);
    1055            0 :       curl_easy_cleanup (eh);
    1056            0 :       GNUNET_free (ph->url);
    1057            0 :       GNUNET_free (ph);
    1058            0 :       return TALER_MERCHANT_OPOEC_CURL_FAILURE;
    1059              :     }
    1060              : 
    1061           70 :     ph->job = GNUNET_CURL_job_add2 (ph->ctx,
    1062              :                                     eh,
    1063           35 :                                     ph->post_ctx.headers,
    1064              :                                     &handle_finished,
    1065              :                                     ph);
    1066              : 
    1067           35 :     ph->am_wallet = true;
    1068           35 :     return TALER_MERCHANT_OPOEC_OK;
    1069              :   }
    1070              : }
        
               |