merchant

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

testing_api_cmd_merchant_get_order.c (34244B)


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