merchant

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

taler-merchant-httpd_post-orders-ID-claim.c (10210B)


      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 taler-merchant-httpd_post-orders-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_json_lib.h>
     30 #include "taler-merchant-httpd_private-get-orders.h"
     31 #include "taler-merchant-httpd_post-orders-ID-claim.h"
     32 
     33 
     34 /**
     35  * How often do we retry the database transaction?
     36  */
     37 #define MAX_RETRIES 3
     38 
     39 
     40 /**
     41  * Run transaction to claim @a order_id for @a nonce.
     42  *
     43  * @param instance_id instance to claim order at
     44  * @param order_id order to claim
     45  * @param nonce nonce to use for the claim
     46  * @param claim_token the token that should be used to verify the claim
     47  * @param[out] contract_terms set to the resulting contract terms
     48  *             (for any non-negative result;
     49  * @return transaction status code
     50  *         #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the order was claimed by a different
     51  *         nonce (@a contract_terms set to non-NULL)
     52  *                OR if the order is is unknown (@a contract_terms is NULL)
     53  *         #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the order was successfully claimed
     54  */
     55 static enum GNUNET_DB_QueryStatus
     56 claim_order (const char *instance_id,
     57              const char *order_id,
     58              const char *nonce,
     59              const struct TALER_ClaimTokenP *claim_token,
     60              json_t **contract_terms)
     61 {
     62   struct TALER_ClaimTokenP order_ct;
     63   enum GNUNET_DB_QueryStatus qs;
     64   uint64_t order_serial;
     65 
     66   if (GNUNET_OK !=
     67       TMH_db->start (TMH_db->cls,
     68                      "claim order"))
     69   {
     70     GNUNET_break (0);
     71     return GNUNET_DB_STATUS_HARD_ERROR;
     72   }
     73   qs = TMH_db->lookup_contract_terms (TMH_db->cls,
     74                                       instance_id,
     75                                       order_id,
     76                                       contract_terms,
     77                                       &order_serial,
     78                                       NULL);
     79   if (0 > qs)
     80   {
     81     TMH_db->rollback (TMH_db->cls);
     82     return qs;
     83   }
     84 
     85   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     86   {
     87     /* We already have claimed contract terms for this order_id */
     88     const char *stored_nonce;
     89     struct GNUNET_JSON_Specification spec[] = {
     90       GNUNET_JSON_spec_string ("nonce",
     91                                &stored_nonce),
     92       GNUNET_JSON_spec_end ()
     93     };
     94 
     95     TMH_db->rollback (TMH_db->cls);
     96     GNUNET_assert (NULL != *contract_terms);
     97 
     98     if (GNUNET_OK !=
     99         GNUNET_JSON_parse (*contract_terms,
    100                            spec,
    101                            NULL,
    102                            NULL))
    103     {
    104       /* this should not be possible: contract_terms should always
    105          have a nonce! */
    106       GNUNET_break (0);
    107       return GNUNET_DB_STATUS_HARD_ERROR;
    108     }
    109 
    110     if (0 != strcmp (stored_nonce,
    111                      nonce))
    112     {
    113       GNUNET_JSON_parse_free (spec);
    114       return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    115     }
    116     GNUNET_JSON_parse_free (spec);
    117     return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    118   }
    119 
    120   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
    121 
    122   /* Now we need to claim the order. */
    123   {
    124     struct TALER_MerchantPostDataHashP unused;
    125     struct GNUNET_TIME_Timestamp timestamp;
    126     struct GNUNET_JSON_Specification spec[] = {
    127       GNUNET_JSON_spec_timestamp ("timestamp",
    128                                   &timestamp),
    129       GNUNET_JSON_spec_end ()
    130     };
    131 
    132     /* see if we have this order in our table of unclaimed orders */
    133     qs = TMH_db->lookup_order (TMH_db->cls,
    134                                instance_id,
    135                                order_id,
    136                                &order_ct,
    137                                &unused,
    138                                contract_terms);
    139     if (0 >= qs)
    140     {
    141       TMH_db->rollback (TMH_db->cls);
    142       return qs;
    143     }
    144     GNUNET_assert (NULL != *contract_terms);
    145     if (GNUNET_OK !=
    146         GNUNET_JSON_parse (*contract_terms,
    147                            spec,
    148                            NULL,
    149                            NULL))
    150     {
    151       /* this should not be possible: contract_terms should always
    152          have a timestamp! */
    153       GNUNET_break (0);
    154       TMH_db->rollback (TMH_db->cls);
    155       return GNUNET_DB_STATUS_HARD_ERROR;
    156     }
    157 
    158     GNUNET_assert (0 ==
    159                    json_object_set_new (*contract_terms,
    160                                         "nonce",
    161                                         json_string (nonce)));
    162     if (0 != GNUNET_memcmp_priv (&order_ct,
    163                                  claim_token))
    164     {
    165       TMH_db->rollback (TMH_db->cls);
    166       json_decref (*contract_terms);
    167       *contract_terms = NULL;
    168       return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    169     }
    170     qs = TMH_db->insert_contract_terms (TMH_db->cls,
    171                                         instance_id,
    172                                         order_id,
    173                                         *contract_terms,
    174                                         &order_serial);
    175     if (0 >= qs)
    176     {
    177       TMH_db->rollback (TMH_db->cls);
    178       json_decref (*contract_terms);
    179       *contract_terms = NULL;
    180       return qs;
    181     }
    182     TMH_notify_order_change (TMH_lookup_instance (instance_id),
    183                              TMH_OSF_CLAIMED,
    184                              timestamp,
    185                              order_serial);
    186     qs = TMH_db->commit (TMH_db->cls);
    187     if (0 > qs)
    188       return qs;
    189     return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    190   }
    191 }
    192 
    193 
    194 MHD_RESULT
    195 TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh,
    196                           struct MHD_Connection *connection,
    197                           struct TMH_HandlerContext *hc)
    198 {
    199   const char *order_id = hc->infix;
    200   const char *nonce;
    201   enum GNUNET_DB_QueryStatus qs;
    202   json_t *contract_terms;
    203   struct TALER_ClaimTokenP claim_token = { 0 };
    204 
    205   {
    206     struct GNUNET_JSON_Specification spec[] = {
    207       GNUNET_JSON_spec_string ("nonce",
    208                                &nonce),
    209       GNUNET_JSON_spec_mark_optional (
    210         GNUNET_JSON_spec_fixed_auto ("token",
    211                                      &claim_token),
    212         NULL),
    213       GNUNET_JSON_spec_end ()
    214     };
    215     enum GNUNET_GenericReturnValue res;
    216 
    217     res = TALER_MHD_parse_json_data (connection,
    218                                      hc->request_body,
    219                                      spec);
    220     if (GNUNET_OK != res)
    221     {
    222       GNUNET_break_op (0);
    223       json_dumpf (hc->request_body,
    224                   stderr,
    225                   JSON_INDENT (2));
    226       return (GNUNET_NO == res)
    227              ? MHD_YES
    228              : MHD_NO;
    229     }
    230   }
    231   contract_terms = NULL;
    232   for (unsigned int i = 0; i<MAX_RETRIES; i++)
    233   {
    234     TMH_db->preflight (TMH_db->cls);
    235     qs = claim_order (hc->instance->settings.id,
    236                       order_id,
    237                       nonce,
    238                       &claim_token,
    239                       &contract_terms);
    240     if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
    241       break;
    242   }
    243   switch (qs)
    244   {
    245   case GNUNET_DB_STATUS_HARD_ERROR:
    246     return TALER_MHD_reply_with_error (connection,
    247                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    248                                        TALER_EC_GENERIC_DB_COMMIT_FAILED,
    249                                        NULL);
    250   case GNUNET_DB_STATUS_SOFT_ERROR:
    251     return TALER_MHD_reply_with_error (connection,
    252                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    253                                        TALER_EC_GENERIC_DB_SOFT_FAILURE,
    254                                        NULL);
    255   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    256     if (NULL == contract_terms)
    257       return TALER_MHD_reply_with_error (connection,
    258                                          MHD_HTTP_NOT_FOUND,
    259                                          TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_NOT_FOUND,
    260                                          order_id);
    261     /* already claimed! */
    262     json_decref (contract_terms);
    263     return TALER_MHD_reply_with_error (connection,
    264                                        MHD_HTTP_CONFLICT,
    265                                        TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED,
    266                                        order_id);
    267   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    268     GNUNET_assert (NULL != contract_terms);
    269     break; /* Good! return signature (below) */
    270   }
    271 
    272   /* create contract signature */
    273   {
    274     struct TALER_PrivateContractHashP hash;
    275     struct TALER_MerchantSignatureP merchant_sig;
    276 
    277     /**
    278      * Hash of the JSON contract in UTF-8 including 0-termination,
    279      * using JSON_COMPACT | JSON_SORT_KEYS
    280      */
    281 
    282     if (GNUNET_OK !=
    283         TALER_JSON_contract_hash (contract_terms,
    284                                   &hash))
    285     {
    286       GNUNET_break (0);
    287       json_decref (contract_terms);
    288       return TALER_MHD_reply_with_error (connection,
    289                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    290                                          TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
    291                                          NULL);
    292     }
    293 
    294     TALER_merchant_contract_sign (&hash,
    295                                   &hc->instance->merchant_priv,
    296                                   &merchant_sig);
    297     return TALER_MHD_REPLY_JSON_PACK (
    298       connection,
    299       MHD_HTTP_OK,
    300       GNUNET_JSON_pack_object_steal ("contract_terms",
    301                                      contract_terms),
    302       GNUNET_JSON_pack_data_auto ("sig",
    303                                   &merchant_sig));
    304   }
    305 }
    306 
    307 
    308 /* end of taler-merchant-httpd_post-orders-ID-claim.c */