merchant

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

testing_api_cmd_post_using_templates.c (20214B)


      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 
    285       order_cmd = TALER_TESTING_interpreter_lookup_command (
    286         tis->is,
    287         tis->duplicate_of);
    288       if (GNUNET_OK !=
    289           TALER_TESTING_get_trait_claim_token (order_cmd,
    290                                                &prev_token))
    291       {
    292         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    293                     "Could not fetch previous order claim token\n");
    294         TALER_TESTING_interpreter_fail (tis->is);
    295         return;
    296       }
    297       if (NULL == por->details.ok.token)
    298         prev_token = &zero_token;
    299       if (0 != GNUNET_memcmp (prev_token,
    300                               por->details.ok.token))
    301       {
    302         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    303                     "Claim tokens for identical requests do not match\n");
    304         TALER_TESTING_interpreter_fail (tis->is);
    305         return;
    306       }
    307     }
    308     break;
    309   case MHD_HTTP_NOT_FOUND:
    310     TALER_TESTING_interpreter_next (tis->is);
    311     return;
    312   case MHD_HTTP_GONE:
    313     TALER_TESTING_interpreter_next (tis->is);
    314     return;
    315   case MHD_HTTP_CONFLICT:
    316     TALER_TESTING_interpreter_next (tis->is);
    317     return;
    318   default:
    319     {
    320       char *s = json_dumps (por->hr.reply,
    321                             JSON_COMPACT);
    322       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    323                   "Unexpected status code from /orders: %u (%d) at %s; JSON: %s\n",
    324                   por->hr.http_status,
    325                   (int) por->hr.ec,
    326                   TALER_TESTING_interpreter_get_current_label (tis->is),
    327                   s);
    328       GNUNET_free (s);
    329       /**
    330        * Not failing, as test cases are _supposed_
    331        * to create non 200 OK situations.
    332        */
    333       TALER_TESTING_interpreter_next (tis->is);
    334     }
    335     return;
    336   }
    337 
    338   if (! tis->with_claim)
    339   {
    340     TALER_TESTING_interpreter_next (tis->is);
    341     return;
    342   }
    343   tis->och = TALER_MERCHANT_post_orders_claim_create (
    344     TALER_TESTING_interpreter_get_context (tis->is),
    345     tis->merchant_url,
    346     tis->order_id,
    347     &tis->nonce);
    348   if (NULL == tis->och)
    349     TALER_TESTING_FAIL (tis->is);
    350   TALER_MERCHANT_post_orders_claim_set_options (
    351     tis->och,
    352     TALER_MERCHANT_post_orders_claim_option_token (
    353       &tis->claim_token));
    354   {
    355     enum TALER_ErrorCode ec;
    356 
    357     ec = TALER_MERCHANT_post_orders_claim_start (tis->och,
    358                                                  &using_claim_cb,
    359                                                  tis);
    360     GNUNET_assert (TALER_EC_NONE == ec);
    361   }
    362 }
    363 
    364 
    365 /**
    366  * Run the "POST /using-templates" CMD.
    367  *
    368  *
    369  * @param cls closure.
    370  * @param cmd command being run now.
    371  * @param is interpreter state.
    372  */
    373 static void
    374 post_using_templates_run (void *cls,
    375                           const struct TALER_TESTING_Command *cmd,
    376                           struct TALER_TESTING_Interpreter *is)
    377 {
    378   struct PostUsingTemplatesState *tis = cls;
    379   const struct TALER_TESTING_Command *ref;
    380   const char *template_id;
    381 
    382   tis->is = is;
    383   ref = TALER_TESTING_interpreter_lookup_command (is,
    384                                                   tis->template_ref);
    385   if (GNUNET_OK !=
    386       TALER_TESTING_get_trait_template_id (ref,
    387                                            &template_id))
    388     TALER_TESTING_FAIL (is);
    389   if (NULL != tis->otp_ref)
    390   {
    391     ref = TALER_TESTING_interpreter_lookup_command (is,
    392                                                     tis->otp_ref);
    393     if (GNUNET_OK !=
    394         TALER_TESTING_get_trait_otp_key (ref,
    395                                          &tis->otp_key))
    396       TALER_TESTING_FAIL (is);
    397     if (GNUNET_OK !=
    398         TALER_TESTING_get_trait_otp_alg (ref,
    399                                          &tis->otp_alg))
    400       TALER_TESTING_FAIL (is);
    401   }
    402   GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
    403                               &tis->nonce,
    404                               sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
    405   tis->iph = TALER_MERCHANT_post_templates_create (
    406     TALER_TESTING_interpreter_get_context (is),
    407     tis->merchant_url,
    408     template_id);
    409   GNUNET_assert (NULL != tis->iph);
    410   if (NULL != tis->details)
    411   {
    412     TALER_MERCHANT_post_templates_set_options (
    413       tis->iph,
    414       TALER_MERCHANT_post_templates_option_details (
    415         tis->details));
    416   }
    417   else
    418   {
    419     if (NULL != tis->summary)
    420       TALER_MERCHANT_post_templates_set_options (
    421         tis->iph,
    422         TALER_MERCHANT_post_templates_option_summary (
    423           tis->summary));
    424     if (TALER_amount_is_valid (&tis->amount))
    425       TALER_MERCHANT_post_templates_set_options (
    426         tis->iph,
    427         TALER_MERCHANT_post_templates_option_amount (
    428           &tis->amount));
    429   }
    430   {
    431     enum TALER_ErrorCode ec;
    432 
    433     ec = TALER_MERCHANT_post_templates_start (tis->iph,
    434                                               &post_using_templates_cb,
    435                                               tis);
    436     GNUNET_assert (TALER_EC_NONE == ec);
    437   }
    438 }
    439 
    440 
    441 /**
    442  * Offers information from the POST /using-templates CMD state to other
    443  * commands.
    444  *
    445  * @param cls closure
    446  * @param[out] ret result (could be anything)
    447  * @param trait name of the trait
    448  * @param index index number of the object to extract.
    449  * @return #GNUNET_OK on success
    450  */
    451 static enum GNUNET_GenericReturnValue
    452 post_using_templates_traits (void *cls,
    453                              const void **ret,
    454                              const char *trait,
    455                              unsigned int index)
    456 {
    457   struct PostUsingTemplatesState *pts = cls;
    458   struct TALER_TESTING_Trait traits[] = {
    459     TALER_TESTING_make_trait_order_id (pts->order_id),
    460     TALER_TESTING_make_trait_contract_terms (pts->contract_terms),
    461     TALER_TESTING_make_trait_order_terms (pts->order_terms),
    462     TALER_TESTING_make_trait_h_contract_terms (&pts->h_contract_terms),
    463     TALER_TESTING_make_trait_merchant_sig (&pts->merchant_sig),
    464     TALER_TESTING_make_trait_merchant_pub (&pts->merchant_pub),
    465     TALER_TESTING_make_trait_claim_nonce (&pts->nonce),
    466     TALER_TESTING_make_trait_claim_token (&pts->claim_token),
    467     TALER_TESTING_make_trait_otp_key (pts->otp_key),
    468     TALER_TESTING_make_trait_otp_alg (pts->otp_alg),
    469     TALER_TESTING_trait_end (),
    470   };
    471 
    472   (void) pts;
    473   return TALER_TESTING_get_trait (traits,
    474                                   ret,
    475                                   trait,
    476                                   index);
    477 }
    478 
    479 
    480 /**
    481  * Free the state of a "POST using-template" CMD, and possibly
    482  * cancel a pending operation thereof.
    483  *
    484  * @param cls closure.
    485  * @param cmd command being run.
    486  */
    487 static void
    488 post_using_templates_cleanup (void *cls,
    489                               const struct TALER_TESTING_Command *cmd)
    490 {
    491   struct PostUsingTemplatesState *tis = cls;
    492 
    493   if (NULL != tis->iph)
    494   {
    495     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    496                 "POST /using-templates operation did not complete\n");
    497     TALER_MERCHANT_post_templates_cancel (tis->iph);
    498   }
    499   json_decref (tis->order_terms);
    500   json_decref (tis->contract_terms);
    501   json_decref (tis->details);
    502   GNUNET_free (tis->order_id);
    503   GNUNET_free (tis);
    504 }
    505 
    506 
    507 /**
    508  * Mark part of the contract terms as possible to forget.
    509  *
    510  * @param cls pointer to the result of the forget operation.
    511  * @param object_id name of the object to forget.
    512  * @param parent parent of the object at @e object_id.
    513  */
    514 static void
    515 mark_forgettable (void *cls,
    516                   const char *object_id,
    517                   json_t *parent)
    518 {
    519   GNUNET_assert (GNUNET_OK ==
    520                  TALER_JSON_contract_mark_forgettable (parent,
    521                                                        object_id));
    522 }
    523 
    524 
    525 /**
    526  * Constructs the json for a POST using template request.
    527  *
    528  * @param using_template_id the name of the using_template to add, can be NULL.
    529  * @param refund_deadline the deadline for refunds on this using template.
    530  * @param pay_deadline the deadline for payment on this using template.
    531  * @param amount the amount this using template is for.
    532  * @param[out] using_template where to write the json string.
    533  */
    534 static void
    535 make_order_json (const char *using_template_id,
    536                  struct GNUNET_TIME_Timestamp refund_deadline,
    537                  struct GNUNET_TIME_Timestamp pay_deadline,
    538                  const char *amount,
    539                  json_t **using_template)
    540 {
    541   struct GNUNET_TIME_Timestamp refund = refund_deadline;
    542   struct GNUNET_TIME_Timestamp pay = pay_deadline;
    543   json_t *contract_terms;
    544   struct TALER_Amount tamount;
    545   json_t *arr;
    546 
    547   if (NULL != amount)
    548     GNUNET_assert (GNUNET_OK ==
    549                    TALER_string_to_amount (amount,
    550                                            &tamount));
    551   /* Include required fields and some dummy objects to test forgetting. */
    552   arr = json_array ();
    553   GNUNET_assert (NULL != arr);
    554   GNUNET_assert (0 ==
    555                  json_array_append_new (
    556                    arr,
    557                    GNUNET_JSON_PACK (
    558                      GNUNET_JSON_pack_string (
    559                        "item", "speakers"))));
    560   GNUNET_assert (0 ==
    561                  json_array_append_new (
    562                    arr,
    563                    GNUNET_JSON_PACK (
    564                      GNUNET_JSON_pack_string (
    565                        "item", "headphones"))));
    566   GNUNET_assert (0 ==
    567                  json_array_append_new (
    568                    arr,
    569                    GNUNET_JSON_PACK (
    570                      GNUNET_JSON_pack_string (
    571                        "item", "earbuds"))));
    572   contract_terms = GNUNET_JSON_PACK (
    573     GNUNET_JSON_pack_string ("summary",
    574                              "merchant-lib testcase"),
    575     GNUNET_JSON_pack_allow_null (
    576       GNUNET_JSON_pack_string (
    577         "using_template_id", using_template_id)),
    578     NULL == amount
    579     ? GNUNET_JSON_pack_allow_null (
    580       GNUNET_JSON_pack_string ("amount",
    581                                NULL))
    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 */