merchant

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

taler-merchant-httpd_post-orders-ORDER_ID-claim.c (11756B)


      1 /*
      2   This file is part of TALER
      3   (C) 2014, 2015, 2016, 2018, 2020 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation; either version 3,
      8   or (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,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file src/backend/taler-merchant-httpd_post-orders-ORDER_ID-claim.c
     22  * @brief headers for POST /orders/$ID/claim handler
     23  * @author Marcello Stanisci
     24  * @author Christian Grothoff
     25  */
     26 #include "platform.h"
     27 #include <jansson.h>
     28 #include <taler/taler_signatures.h>
     29 #include <taler/taler_dbevents.h>
     30 #include <taler/taler_json_lib.h>
     31 #include "taler-merchant-httpd_get-private-orders.h"
     32 #include "taler-merchant-httpd_post-orders-ORDER_ID-claim.h"
     33 #include "merchant-database/insert_contract_terms.h"
     34 #include "merchant-database/get_contract_terms.h"
     35 #include "merchant-database/get_order.h"
     36 #include "merchant-database/event_notify.h"
     37 #include "merchant-database/preflight.h"
     38 #include "merchant-database/start.h"
     39 
     40 
     41 /**
     42  * How often do we retry the database transaction?
     43  */
     44 #define MAX_RETRIES 3
     45 
     46 
     47 /**
     48  * Run transaction to claim @a order_id for @a nonce.
     49  *
     50  * @param hc handler context with information about instance to claim order at
     51  * @param order_id order to claim
     52  * @param nonce nonce to use for the claim
     53  * @param claim_token the token that should be used to verify the claim
     54  * @param[out] contract_terms set to the resulting contract terms
     55  *             (for any non-negative result;
     56  * @return transaction status code
     57  *         #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the order was claimed by a different
     58  *         nonce (@a contract_terms set to non-NULL)
     59  *                OR if the order is is unknown (@a contract_terms is NULL)
     60  *         #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the order was successfully claimed
     61  */
     62 static enum GNUNET_DB_QueryStatus
     63 claim_order (struct TMH_HandlerContext *hc,
     64              const char *order_id,
     65              const struct GNUNET_CRYPTO_EddsaPublicKey *nonce,
     66              const struct TALER_ClaimTokenP *claim_token,
     67              json_t **contract_terms)
     68 {
     69   const char *instance_id = hc->instance->settings.id;
     70   struct TALER_ClaimTokenP order_ct;
     71   enum GNUNET_DB_QueryStatus qs;
     72   uint64_t order_serial;
     73 
     74   if (GNUNET_OK !=
     75       TALER_MERCHANTDB_start (TMH_db,
     76                               "claim order"))
     77   {
     78     GNUNET_break (0);
     79     return GNUNET_DB_STATUS_HARD_ERROR;
     80   }
     81   qs = TALER_MERCHANTDB_get_contract_terms (TMH_db,
     82                                             instance_id,
     83                                             order_id,
     84                                             contract_terms,
     85                                             &order_serial,
     86                                             NULL);
     87   if (0 > qs)
     88   {
     89     TALER_MERCHANTDB_rollback (TMH_db);
     90     return qs;
     91   }
     92 
     93   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     94   {
     95     /* We already have claimed contract terms for this order_id */
     96     struct GNUNET_CRYPTO_EddsaPublicKey stored_nonce;
     97     struct GNUNET_JSON_Specification spec[] = {
     98       GNUNET_JSON_spec_fixed_auto ("nonce",
     99                                    &stored_nonce),
    100       GNUNET_JSON_spec_end ()
    101     };
    102 
    103     TALER_MERCHANTDB_rollback (TMH_db);
    104     GNUNET_assert (NULL != *contract_terms);
    105 
    106     if (GNUNET_OK !=
    107         GNUNET_JSON_parse (*contract_terms,
    108                            spec,
    109                            NULL,
    110                            NULL))
    111     {
    112       /* this should not be possible: contract_terms should always
    113          have a nonce! */
    114       GNUNET_break (0);
    115       json_decref (*contract_terms);
    116       *contract_terms = NULL;
    117       return GNUNET_DB_STATUS_HARD_ERROR;
    118     }
    119 
    120     if (0 !=
    121         GNUNET_memcmp (&stored_nonce,
    122                        nonce))
    123     {
    124       GNUNET_JSON_parse_free (spec);
    125       return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    126     }
    127     GNUNET_JSON_parse_free (spec);
    128     return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    129   }
    130 
    131   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
    132 
    133   /* Now we need to claim the order. */
    134   {
    135     struct TALER_MerchantPostDataHashP unused;
    136     struct GNUNET_TIME_Timestamp timestamp;
    137     struct GNUNET_JSON_Specification spec[] = {
    138       GNUNET_JSON_spec_timestamp ("timestamp",
    139                                   &timestamp),
    140       GNUNET_JSON_spec_end ()
    141     };
    142 
    143     /* see if we have this order in our table of unclaimed orders */
    144     qs = TALER_MERCHANTDB_get_order (TMH_db,
    145                                      instance_id,
    146                                      order_id,
    147                                      &order_ct,
    148                                      &unused,
    149                                      contract_terms);
    150     if (0 >= qs)
    151     {
    152       TALER_MERCHANTDB_rollback (TMH_db);
    153       return qs;
    154     }
    155     GNUNET_assert (NULL != *contract_terms);
    156     if (GNUNET_OK !=
    157         GNUNET_JSON_parse (*contract_terms,
    158                            spec,
    159                            NULL,
    160                            NULL))
    161     {
    162       /* this should not be possible: contract_terms should always
    163          have a timestamp! */
    164       GNUNET_break (0);
    165       TALER_MERCHANTDB_rollback (TMH_db);
    166       json_decref (*contract_terms);
    167       *contract_terms = NULL;
    168       return GNUNET_DB_STATUS_HARD_ERROR;
    169     }
    170 
    171     GNUNET_assert (0 ==
    172                    json_object_set_new (
    173                      *contract_terms,
    174                      "nonce",
    175                      GNUNET_JSON_from_data_auto (nonce)));
    176     if (0 != GNUNET_memcmp_priv (&order_ct,
    177                                  claim_token))
    178     {
    179       TALER_MERCHANTDB_rollback (TMH_db);
    180       json_decref (*contract_terms);
    181       *contract_terms = NULL;
    182       return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    183     }
    184     qs = TALER_MERCHANTDB_insert_contract_terms (TMH_db,
    185                                                  instance_id,
    186                                                  order_id,
    187                                                  *contract_terms,
    188                                                  &order_serial);
    189     if (0 >= qs)
    190     {
    191       TALER_MERCHANTDB_rollback (TMH_db);
    192       json_decref (*contract_terms);
    193       *contract_terms = NULL;
    194       return qs;
    195     }
    196     // FIXME: unify notifications? or do we need both?
    197     TMH_notify_order_change (TMH_lookup_instance (instance_id),
    198                              TMH_OSF_CLAIMED,
    199                              timestamp,
    200                              order_serial);
    201     {
    202       struct TMH_OrderPayEventP pay_eh = {
    203         .header.size = htons (sizeof (pay_eh)),
    204         .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_STATUS_CHANGED),
    205         .merchant_pub = hc->instance->merchant_pub
    206       };
    207 
    208       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    209                   "Notifying clients about status change of order %s\n",
    210                   order_id);
    211       GNUNET_CRYPTO_hash (order_id,
    212                           strlen (order_id),
    213                           &pay_eh.h_order_id);
    214       TALER_MERCHANTDB_event_notify (TMH_db,
    215                                      &pay_eh.header,
    216                                      NULL,
    217                                      0);
    218     }
    219     qs = TALER_MERCHANTDB_commit (TMH_db);
    220     if (0 > qs)
    221       return qs;
    222     return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    223   }
    224 }
    225 
    226 
    227 enum MHD_Result
    228 TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh,
    229                           struct MHD_Connection *connection,
    230                           struct TMH_HandlerContext *hc)
    231 {
    232   const char *order_id = hc->infix;
    233   struct GNUNET_CRYPTO_EddsaPublicKey nonce;
    234   enum GNUNET_DB_QueryStatus qs;
    235   json_t *contract_terms;
    236   struct TALER_ClaimTokenP claim_token = { 0 };
    237 
    238   {
    239     struct GNUNET_JSON_Specification spec[] = {
    240       GNUNET_JSON_spec_fixed_auto ("nonce",
    241                                    &nonce),
    242       GNUNET_JSON_spec_mark_optional (
    243         GNUNET_JSON_spec_fixed_auto ("token",
    244                                      &claim_token),
    245         NULL),
    246       GNUNET_JSON_spec_end ()
    247     };
    248     enum GNUNET_GenericReturnValue res;
    249 
    250     res = TALER_MHD_parse_json_data (connection,
    251                                      hc->request_body,
    252                                      spec);
    253     if (GNUNET_OK != res)
    254     {
    255       GNUNET_break_op (0);
    256       json_dumpf (hc->request_body,
    257                   stderr,
    258                   JSON_INDENT (2));
    259       return (GNUNET_NO == res)
    260              ? MHD_YES
    261              : MHD_NO;
    262     }
    263   }
    264   contract_terms = NULL;
    265   for (unsigned int i = 0; i<MAX_RETRIES; i++)
    266   {
    267     TALER_MERCHANTDB_preflight (TMH_db);
    268     qs = claim_order (hc,
    269                       order_id,
    270                       &nonce,
    271                       &claim_token,
    272                       &contract_terms);
    273     if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
    274       break;
    275   }
    276   switch (qs)
    277   {
    278   case GNUNET_DB_STATUS_HARD_ERROR:
    279     return TALER_MHD_reply_with_error (connection,
    280                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    281                                        TALER_EC_GENERIC_DB_COMMIT_FAILED,
    282                                        NULL);
    283   case GNUNET_DB_STATUS_SOFT_ERROR:
    284     return TALER_MHD_reply_with_error (connection,
    285                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    286                                        TALER_EC_GENERIC_DB_SOFT_FAILURE,
    287                                        NULL);
    288   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    289     if (NULL == contract_terms)
    290       return TALER_MHD_reply_with_error (connection,
    291                                          MHD_HTTP_NOT_FOUND,
    292                                          TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_NOT_FOUND,
    293                                          order_id);
    294     /* already claimed! */
    295     json_decref (contract_terms);
    296     return TALER_MHD_reply_with_error (connection,
    297                                        MHD_HTTP_CONFLICT,
    298                                        TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED,
    299                                        order_id);
    300   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    301     GNUNET_assert (NULL != contract_terms);
    302     break; /* Good! return signature (below) */
    303   }
    304 
    305   /* create contract signature */
    306   {
    307     struct TALER_PrivateContractHashP hash;
    308     struct TALER_MerchantSignatureP merchant_sig;
    309 
    310     /**
    311      * Hash of the JSON contract in UTF-8 including 0-termination,
    312      * using JSON_COMPACT | JSON_SORT_KEYS
    313      */
    314 
    315     if (GNUNET_OK !=
    316         TALER_JSON_contract_hash (contract_terms,
    317                                   &hash))
    318     {
    319       GNUNET_break (0);
    320       json_decref (contract_terms);
    321       return TALER_MHD_reply_with_error (connection,
    322                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    323                                          TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
    324                                          NULL);
    325     }
    326 
    327     TALER_merchant_contract_sign (&hash,
    328                                   &hc->instance->merchant_priv,
    329                                   &merchant_sig);
    330     return TALER_MHD_REPLY_JSON_PACK (
    331       connection,
    332       MHD_HTTP_OK,
    333       GNUNET_JSON_pack_object_steal ("contract_terms",
    334                                      contract_terms),
    335       GNUNET_JSON_pack_data_auto ("sig",
    336                                   &merchant_sig));
    337   }
    338 }
    339 
    340 
    341 /* end of taler-merchant-httpd_post-orders-ORDER_ID-claim.c */