merchant

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

testing_api_cmd_post_using_templates.c (20156B)


      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 (&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 */