merchant

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

testing_api_cmd_post_using_templates.c (20173B)


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