merchant

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

testing_api_cmd_post_using_templates.c (17545B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022-2023 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU General Public License as
      7   published by the Free Software Foundation; either version 3, or
      8   (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not, see
     17   <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file testing_api_cmd_post_using_templates.c
     21  * @brief command to test POST /using-templates
     22  * @author Priscilla HUANG
     23  */
     24 #include "platform.h"
     25 #include <taler/taler_exchange_service.h>
     26 #include <taler/taler_testing_lib.h>
     27 #include "taler_merchant_service.h"
     28 #include "taler_merchant_testing_lib.h"
     29 
     30 
     31 /**
     32  * State of a "POST /templates" CMD.
     33  */
     34 struct PostUsingTemplatesState
     35 {
     36 
     37   /**
     38    * Handle for a "GET using-template" request.
     39    */
     40   struct TALER_MERCHANT_UsingTemplatesPostHandle *iph;
     41 
     42   /**
     43    * The interpreter state.
     44    */
     45   struct TALER_TESTING_Interpreter *is;
     46 
     47   /**
     48    * The (initial) POST /orders/$ID/claim operation handle.
     49    * The logic is such that after an order creation,
     50    * we immediately claim the order.
     51    */
     52   struct TALER_MERCHANT_OrderClaimHandle *och;
     53 
     54   /**
     55    * Base URL of the merchant serving the request.
     56    */
     57   const char *merchant_url;
     58 
     59   /**
     60    * ID of the using template to run.
     61    */
     62   const char *using_template_id;
     63 
     64   /**
     65    * Summary given by the customer.
     66    */
     67   const char *summary;
     68 
     69   /**
     70    * Amount given by the customer.
     71    */
     72   struct TALER_Amount amount;
     73 
     74   /**
     75    * Label of a command that created the template we should use.
     76    */
     77   const char *template_ref;
     78 
     79   /**
     80    * Order id.
     81    */
     82   char *order_id;
     83 
     84   /**
     85    * The order id we expect the merchant to assign (if not NULL).
     86    */
     87   const char *expected_order_id;
     88 
     89   /**
     90    * Contract terms obtained from the backend.
     91    */
     92   json_t *contract_terms;
     93 
     94   /**
     95    * Order submitted to the backend.
     96    */
     97   json_t *order_terms;
     98 
     99   /**
    100    * Contract terms hash code.
    101    */
    102   struct TALER_PrivateContractHashP h_contract_terms;
    103 
    104   /**
    105    * Merchant signature over the orders.
    106    */
    107   struct TALER_MerchantSignatureP merchant_sig;
    108 
    109   /**
    110    * Merchant public key.
    111    */
    112   struct TALER_MerchantPublicKeyP merchant_pub;
    113 
    114   /**
    115    * The nonce.
    116    */
    117   struct GNUNET_CRYPTO_EddsaPublicKey nonce;
    118 
    119   /**
    120    * The claim token
    121    */
    122   struct TALER_ClaimTokenP claim_token;
    123 
    124   /**
    125    * Should the command also CLAIM the order?
    126    */
    127   bool with_claim;
    128 
    129   /**
    130    * If not NULL, the command should duplicate the request and verify the
    131    * response is the same as in this command.
    132    */
    133   const char *duplicate_of;
    134 
    135   /**
    136    * Label of command creating/updating OTP device, or NULL.
    137    */
    138   const char *otp_ref;
    139 
    140   /**
    141    * Encoded key for the payment verification.
    142    */
    143   const char *otp_key;
    144 
    145   /**
    146    * Option that add amount of the order
    147    */
    148   const enum TALER_MerchantConfirmationAlgorithm *otp_alg;
    149 
    150   /**
    151    * Expected HTTP response code.
    152    */
    153   unsigned int http_status;
    154 
    155 };
    156 
    157 /**
    158  * Used to fill the "using_template" CMD state with backend-provided
    159  * values.  Also double-checks that the using_template was correctly
    160  * created.
    161  *
    162  * @param cls closure
    163  * @param ocr response we got
    164  */
    165 static void
    166 using_claim_cb (void *cls,
    167                 const struct TALER_MERCHANT_OrderClaimResponse *ocr)
    168 {
    169   struct PostUsingTemplatesState *tis = cls;
    170   const char *error_name;
    171   unsigned int error_line;
    172   struct GNUNET_JSON_Specification spec[] = {
    173     GNUNET_JSON_spec_fixed_auto ("merchant_pub",
    174                                  &tis->merchant_pub),
    175     GNUNET_JSON_spec_end ()
    176   };
    177 
    178   tis->och = NULL;
    179   if (tis->http_status != ocr->hr.http_status)
    180   {
    181     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    182                 "Expected status %u, got %u\n",
    183                 tis->http_status,
    184                 ocr->hr.http_status);
    185     TALER_TESTING_FAIL (tis->is);
    186   }
    187   if (MHD_HTTP_OK != ocr->hr.http_status)
    188   {
    189     TALER_TESTING_interpreter_next (tis->is);
    190     return;
    191   }
    192   tis->contract_terms = json_deep_copy (
    193     (json_t *) ocr->details.ok.contract_terms);
    194   tis->h_contract_terms = ocr->details.ok.h_contract_terms;
    195   tis->merchant_sig = ocr->details.ok.sig;
    196   if (GNUNET_OK !=
    197       GNUNET_JSON_parse (tis->contract_terms,
    198                          spec,
    199                          &error_name,
    200                          &error_line))
    201   {
    202     char *log;
    203 
    204     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    205                 "Parser failed on %s:%u\n",
    206                 error_name,
    207                 error_line);
    208     log = json_dumps (tis->contract_terms,
    209                       JSON_INDENT (1));
    210     fprintf (stderr,
    211              "%s\n",
    212              log);
    213     free (log);
    214     TALER_TESTING_FAIL (tis->is);
    215   }
    216   TALER_TESTING_interpreter_next (tis->is);
    217 }
    218 
    219 
    220 /**
    221  * Callback for a POST /using-templates operation.
    222  *
    223  * @param cls closure for this function
    224  * @param por response being processed
    225  */
    226 static void
    227 post_using_templates_cb (void *cls,
    228                          const struct TALER_MERCHANT_PostOrdersReply *por)
    229 {
    230   struct PostUsingTemplatesState *tis = cls;
    231 
    232   tis->iph = NULL;
    233   if (tis->http_status != por->hr.http_status)
    234   {
    235     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    236                 "Unexpected response code %u (%d) to command %s\n",
    237                 por->hr.http_status,
    238                 (int) por->hr.ec,
    239                 TALER_TESTING_interpreter_get_current_label (tis->is));
    240     TALER_TESTING_interpreter_fail (tis->is);
    241     return;
    242   }
    243   if (0 == tis->http_status)
    244   {
    245     TALER_LOG_DEBUG ("/using_templates, expected 0 status code\n");
    246     TALER_TESTING_interpreter_next (tis->is);
    247     return;
    248   }
    249   // check for order
    250   switch (por->hr.http_status)
    251   {
    252   case MHD_HTTP_OK:
    253     if (NULL != por->details.ok.token)
    254       tis->claim_token = *por->details.ok.token;
    255     tis->order_id = GNUNET_strdup (por->details.ok.order_id);
    256     if ((NULL != tis->expected_order_id) &&
    257         (0 != strcmp (por->details.ok.order_id,
    258                       tis->expected_order_id)))
    259     {
    260       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    261                   "Order id assigned does not match\n");
    262       TALER_TESTING_interpreter_fail (tis->is);
    263       return;
    264     }
    265     if (NULL != tis->duplicate_of)
    266     {
    267       const struct TALER_TESTING_Command *order_cmd;
    268       const struct TALER_ClaimTokenP *prev_token;
    269       struct TALER_ClaimTokenP zero_token = {0};
    270 
    271       order_cmd = TALER_TESTING_interpreter_lookup_command (
    272         tis->is,
    273         tis->duplicate_of);
    274       if (GNUNET_OK !=
    275           TALER_TESTING_get_trait_claim_token (order_cmd,
    276                                                &prev_token))
    277       {
    278         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    279                     "Could not fetch previous order claim token\n");
    280         TALER_TESTING_interpreter_fail (tis->is);
    281         return;
    282       }
    283       if (NULL == por->details.ok.token)
    284         prev_token = &zero_token;
    285       if (0 != GNUNET_memcmp (prev_token,
    286                               por->details.ok.token))
    287       {
    288         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    289                     "Claim tokens for identical requests do not match\n");
    290         TALER_TESTING_interpreter_fail (tis->is);
    291         return;
    292       }
    293     }
    294     break;
    295   case MHD_HTTP_NOT_FOUND:
    296     TALER_TESTING_interpreter_next (tis->is);
    297     return;
    298   case MHD_HTTP_GONE:
    299     TALER_TESTING_interpreter_next (tis->is);
    300     return;
    301   case MHD_HTTP_CONFLICT:
    302     TALER_TESTING_interpreter_next (tis->is);
    303     return;
    304   default:
    305     {
    306       char *s = json_dumps (por->hr.reply,
    307                             JSON_COMPACT);
    308       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    309                   "Unexpected status code from /orders: %u (%d) at %s; JSON: %s\n",
    310                   por->hr.http_status,
    311                   (int) por->hr.ec,
    312                   TALER_TESTING_interpreter_get_current_label (tis->is),
    313                   s);
    314       GNUNET_free (s);
    315       /**
    316        * Not failing, as test cases are _supposed_
    317        * to create non 200 OK situations.
    318        */
    319       TALER_TESTING_interpreter_next (tis->is);
    320     }
    321     return;
    322   }
    323 
    324   if (! tis->with_claim)
    325   {
    326     TALER_TESTING_interpreter_next (tis->is);
    327     return;
    328   }
    329   if (NULL ==
    330       (tis->och = TALER_MERCHANT_order_claim (
    331          TALER_TESTING_interpreter_get_context (tis->is),
    332          tis->merchant_url,
    333          tis->order_id,
    334          &tis->nonce,
    335          &tis->claim_token,
    336          &using_claim_cb,
    337          tis)))
    338     TALER_TESTING_FAIL (tis->is);
    339 }
    340 
    341 
    342 /**
    343  * Run the "POST /using-templates" CMD.
    344  *
    345  *
    346  * @param cls closure.
    347  * @param cmd command being run now.
    348  * @param is interpreter state.
    349  */
    350 static void
    351 post_using_templates_run (void *cls,
    352                           const struct TALER_TESTING_Command *cmd,
    353                           struct TALER_TESTING_Interpreter *is)
    354 {
    355   struct PostUsingTemplatesState *tis = cls;
    356   const struct TALER_TESTING_Command *ref;
    357   const char *template_id;
    358 
    359   tis->is = is;
    360   ref = TALER_TESTING_interpreter_lookup_command (is,
    361                                                   tis->template_ref);
    362   if (GNUNET_OK !=
    363       TALER_TESTING_get_trait_template_id (ref,
    364                                            &template_id))
    365     TALER_TESTING_FAIL (is);
    366   if (NULL != tis->otp_ref)
    367   {
    368     ref = TALER_TESTING_interpreter_lookup_command (is,
    369                                                     tis->otp_ref);
    370     if (GNUNET_OK !=
    371         TALER_TESTING_get_trait_otp_key (ref,
    372                                          &tis->otp_key))
    373       TALER_TESTING_FAIL (is);
    374     if (GNUNET_OK !=
    375         TALER_TESTING_get_trait_otp_alg (ref,
    376                                          &tis->otp_alg))
    377       TALER_TESTING_FAIL (is);
    378   }
    379   tis->iph = TALER_MERCHANT_using_templates_post (
    380     TALER_TESTING_interpreter_get_context (is),
    381     tis->merchant_url,
    382     template_id,
    383     tis->summary,
    384     TALER_amount_is_valid (&tis->amount)
    385     ? &tis->amount
    386     : NULL,
    387     &post_using_templates_cb,
    388     tis);
    389   GNUNET_assert (NULL != tis->iph);
    390 }
    391 
    392 
    393 /**
    394  * Offers information from the POST /using-templates CMD state to other
    395  * commands.
    396  *
    397  * @param cls closure
    398  * @param[out] ret result (could be anything)
    399  * @param trait name of the trait
    400  * @param index index number of the object to extract.
    401  * @return #GNUNET_OK on success
    402  */
    403 static enum GNUNET_GenericReturnValue
    404 post_using_templates_traits (void *cls,
    405                              const void **ret,
    406                              const char *trait,
    407                              unsigned int index)
    408 {
    409   struct PostUsingTemplatesState *pts = cls;
    410   struct TALER_TESTING_Trait traits[] = {
    411     TALER_TESTING_make_trait_order_id (pts->order_id),
    412     TALER_TESTING_make_trait_contract_terms (pts->contract_terms),
    413     TALER_TESTING_make_trait_order_terms (pts->order_terms),
    414     TALER_TESTING_make_trait_h_contract_terms (&pts->h_contract_terms),
    415     TALER_TESTING_make_trait_merchant_sig (&pts->merchant_sig),
    416     TALER_TESTING_make_trait_merchant_pub (&pts->merchant_pub),
    417     TALER_TESTING_make_trait_claim_nonce (&pts->nonce),
    418     TALER_TESTING_make_trait_claim_token (&pts->claim_token),
    419     TALER_TESTING_make_trait_otp_key (pts->otp_key),
    420     TALER_TESTING_make_trait_otp_alg (pts->otp_alg),
    421     TALER_TESTING_trait_end (),
    422   };
    423 
    424   (void) pts;
    425   return TALER_TESTING_get_trait (traits,
    426                                   ret,
    427                                   trait,
    428                                   index);
    429 }
    430 
    431 
    432 /**
    433  * Free the state of a "POST using-template" CMD, and possibly
    434  * cancel a pending operation thereof.
    435  *
    436  * @param cls closure.
    437  * @param cmd command being run.
    438  */
    439 static void
    440 post_using_templates_cleanup (void *cls,
    441                               const struct TALER_TESTING_Command *cmd)
    442 {
    443   struct PostUsingTemplatesState *tis = cls;
    444 
    445   if (NULL != tis->iph)
    446   {
    447     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    448                 "POST /using-templates operation did not complete\n");
    449     TALER_MERCHANT_using_templates_post_cancel (tis->iph);
    450   }
    451   json_decref (tis->order_terms);
    452   json_decref (tis->contract_terms);
    453   GNUNET_free (tis->order_id);
    454   GNUNET_free (tis);
    455 }
    456 
    457 
    458 /**
    459  * Mark part of the contract terms as possible to forget.
    460  *
    461  * @param cls pointer to the result of the forget operation.
    462  * @param object_id name of the object to forget.
    463  * @param parent parent of the object at @e object_id.
    464  */
    465 static void
    466 mark_forgettable (void *cls,
    467                   const char *object_id,
    468                   json_t *parent)
    469 {
    470   GNUNET_assert (GNUNET_OK ==
    471                  TALER_JSON_contract_mark_forgettable (parent,
    472                                                        object_id));
    473 }
    474 
    475 
    476 /**
    477  * Constructs the json for a POST using template request.
    478  *
    479  * @param using_template_id the name of the using_template to add, can be NULL.
    480  * @param refund_deadline the deadline for refunds on this using template.
    481  * @param pay_deadline the deadline for payment on this using template.
    482  * @param amount the amount this using template is for.
    483  * @param[out] using_template where to write the json string.
    484  */
    485 static void
    486 make_order_json (const char *using_template_id,
    487                  struct GNUNET_TIME_Timestamp refund_deadline,
    488                  struct GNUNET_TIME_Timestamp pay_deadline,
    489                  const char *amount,
    490                  json_t **using_template)
    491 {
    492   struct GNUNET_TIME_Timestamp refund = refund_deadline;
    493   struct GNUNET_TIME_Timestamp pay = pay_deadline;
    494   json_t *contract_terms;
    495   struct TALER_Amount tamount;
    496   json_t *arr;
    497 
    498   if (NULL != amount)
    499     GNUNET_assert (GNUNET_OK ==
    500                    TALER_string_to_amount (amount,
    501                                            &tamount));
    502   /* Include required fields and some dummy objects to test forgetting. */
    503   arr = json_array ();
    504   GNUNET_assert (NULL != arr);
    505   GNUNET_assert (0 ==
    506                  json_array_append_new (
    507                    arr,
    508                    GNUNET_JSON_PACK (
    509                      GNUNET_JSON_pack_string (
    510                        "item", "speakers"))));
    511   GNUNET_assert (0 ==
    512                  json_array_append_new (
    513                    arr,
    514                    GNUNET_JSON_PACK (
    515                      GNUNET_JSON_pack_string (
    516                        "item", "headphones"))));
    517   GNUNET_assert (0 ==
    518                  json_array_append_new (
    519                    arr,
    520                    GNUNET_JSON_PACK (
    521                      GNUNET_JSON_pack_string (
    522                        "item", "earbuds"))));
    523   contract_terms = GNUNET_JSON_PACK (
    524     GNUNET_JSON_pack_string ("summary",
    525                              "merchant-lib testcase"),
    526     GNUNET_JSON_pack_allow_null (
    527       GNUNET_JSON_pack_string (
    528         "using_template_id", using_template_id)),
    529     NULL == amount
    530     ? GNUNET_JSON_pack_allow_null (
    531       GNUNET_JSON_pack_string ("amount",
    532                                NULL))
    533     : TALER_JSON_pack_amount ("amount",
    534                               &tamount),
    535     GNUNET_JSON_pack_string ("fulfillment_url",
    536                              "https://example.com"),
    537     GNUNET_JSON_pack_allow_null (
    538       GNUNET_JSON_pack_timestamp ("refund_deadline",
    539                                   refund)),
    540     GNUNET_JSON_pack_allow_null (
    541       GNUNET_JSON_pack_timestamp ("pay_deadline",
    542                                   pay)),
    543     GNUNET_JSON_pack_string ("dummy_obj",
    544                              "EUR:1.0"),
    545     GNUNET_JSON_pack_array_steal ("dummy_array",
    546                                   arr));
    547   GNUNET_assert (GNUNET_OK ==
    548                  TALER_JSON_expand_path (contract_terms,
    549                                          "$.dummy_obj",
    550                                          &mark_forgettable,
    551                                          NULL));
    552   GNUNET_assert (GNUNET_OK ==
    553                  TALER_JSON_expand_path (contract_terms,
    554                                          "$.dummy_array[*].item",
    555                                          &mark_forgettable,
    556                                          NULL));
    557   *using_template = contract_terms;
    558 }
    559 
    560 
    561 struct TALER_TESTING_Command
    562 TALER_TESTING_cmd_merchant_post_using_templates (
    563   const char *label,
    564   const char *template_ref,
    565   const char *otp_ref,
    566   const char *merchant_url,
    567   const char *using_template_id,
    568   const char *summary,
    569   const char *amount,
    570   struct GNUNET_TIME_Timestamp refund_deadline,
    571   struct GNUNET_TIME_Timestamp pay_deadline,
    572   unsigned int http_status)
    573 {
    574   struct PostUsingTemplatesState *tis;
    575 
    576   tis = GNUNET_new (struct PostUsingTemplatesState);
    577   tis->template_ref = template_ref;
    578   tis->otp_ref = otp_ref;
    579   tis->merchant_url = merchant_url;
    580   tis->using_template_id = using_template_id;
    581   tis->http_status = http_status;
    582   tis->summary = summary;
    583   tis->with_claim = true;
    584   make_order_json (using_template_id,
    585                    refund_deadline,
    586                    pay_deadline,
    587                    amount,
    588                    &tis->order_terms);
    589   if (NULL != amount)
    590     GNUNET_assert (GNUNET_OK ==
    591                    TALER_string_to_amount (amount,
    592                                            &tis->amount));
    593   {
    594     struct TALER_TESTING_Command cmd = {
    595       .cls = tis,
    596       .label = label,
    597       .run = &post_using_templates_run,
    598       .cleanup = &post_using_templates_cleanup,
    599       .traits = &post_using_templates_traits
    600     };
    601 
    602     return cmd;
    603   }
    604 }
    605 
    606 
    607 /* end of testing_api_cmd_post_using_templates.c */