merchant

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

testing_api_cmd_merchant_get_order.c (34148B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2020-2024 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_merchant_get_order.c
     21  * @brief command to test GET /private/orders/$ORDER_ID.
     22  * @author Jonathan Buchanan
     23  */
     24 #include "platform.h"
     25 #include <taler/taler_exchange_service.h>
     26 #include <taler/taler_testing_lib.h>
     27 #include "taler/taler_merchant_testing_lib.h"
     28 #include <taler/merchant/get-private-orders-ORDER_ID.h>
     29 
     30 
     31 /**
     32  * State for a GET /private/orders/$ORDER_ID CMD.
     33  */
     34 struct MerchantGetOrderState
     35 {
     36   /**
     37    * The merchant base URL.
     38    */
     39   const char *merchant_url;
     40 
     41   /**
     42    * Expected HTTP response code for this CMD.
     43    */
     44   unsigned int http_status;
     45 
     46   /**
     47    * The handle to the current GET /private/orders/$ORDER_ID request.
     48    */
     49   struct TALER_MERCHANT_GetPrivateOrderHandle *ogh;
     50 
     51   /**
     52    * The interpreter state.
     53    */
     54   struct TALER_TESTING_Interpreter *is;
     55 
     56   /**
     57    * Reference to a command that created an order.
     58    */
     59   const char *order_reference;
     60 
     61   /**
     62    * Expected order status.
     63    */
     64   enum TALER_MERCHANT_OrderStatusCode osc;
     65 
     66   /**
     67    * A NULL-terminated list of refunds associated with this order.
     68    */
     69   const char **refunds;
     70 
     71   /**
     72    * The length of @e refunds.
     73    */
     74   unsigned int refunds_length;
     75 
     76   /**
     77    * A NULL-terminated list of transfers associated with this order.
     78    */
     79   const char **transfers;
     80 
     81   /**
     82    * The length of @e transfers.
     83    */
     84   unsigned int transfers_length;
     85 
     86   /**
     87    * A list of forget commands that apply to this order's contract terms.
     88    */
     89   const char **forgets;
     90 
     91   /**
     92    * The length of @e forgets.
     93    */
     94   unsigned int forgets_length;
     95 
     96   /**
     97    * Set to a session ID, if we should pass one as part
     98    * of the request.
     99    */
    100   const char *session_id;
    101 
    102   /**
    103    * Set if we expect to be referred to another equivalent order which was
    104    * already paid by the wallet under this @e session_id.
    105    */
    106   const char *repurchase_order_ref;
    107 
    108   /**
    109    * Expected minimum age.
    110    */
    111   unsigned int expected_min_age;
    112 
    113   /**
    114    * True if we should pass the 'allow_refunded_for_repurchase' flag.
    115    */
    116   bool allow_refunded_for_repurchase;
    117 
    118   /**
    119    * Whether the order was refunded or not.
    120    */
    121   bool refunded;
    122 
    123   /**
    124    * Whether the order was wired or not.
    125    */
    126   bool wired;
    127 };
    128 
    129 
    130 /**
    131  * Forget part of the contract terms.
    132  *
    133  * @param cls pointer to the result of the forget operation.
    134  * @param object_id name of the object to forget.
    135  * @param parent parent of the object at @e object_id.
    136  */
    137 static void
    138 apply_forget (void *cls,
    139               const char *object_id,
    140               json_t *parent)
    141 {
    142   int *res = cls;
    143 
    144   if (GNUNET_SYSERR ==
    145       TALER_JSON_contract_part_forget (parent,
    146                                        object_id))
    147     *res = GNUNET_SYSERR;
    148 }
    149 
    150 
    151 /**
    152  * Callback to process a GET /orders/$ID request
    153  *
    154  * @param cls closure
    155  * @param osr order status response details
    156  */
    157 /* FIXME_POLYMORPHISM: TALER_MERCHANT_GetPrivateOrderCallback is used in this
    158    compilation unit with two different closure types (struct MerchantGetOrderState here,
    159    struct MerchantPollOrderStartState in merchant_poll_order_cb below), so a single
    160    TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE override cannot type both. Left as
    161    void *cls; adopting the RESULT_CLOSURE pattern needs a manual refactor. */
    162 static void
    163 merchant_get_order_cb (
    164   void *cls,
    165   const struct TALER_MERCHANT_GetPrivateOrderResponse *osr)
    166 {
    167   struct MerchantGetOrderState *gos = cls;
    168 
    169   gos->ogh = NULL;
    170   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    171               "GET /private/orders/$ID completed with status %u\n",
    172               osr->hr.http_status);
    173   if (gos->http_status != osr->hr.http_status)
    174   {
    175     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    176                 "Unexpected response code %u (%d) to command %s\n",
    177                 osr->hr.http_status,
    178                 (int) osr->hr.ec,
    179                 TALER_TESTING_interpreter_get_current_label (gos->is));
    180     TALER_TESTING_interpreter_fail (gos->is);
    181     return;
    182   }
    183   switch (osr->hr.http_status)
    184   {
    185   case MHD_HTTP_OK:
    186     if (gos->osc != osr->details.ok.status)
    187     {
    188       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    189                   "Order paid does not match: %d vs %d\n",
    190                   gos->osc,
    191                   osr->details.ok.status);
    192       TALER_TESTING_interpreter_fail (gos->is);
    193       return;
    194     }
    195     switch (osr->details.ok.status)
    196     {
    197     case TALER_MERCHANT_OSC_PAID:
    198       {
    199         const struct TALER_TESTING_Command *order_cmd;
    200         struct TALER_Amount refunded_total;
    201 
    202         if ( (0 != gos->expected_min_age) &&
    203              (gos->expected_min_age !=
    204               json_integer_value (
    205                 json_object_get (
    206                   osr->details.ok.details.paid.contract_terms,
    207                   "minimum_age"))) )
    208         {
    209           GNUNET_break (0);
    210           TALER_TESTING_interpreter_fail (gos->is);
    211           return;
    212         }
    213         order_cmd = TALER_TESTING_interpreter_lookup_command (
    214           gos->is,
    215           gos->order_reference);
    216 
    217         {
    218           const json_t *expected_contract_terms;
    219           json_t *ct;
    220 
    221           if (GNUNET_OK !=
    222               TALER_TESTING_get_trait_contract_terms (order_cmd,
    223                                                       &expected_contract_terms))
    224           {
    225             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    226                         "Could not fetch order contract terms\n");
    227             TALER_TESTING_interpreter_fail (gos->is);
    228             return;
    229           }
    230 
    231           ct = json_deep_copy (expected_contract_terms);
    232 
    233           /* Apply all forgets, then compare */
    234           for (unsigned int i = 0; i < gos->forgets_length; ++i)
    235           {
    236             const struct TALER_TESTING_Command *forget_cmd;
    237             const uint32_t *paths_length;
    238 
    239             forget_cmd = TALER_TESTING_interpreter_lookup_command (
    240               gos->is,
    241               gos->forgets[i]);
    242 
    243             if (GNUNET_OK !=
    244                 TALER_TESTING_get_trait_paths_length (forget_cmd,
    245                                                       &paths_length))
    246             {
    247               GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    248                           "Couldn't fetch forget paths length\n");
    249               TALER_TESTING_interpreter_fail (gos->is);
    250               return;
    251             }
    252 
    253             for (unsigned int j = 0; j < *paths_length; ++j)
    254             {
    255               const char *path;
    256               int res = GNUNET_OK;
    257 
    258               if (GNUNET_OK !=
    259                   TALER_TESTING_get_trait_paths (forget_cmd,
    260                                                  j,
    261                                                  &path))
    262               {
    263                 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    264                             "Couldn't fetch forget path\n");
    265                 TALER_TESTING_interpreter_fail (gos->is);
    266                 return;
    267               }
    268 
    269               GNUNET_assert (GNUNET_OK ==
    270                              TALER_JSON_expand_path (ct,
    271                                                      path,
    272                                                      &apply_forget,
    273                                                      &res));
    274               GNUNET_assert (GNUNET_OK == res);
    275             }
    276           }
    277 
    278           if (1 != json_equal (ct,
    279                                osr->details.ok.details.paid.contract_terms))
    280           {
    281             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    282                         "Order contract terms do not match\n");
    283             TALER_TESTING_interpreter_fail (gos->is);
    284             return;
    285           }
    286 
    287           json_decref (ct);
    288         }
    289         if (gos->wired != osr->details.ok.details.paid.wired)
    290         {
    291           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    292                       "Order wired does not match\n");
    293           TALER_TESTING_interpreter_fail (gos->is);
    294           return;
    295         }
    296         if (gos->transfers_length != osr->details.ok.details.paid.wts_len)
    297         {
    298           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    299                       "Number of transfers found does not match\n");
    300           TALER_TESTING_interpreter_fail (gos->is);
    301           return;
    302         }
    303         for (unsigned int i = 0; i < gos->transfers_length; ++i)
    304         {
    305           const struct TALER_TESTING_Command *transfer_cmd;
    306 
    307           transfer_cmd = TALER_TESTING_interpreter_lookup_command (
    308             gos->is,
    309             gos->transfers[i]);
    310           {
    311             const struct TALER_WireTransferIdentifierRawP *wtid;
    312 
    313             if (GNUNET_OK !=
    314                 TALER_TESTING_get_trait_wtid (transfer_cmd,
    315                                               &wtid))
    316             {
    317               GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    318                           "Could not fetch wire transfer id\n");
    319               TALER_TESTING_interpreter_fail (gos->is);
    320               return;
    321             }
    322             if (0 != GNUNET_memcmp (wtid,
    323                                     &osr->details.ok.details.paid.wts[i].
    324                                     wtid))
    325             {
    326               GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    327                           "Wire transfer id does not match\n");
    328               TALER_TESTING_interpreter_fail (gos->is);
    329               return;
    330             }
    331           }
    332           {
    333             const char *exchange_url;
    334 
    335             if (GNUNET_OK !=
    336                 TALER_TESTING_get_trait_exchange_url (transfer_cmd,
    337                                                       &exchange_url))
    338             {
    339               GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    340                           "Could not fetch wire transfer exchange url\n");
    341               TALER_TESTING_interpreter_fail (gos->is);
    342               return;
    343             }
    344             if (0 != strcmp (
    345                   exchange_url,
    346                   osr->details.ok.details.paid.wts[i].exchange_url))
    347             {
    348               GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    349                           "Wire transfer exchange url does not match\n");
    350               TALER_TESTING_interpreter_fail (gos->is);
    351               return;
    352             }
    353           }
    354         }
    355         if (gos->refunded != osr->details.ok.details.paid.refunded)
    356         {
    357           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    358                       "Order refunded does not match\n");
    359           TALER_TESTING_interpreter_fail (gos->is);
    360           return;
    361         }
    362         if (gos->refunds_length !=
    363             osr->details.ok.details.paid.refunds_len)
    364         {
    365           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    366                       "Number of refunds found does not match\n");
    367           TALER_TESTING_interpreter_fail (gos->is);
    368           return;
    369         }
    370         if (0 < gos->refunds_length)
    371           GNUNET_assert (
    372             GNUNET_OK ==
    373             TALER_amount_set_zero (
    374               osr->details.ok.details.paid.refund_amount.currency,
    375               &refunded_total));
    376         for (unsigned int i = 0; i < gos->refunds_length; ++i)
    377         {
    378           const struct TALER_TESTING_Command *refund_cmd;
    379 
    380           refund_cmd = TALER_TESTING_interpreter_lookup_command (
    381             gos->is,
    382             gos->refunds[i]);
    383           {
    384             const struct TALER_Amount *expected_amount;
    385             struct TALER_Amount *amount_found =
    386               &osr->details.ok.details.paid.refunds[i].refund_amount;
    387 
    388             if (GNUNET_OK !=
    389                 TALER_TESTING_get_trait_amount (refund_cmd,
    390                                                 &expected_amount))
    391             {
    392               GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    393                           "Could not fetch refund amount\n");
    394               TALER_TESTING_interpreter_fail (gos->is);
    395               return;
    396             }
    397             GNUNET_assert (0 <= TALER_amount_add (&refunded_total,
    398                                                   &refunded_total,
    399                                                   amount_found));
    400             if ((GNUNET_OK !=
    401                  TALER_amount_cmp_currency (expected_amount,
    402                                             &refunded_total)) ||
    403                 (0 != TALER_amount_cmp (expected_amount,
    404                                         &refunded_total)))
    405             {
    406               GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    407                           "Refund amounts do not match\n");
    408               TALER_TESTING_interpreter_fail (gos->is);
    409               return;
    410             }
    411           }
    412           {
    413             const char *expected_reason;
    414 
    415             if (GNUNET_OK !=
    416                 TALER_TESTING_get_trait_reason (refund_cmd,
    417                                                 &expected_reason))
    418             {
    419               GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    420                           "Could not fetch reason\n");
    421               TALER_TESTING_interpreter_fail (gos->is);
    422               return;
    423             }
    424             if (0 !=
    425                 strcmp (
    426                   expected_reason,
    427                   osr->details.ok.details.paid.refunds[i].reason))
    428             {
    429               GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    430                           "Refund reason does not match\n");
    431               TALER_TESTING_interpreter_fail (gos->is);
    432               return;
    433             }
    434           }
    435         }
    436 
    437         if (gos->wired != osr->details.ok.details.paid.wired)
    438         {
    439           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    440                       "Order wired does not match\n");
    441           TALER_TESTING_interpreter_fail (gos->is);
    442           return;
    443         }
    444       }
    445       break;
    446     case TALER_MERCHANT_OSC_CLAIMED:
    447       /* FIXME: Check contract terms... */
    448       if ( (0 != gos->expected_min_age) &&
    449            (gos->expected_min_age !=
    450             json_integer_value (
    451               json_object_get (
    452                 osr->details.ok.details.claimed.contract_terms,
    453                 "minimum_age"))) )
    454       {
    455         GNUNET_break (0);
    456         TALER_TESTING_interpreter_fail (gos->is);
    457         return;
    458       }
    459       break;
    460     case TALER_MERCHANT_OSC_UNPAID:
    461       {
    462         struct TALER_MERCHANT_PayUriData pud;
    463         const struct TALER_TESTING_Command *order_cmd;
    464         const char *order_id;
    465         const struct TALER_ClaimTokenP *claim_token;
    466 
    467         if (NULL != gos->repurchase_order_ref)
    468         {
    469           const struct TALER_TESTING_Command *rep_cmd;
    470           const char *rep_id;
    471           const char *ri;
    472 
    473           rep_cmd = TALER_TESTING_interpreter_lookup_command (
    474             gos->is,
    475             gos->repurchase_order_ref);
    476           if (GNUNET_OK !=
    477               TALER_TESTING_get_trait_order_id (rep_cmd,
    478                                                 &rep_id))
    479           {
    480             TALER_TESTING_FAIL (gos->is);
    481           }
    482           ri = osr->details.ok.details.unpaid.already_paid_order_id;
    483           if ( (NULL == ri) ||
    484                (0 !=
    485                 strcmp (ri,
    486                         rep_id)) )
    487           {
    488             TALER_TESTING_FAIL (gos->is);
    489           }
    490         }
    491 
    492         if (GNUNET_OK !=
    493             TALER_MERCHANT_parse_pay_uri (
    494               osr->details.ok.details.unpaid.taler_pay_uri,
    495               &pud))
    496         {
    497           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    498                       "Taler pay uri `%s' is malformed\n",
    499                       osr->details.ok.details.unpaid.taler_pay_uri);
    500           TALER_TESTING_interpreter_fail (gos->is);
    501           return;
    502         }
    503 
    504         order_cmd = TALER_TESTING_interpreter_lookup_command (
    505           gos->is,
    506           gos->order_reference);
    507 
    508         if (GNUNET_OK !=
    509             TALER_TESTING_get_trait_order_id (order_cmd,
    510                                               &order_id))
    511         {
    512           TALER_MERCHANT_parse_pay_uri_free (&pud);
    513           TALER_TESTING_FAIL (gos->is);
    514         }
    515 
    516         if (GNUNET_OK !=
    517             TALER_TESTING_get_trait_claim_token (order_cmd,
    518                                                  &claim_token))
    519         {
    520           TALER_MERCHANT_parse_pay_uri_free (&pud);
    521           TALER_TESTING_FAIL (gos->is);
    522         }
    523         {
    524           char *host;
    525 
    526           host = TALER_MERCHANT_TESTING_extract_host (gos->merchant_url);
    527           if ((0 != strcmp (host,
    528                             pud.merchant_host)) ||
    529               (NULL != pud.merchant_prefix_path) ||
    530               (0 != strcmp (order_id,
    531                             pud.order_id)) ||
    532               (NULL != pud.ssid))
    533           {
    534             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    535                         "Order pay uri `%s' does not match, wanted %s/%s\n",
    536                         osr->details.ok.details.unpaid.taler_pay_uri,
    537                         host,
    538                         order_id);
    539             TALER_TESTING_interpreter_fail (gos->is);
    540             TALER_MERCHANT_parse_pay_uri_free (&pud);
    541             GNUNET_free (host);
    542             return;
    543           }
    544           GNUNET_free (host);
    545         }
    546         /* The claim token is not given in the pay uri if the order
    547            has been claimed already. */
    548         if ( (NULL != pud.claim_token) &&
    549              ( (NULL == claim_token) ||
    550                (0 != GNUNET_memcmp (claim_token,
    551                                     pud.claim_token)) ) )
    552         {
    553           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    554                       "Order pay uri claim token does not match (%d/%d/%d/%d)\n",
    555                       NULL == pud.claim_token,
    556                       NULL == claim_token,
    557                       (NULL != pud.claim_token) &&
    558                       GNUNET_is_zero (pud.claim_token),
    559                       (NULL != claim_token) &&
    560                       GNUNET_is_zero (claim_token));
    561           TALER_TESTING_interpreter_fail (gos->is);
    562           TALER_MERCHANT_parse_pay_uri_free (&pud);
    563           return;
    564         }
    565         TALER_MERCHANT_parse_pay_uri_free (&pud);
    566         break;
    567       }
    568     }
    569     break;
    570   default:
    571     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    572                 "Unhandled HTTP status.\n");
    573   }
    574   TALER_TESTING_interpreter_next (gos->is);
    575 }
    576 
    577 
    578 /**
    579  * Run the "GET order" CMD.
    580  *
    581  * @param cls closure.
    582  * @param cmd command being run now.
    583  * @param is interpreter state.
    584  */
    585 static void
    586 merchant_get_order_run (void *cls,
    587                         const struct TALER_TESTING_Command *cmd,
    588                         struct TALER_TESTING_Interpreter *is)
    589 {
    590   struct MerchantGetOrderState *gos = cls;
    591   const struct TALER_TESTING_Command *order_cmd;
    592   const char *order_id;
    593   const struct TALER_PrivateContractHashP *h_contract;
    594 
    595   order_cmd = TALER_TESTING_interpreter_lookup_command (
    596     is,
    597     gos->order_reference);
    598 
    599   if (GNUNET_OK !=
    600       TALER_TESTING_get_trait_order_id (order_cmd,
    601                                         &order_id))
    602     TALER_TESTING_FAIL (is);
    603 
    604   if (GNUNET_OK !=
    605       TALER_TESTING_get_trait_h_contract_terms (order_cmd,
    606                                                 &h_contract))
    607     TALER_TESTING_FAIL (is);
    608 
    609   gos->is = is;
    610   gos->ogh = TALER_MERCHANT_get_private_order_create (
    611     TALER_TESTING_interpreter_get_context (is),
    612     gos->merchant_url,
    613     order_id);
    614   GNUNET_assert (NULL != gos->ogh);
    615   if (NULL != gos->session_id)
    616   {
    617     TALER_MERCHANT_get_private_order_set_options (
    618       gos->ogh,
    619       TALER_MERCHANT_get_private_order_option_session_id (
    620         gos->session_id));
    621   }
    622   {
    623     enum TALER_ErrorCode ec;
    624 
    625     ec = TALER_MERCHANT_get_private_order_start (
    626       gos->ogh,
    627       &merchant_get_order_cb,
    628       gos);
    629     GNUNET_assert (TALER_EC_NONE == ec);
    630   }
    631 }
    632 
    633 
    634 /**
    635  * Free the state of a "GET order" CMD, and possibly
    636  * cancel a pending operation thereof.
    637  *
    638  * @param cls closure.
    639  * @param cmd command being run.
    640  */
    641 static void
    642 merchant_get_order_cleanup (void *cls,
    643                             const struct TALER_TESTING_Command *cmd)
    644 {
    645   struct MerchantGetOrderState *gos = cls;
    646 
    647   if (NULL != gos->ogh)
    648   {
    649     TALER_LOG_WARNING ("Get order operation did not complete\n");
    650     TALER_MERCHANT_get_private_order_cancel (gos->ogh);
    651   }
    652   GNUNET_array_grow (gos->transfers,
    653                      gos->transfers_length,
    654                      0);
    655   GNUNET_array_grow (gos->refunds,
    656                      gos->refunds_length,
    657                      0);
    658   GNUNET_array_grow (gos->forgets,
    659                      gos->forgets_length,
    660                      0);
    661   GNUNET_free (gos);
    662 }
    663 
    664 
    665 struct TALER_TESTING_Command
    666 TALER_TESTING_cmd_merchant_get_order (
    667   const char *label,
    668   const char *merchant_url,
    669   const char *order_reference,
    670   enum TALER_MERCHANT_OrderStatusCode osc,
    671   bool refunded,
    672   unsigned int http_status,
    673   ...)
    674 {
    675   struct MerchantGetOrderState *gos;
    676 
    677   gos = GNUNET_new (struct MerchantGetOrderState);
    678   gos->merchant_url = merchant_url;
    679   gos->order_reference = order_reference;
    680   gos->osc = osc;
    681   gos->refunded = refunded;
    682   gos->http_status = http_status;
    683   if (refunded)
    684   {
    685     const char *clabel;
    686     va_list ap;
    687 
    688     va_start (ap, http_status);
    689     while (NULL != (clabel = va_arg (ap, const char *)))
    690     {
    691       GNUNET_array_append (gos->refunds,
    692                            gos->refunds_length,
    693                            clabel);
    694     }
    695     va_end (ap);
    696   }
    697   {
    698     struct TALER_TESTING_Command cmd = {
    699       .cls = gos,
    700       .label = label,
    701       .run = &merchant_get_order_run,
    702       .cleanup = &merchant_get_order_cleanup
    703     };
    704 
    705     return cmd;
    706   }
    707 }
    708 
    709 
    710 struct TALER_TESTING_Command
    711 TALER_TESTING_cmd_merchant_get_order2 (
    712   const char *label,
    713   const char *merchant_url,
    714   const char *order_reference,
    715   enum TALER_MERCHANT_OrderStatusCode osc,
    716   bool wired,
    717   const char **transfers,
    718   bool refunded,
    719   const char **refunds,
    720   const char **forgets,
    721   unsigned int http_status)
    722 {
    723   struct MerchantGetOrderState *gos;
    724 
    725   gos = GNUNET_new (struct MerchantGetOrderState);
    726   gos->merchant_url = merchant_url;
    727   gos->order_reference = order_reference;
    728   gos->osc = osc;
    729   gos->wired = wired;
    730   gos->refunded = refunded;
    731   gos->http_status = http_status;
    732   if (wired)
    733   {
    734     for (const char **clabel = transfers; *clabel != NULL; ++clabel)
    735     {
    736       GNUNET_array_append (gos->transfers,
    737                            gos->transfers_length,
    738                            *clabel);
    739     }
    740   }
    741   if (refunded)
    742   {
    743     for (const char **clabel = refunds; *clabel != NULL; ++clabel)
    744     {
    745       GNUNET_array_append (gos->refunds,
    746                            gos->refunds_length,
    747                            *clabel);
    748     }
    749   }
    750   if (NULL != forgets)
    751   {
    752     for (const char **clabel = forgets; *clabel != NULL; ++clabel)
    753     {
    754       GNUNET_array_append (gos->forgets,
    755                            gos->forgets_length,
    756                            *clabel);
    757     }
    758   }
    759   {
    760     struct TALER_TESTING_Command cmd = {
    761       .cls = gos,
    762       .label = label,
    763       .run = &merchant_get_order_run,
    764       .cleanup = &merchant_get_order_cleanup
    765     };
    766 
    767     return cmd;
    768   }
    769 }
    770 
    771 
    772 struct TALER_TESTING_Command
    773 TALER_TESTING_cmd_merchant_get_order3 (
    774   const char *label,
    775   const char *merchant_url,
    776   const char *order_reference,
    777   enum TALER_MERCHANT_OrderStatusCode osc,
    778   const char *session_id,
    779   const char *repurchase_order_ref,
    780   unsigned int expected_http_status)
    781 {
    782   struct MerchantGetOrderState *gos;
    783 
    784   gos = GNUNET_new (struct MerchantGetOrderState);
    785   gos->merchant_url = merchant_url;
    786   gos->order_reference = order_reference;
    787   gos->osc = osc;
    788   gos->session_id = session_id;
    789   gos->repurchase_order_ref = repurchase_order_ref;
    790   gos->http_status = expected_http_status;
    791   {
    792     struct TALER_TESTING_Command cmd = {
    793       .cls = gos,
    794       .label = label,
    795       .run = &merchant_get_order_run,
    796       .cleanup = &merchant_get_order_cleanup
    797     };
    798 
    799     return cmd;
    800   }
    801 }
    802 
    803 
    804 struct TALER_TESTING_Command
    805 TALER_TESTING_cmd_merchant_get_order4 (
    806   const char *label,
    807   const char *merchant_url,
    808   const char *order_reference,
    809   enum TALER_MERCHANT_OrderStatusCode osc,
    810   uint32_t expected_min_age,
    811   unsigned int expected_http_status)
    812 {
    813   struct MerchantGetOrderState *gos;
    814 
    815   gos = GNUNET_new (struct MerchantGetOrderState);
    816   gos->merchant_url = merchant_url;
    817   gos->order_reference = order_reference;
    818   gos->osc = osc;
    819   gos->expected_min_age = expected_min_age;
    820   gos->http_status = expected_http_status;
    821   {
    822     struct TALER_TESTING_Command cmd = {
    823       .cls = gos,
    824       .label = label,
    825       .run = &merchant_get_order_run,
    826       .cleanup = &merchant_get_order_cleanup
    827     };
    828 
    829     return cmd;
    830   }
    831 }
    832 
    833 
    834 struct MerchantPollOrderConcludeState
    835 {
    836   /**
    837    * The interpreter state.
    838    */
    839   struct TALER_TESTING_Interpreter *is;
    840 
    841   /**
    842    * Reference to a command that can provide a poll order start command.
    843    */
    844   const char *start_reference;
    845 
    846   /**
    847    * Task to wait for the deadline.
    848    */
    849   struct GNUNET_SCHEDULER_Task *task;
    850 
    851   /**
    852    * Expected HTTP response status code.
    853    */
    854   unsigned int expected_http_status;
    855 };
    856 
    857 
    858 struct MerchantPollOrderStartState
    859 {
    860   /**
    861    * The merchant base URL.
    862    */
    863   const char *merchant_url;
    864 
    865   /**
    866    * The handle to the current GET /private/orders/$ORDER_ID request.
    867    */
    868   struct TALER_MERCHANT_GetPrivateOrderHandle *ogh;
    869 
    870   /**
    871    * The interpreter state.
    872    */
    873   struct TALER_TESTING_Interpreter *is;
    874 
    875   /**
    876    * Reference to a command that created an order.
    877    */
    878   const char *order_id;
    879 
    880   /**
    881    * How long to wait for server to return a response.
    882    */
    883   struct GNUNET_TIME_Relative timeout;
    884 
    885   /**
    886    * Conclude state waiting for completion (if any).
    887    */
    888   struct MerchantPollOrderConcludeState *cs;
    889 
    890   /**
    891    * The HTTP status code returned by the backend.
    892    */
    893   unsigned int http_status;
    894 
    895   /**
    896    * When the request should be completed by.
    897    */
    898   struct GNUNET_TIME_Absolute deadline;
    899 };
    900 
    901 
    902 /**
    903  * Task called when either the timeout for the GET /private/order/$ID command
    904  * expired or we got a response.  Checks if the result is what we expected.
    905  *
    906  * @param cls a `struct MerchantPollOrderConcludeState`
    907  */
    908 static void
    909 conclude_task (void *cls)
    910 {
    911   struct MerchantPollOrderConcludeState *ppc = cls;
    912   const struct TALER_TESTING_Command *poll_cmd;
    913   struct MerchantPollOrderStartState *cps;
    914   struct GNUNET_TIME_Absolute now;
    915 
    916   ppc->task = NULL;
    917   poll_cmd =
    918     TALER_TESTING_interpreter_lookup_command (ppc->is,
    919                                               ppc->start_reference);
    920   if (NULL == poll_cmd)
    921     TALER_TESTING_FAIL (ppc->is);
    922   cps = poll_cmd->cls;
    923   if (NULL != cps->ogh)
    924   {
    925     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    926                 "Expected poll GET /private/orders/$ORDER_ID to have completed, but it did not!\n");
    927     TALER_TESTING_FAIL (ppc->is);
    928   }
    929   if (cps->http_status != ppc->expected_http_status)
    930   {
    931     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    932                 "Expected HTTP status %u, got %u\n",
    933                 ppc->expected_http_status,
    934                 cps->http_status);
    935     TALER_TESTING_FAIL (ppc->is);
    936   }
    937   now = GNUNET_TIME_absolute_get ();
    938   if ((GNUNET_TIME_absolute_add (cps->deadline,
    939                                  GNUNET_TIME_UNIT_SECONDS).abs_value_us <
    940        now.abs_value_us) )
    941   {
    942     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    943                 "Expected answer to be delayed until %llu, but got response at %llu\n",
    944                 (unsigned long long) cps->deadline.abs_value_us,
    945                 (unsigned long long) now.abs_value_us);
    946     TALER_TESTING_FAIL (ppc->is);
    947   }
    948   TALER_TESTING_interpreter_next (ppc->is);
    949 }
    950 
    951 
    952 /**
    953  * Callback to process a GET /private/orders/$ID request
    954  *
    955  * @param cls closure
    956  * @param osr order status response details
    957  */
    958 /* FIXME_POLYMORPHISM: see merchant_get_order_cb above — same GetPrivateOrderCallback
    959    typedef is reused here with a different closure type, so this compilation unit cannot
    960    adopt a single TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE override. Left as
    961    void *cls. */
    962 static void
    963 merchant_poll_order_cb (
    964   void *cls,
    965   const struct TALER_MERCHANT_GetPrivateOrderResponse *osr)
    966 {
    967   struct MerchantPollOrderStartState *pos = cls;
    968   const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr;
    969 
    970   pos->ogh = NULL;
    971   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    972               "GET /private/orders/$ID finished with status %u.\n",
    973               hr->http_status);
    974   pos->http_status = hr->http_status;
    975   switch (hr->http_status)
    976   {
    977   case MHD_HTTP_OK:
    978     // FIXME: keep data from 'osr' here for checking?
    979     break;
    980   default:
    981     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    982                 "Unhandled HTTP status.\n");
    983   }
    984   if (NULL != pos->cs)
    985   {
    986     GNUNET_SCHEDULER_cancel (pos->cs->task);
    987     pos->cs->task = GNUNET_SCHEDULER_add_now (&conclude_task,
    988                                               pos->cs);
    989   }
    990 }
    991 
    992 
    993 /**
    994  * Run the "GET order" CMD.
    995  *
    996  * @param cls closure.
    997  * @param cmd command being run now.
    998  * @param is interpreter state.
    999  */
   1000 static void
   1001 merchant_poll_order_start_run (void *cls,
   1002                                const struct TALER_TESTING_Command *cmd,
   1003                                struct TALER_TESTING_Interpreter *is)
   1004 {
   1005   struct MerchantPollOrderStartState *pos = cls;
   1006 
   1007   /* add 1s grace time to timeout */
   1008   pos->deadline
   1009     = GNUNET_TIME_absolute_add (GNUNET_TIME_relative_to_absolute (pos->timeout),
   1010                                 GNUNET_TIME_UNIT_SECONDS);
   1011   pos->is = is;
   1012   pos->ogh = TALER_MERCHANT_get_private_order_create (
   1013     TALER_TESTING_interpreter_get_context (is),
   1014     pos->merchant_url,
   1015     pos->order_id);
   1016   GNUNET_assert (NULL != pos->ogh);
   1017   if (pos->timeout.rel_value_us > 0)
   1018   {
   1019     TALER_MERCHANT_get_private_order_set_options (
   1020       pos->ogh,
   1021       TALER_MERCHANT_get_private_order_option_timeout (
   1022         pos->timeout));
   1023   }
   1024   {
   1025     enum TALER_ErrorCode ec;
   1026 
   1027     ec = TALER_MERCHANT_get_private_order_start (
   1028       pos->ogh,
   1029       &merchant_poll_order_cb,
   1030       pos);
   1031     GNUNET_assert (TALER_EC_NONE == ec);
   1032   }
   1033   /* We CONTINUE to run the interpreter while the long-polled command
   1034      completes asynchronously! */
   1035   TALER_TESTING_interpreter_next (pos->is);
   1036 }
   1037 
   1038 
   1039 /**
   1040  * Free the state of a "GET order" CMD, and possibly
   1041  * cancel a pending operation thereof.
   1042  *
   1043  * @param cls closure.
   1044  * @param cmd command being run.
   1045  */
   1046 static void
   1047 merchant_poll_order_start_cleanup (void *cls,
   1048                                    const struct TALER_TESTING_Command *cmd)
   1049 {
   1050   struct MerchantPollOrderStartState *pos = cls;
   1051 
   1052   if (NULL != pos->ogh)
   1053   {
   1054     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1055                 "Command `%s' was not terminated\n",
   1056                 TALER_TESTING_interpreter_get_current_label (
   1057                   pos->is));
   1058     TALER_MERCHANT_get_private_order_cancel (pos->ogh);
   1059   }
   1060   GNUNET_free (pos);
   1061 }
   1062 
   1063 
   1064 struct TALER_TESTING_Command
   1065 TALER_TESTING_cmd_poll_order_start (
   1066   const char *label,
   1067   const char *merchant_url,
   1068   const char *order_id,
   1069   struct GNUNET_TIME_Relative timeout)
   1070 {
   1071   struct MerchantPollOrderStartState *pos;
   1072 
   1073   pos = GNUNET_new (struct MerchantPollOrderStartState);
   1074   pos->order_id = order_id;
   1075   pos->merchant_url = merchant_url;
   1076   pos->timeout = timeout;
   1077   {
   1078     struct TALER_TESTING_Command cmd = {
   1079       .cls = pos,
   1080       .label = label,
   1081       .run = &merchant_poll_order_start_run,
   1082       .cleanup = &merchant_poll_order_start_cleanup
   1083     };
   1084 
   1085     return cmd;
   1086   }
   1087 }
   1088 
   1089 
   1090 /**
   1091  * Run the "GET order conclude" CMD.
   1092  *
   1093  * @param cls closure.
   1094  * @param cmd command being run now.
   1095  * @param is interpreter state.
   1096  */
   1097 static void
   1098 merchant_poll_order_conclude_run (void *cls,
   1099                                   const struct TALER_TESTING_Command *cmd,
   1100                                   struct TALER_TESTING_Interpreter *is)
   1101 {
   1102   struct MerchantPollOrderConcludeState *poc = cls;
   1103   const struct TALER_TESTING_Command *poll_cmd;
   1104   struct MerchantPollOrderStartState *pos;
   1105 
   1106   poc->is = is;
   1107   poll_cmd =
   1108     TALER_TESTING_interpreter_lookup_command (is,
   1109                                               poc->start_reference);
   1110   if (NULL == poll_cmd)
   1111     TALER_TESTING_FAIL (poc->is);
   1112   GNUNET_assert (poll_cmd->run == &merchant_poll_order_start_run);
   1113   pos = poll_cmd->cls;
   1114   pos->cs = poc;
   1115   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1116               "Waiting on GET /private/orders/$ID of %s (%s)\n",
   1117               poc->start_reference,
   1118               (NULL == pos->ogh)
   1119               ? "finished"
   1120               : "active");
   1121   if (NULL == pos->ogh)
   1122     poc->task = GNUNET_SCHEDULER_add_now (&conclude_task,
   1123                                           poc);
   1124   else
   1125     poc->task = GNUNET_SCHEDULER_add_at (pos->deadline,
   1126                                          &conclude_task,
   1127                                          poc);
   1128 }
   1129 
   1130 
   1131 /**
   1132  * Free the state of a "GET order" CMD, and possibly
   1133  * cancel a pending operation thereof.
   1134  *
   1135  * @param cls closure.
   1136  * @param cmd command being run.
   1137  */
   1138 static void
   1139 merchant_poll_order_conclude_cleanup (void *cls,
   1140                                       const struct TALER_TESTING_Command *cmd)
   1141 {
   1142   struct MerchantPollOrderConcludeState *poc = cls;
   1143 
   1144   if (NULL != poc->task)
   1145   {
   1146     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1147                 "Command `%s' was not terminated\n",
   1148                 TALER_TESTING_interpreter_get_current_label (
   1149                   poc->is));
   1150     GNUNET_SCHEDULER_cancel (poc->task);
   1151     poc->task = NULL;
   1152   }
   1153   GNUNET_free (poc);
   1154 }
   1155 
   1156 
   1157 struct TALER_TESTING_Command
   1158 TALER_TESTING_cmd_poll_order_conclude (const char *label,
   1159                                        unsigned int http_status,
   1160                                        const char *poll_start_reference)
   1161 {
   1162   struct MerchantPollOrderConcludeState *cps;
   1163 
   1164   cps = GNUNET_new (struct MerchantPollOrderConcludeState);
   1165   cps->start_reference = poll_start_reference;
   1166   cps->expected_http_status = http_status;
   1167   {
   1168     struct TALER_TESTING_Command cmd = {
   1169       .cls = cps,
   1170       .label = label,
   1171       .run = &merchant_poll_order_conclude_run,
   1172       .cleanup = &merchant_poll_order_conclude_cleanup
   1173     };
   1174 
   1175     return cmd;
   1176   }
   1177 }
   1178 
   1179 
   1180 /* end of testing_api_cmd_merchant_get_order.c */