LCOV - code coverage report
Current view: top level - lib - taler_merchant_pay_service.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 226 358 63.1 %
Date: 2025-08-28 06:06:54 Functions: 7 7 100.0 %

          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             : }

Generated by: LCOV version 1.16