merchant

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

testing_api_cmd_post_using_templates.c (19033B)


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