merchant

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

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


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