merchant

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

testing_api_cmd_post_using_templates.c (20055B)


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