merchant

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

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


      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/lookup_contract_terms.h"
     35 #include "merchant-database/lookup_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_lookup_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       return GNUNET_DB_STATUS_HARD_ERROR;
    116     }
    117 
    118     if (0 !=
    119         GNUNET_memcmp (&stored_nonce,
    120                        nonce))
    121     {
    122       GNUNET_JSON_parse_free (spec);
    123       return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    124     }
    125     GNUNET_JSON_parse_free (spec);
    126     return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    127   }
    128 
    129   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
    130 
    131   /* Now we need to claim the order. */
    132   {
    133     struct TALER_MerchantPostDataHashP unused;
    134     struct GNUNET_TIME_Timestamp timestamp;
    135     struct GNUNET_JSON_Specification spec[] = {
    136       GNUNET_JSON_spec_timestamp ("timestamp",
    137                                   &timestamp),
    138       GNUNET_JSON_spec_end ()
    139     };
    140 
    141     /* see if we have this order in our table of unclaimed orders */
    142     qs = TALER_MERCHANTDB_lookup_order (TMH_db,
    143                                         instance_id,
    144                                         order_id,
    145                                         &order_ct,
    146                                         &unused,
    147                                         contract_terms);
    148     if (0 >= qs)
    149     {
    150       TALER_MERCHANTDB_rollback (TMH_db);
    151       return qs;
    152     }
    153     GNUNET_assert (NULL != *contract_terms);
    154     if (GNUNET_OK !=
    155         GNUNET_JSON_parse (*contract_terms,
    156                            spec,
    157                            NULL,
    158                            NULL))
    159     {
    160       /* this should not be possible: contract_terms should always
    161          have a timestamp! */
    162       GNUNET_break (0);
    163       TALER_MERCHANTDB_rollback (TMH_db);
    164       return GNUNET_DB_STATUS_HARD_ERROR;
    165     }
    166 
    167     GNUNET_assert (0 ==
    168                    json_object_set_new (
    169                      *contract_terms,
    170                      "nonce",
    171                      GNUNET_JSON_from_data_auto (nonce)));
    172     if (0 != GNUNET_memcmp_priv (&order_ct,
    173                                  claim_token))
    174     {
    175       TALER_MERCHANTDB_rollback (TMH_db);
    176       json_decref (*contract_terms);
    177       *contract_terms = NULL;
    178       return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    179     }
    180     qs = TALER_MERCHANTDB_insert_contract_terms (TMH_db,
    181                                                  instance_id,
    182                                                  order_id,
    183                                                  *contract_terms,
    184                                                  &order_serial);
    185     if (0 >= qs)
    186     {
    187       TALER_MERCHANTDB_rollback (TMH_db);
    188       json_decref (*contract_terms);
    189       *contract_terms = NULL;
    190       return qs;
    191     }
    192     // FIXME: unify notifications? or do we need both?
    193     TMH_notify_order_change (TMH_lookup_instance (instance_id),
    194                              TMH_OSF_CLAIMED,
    195                              timestamp,
    196                              order_serial);
    197     {
    198       struct TMH_OrderPayEventP pay_eh = {
    199         .header.size = htons (sizeof (pay_eh)),
    200         .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_STATUS_CHANGED),
    201         .merchant_pub = hc->instance->merchant_pub
    202       };
    203 
    204       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    205                   "Notifying clients about status change of order %s\n",
    206                   order_id);
    207       GNUNET_CRYPTO_hash (order_id,
    208                           strlen (order_id),
    209                           &pay_eh.h_order_id);
    210       TALER_MERCHANTDB_event_notify (TMH_db,
    211                                      &pay_eh.header,
    212                                      NULL,
    213                                      0);
    214     }
    215     qs = TALER_MERCHANTDB_commit (TMH_db);
    216     if (0 > qs)
    217       return qs;
    218     return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    219   }
    220 }
    221 
    222 
    223 enum MHD_Result
    224 TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh,
    225                           struct MHD_Connection *connection,
    226                           struct TMH_HandlerContext *hc)
    227 {
    228   const char *order_id = hc->infix;
    229   struct GNUNET_CRYPTO_EddsaPublicKey nonce;
    230   enum GNUNET_DB_QueryStatus qs;
    231   json_t *contract_terms;
    232   struct TALER_ClaimTokenP claim_token = { 0 };
    233 
    234   {
    235     struct GNUNET_JSON_Specification spec[] = {
    236       GNUNET_JSON_spec_fixed_auto ("nonce",
    237                                    &nonce),
    238       GNUNET_JSON_spec_mark_optional (
    239         GNUNET_JSON_spec_fixed_auto ("token",
    240                                      &claim_token),
    241         NULL),
    242       GNUNET_JSON_spec_end ()
    243     };
    244     enum GNUNET_GenericReturnValue res;
    245 
    246     res = TALER_MHD_parse_json_data (connection,
    247                                      hc->request_body,
    248                                      spec);
    249     if (GNUNET_OK != res)
    250     {
    251       GNUNET_break_op (0);
    252       json_dumpf (hc->request_body,
    253                   stderr,
    254                   JSON_INDENT (2));
    255       return (GNUNET_NO == res)
    256              ? MHD_YES
    257              : MHD_NO;
    258     }
    259   }
    260   contract_terms = NULL;
    261   for (unsigned int i = 0; i<MAX_RETRIES; i++)
    262   {
    263     TALER_MERCHANTDB_preflight (TMH_db);
    264     qs = claim_order (hc,
    265                       order_id,
    266                       &nonce,
    267                       &claim_token,
    268                       &contract_terms);
    269     if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
    270       break;
    271   }
    272   switch (qs)
    273   {
    274   case GNUNET_DB_STATUS_HARD_ERROR:
    275     return TALER_MHD_reply_with_error (connection,
    276                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    277                                        TALER_EC_GENERIC_DB_COMMIT_FAILED,
    278                                        NULL);
    279   case GNUNET_DB_STATUS_SOFT_ERROR:
    280     return TALER_MHD_reply_with_error (connection,
    281                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    282                                        TALER_EC_GENERIC_DB_SOFT_FAILURE,
    283                                        NULL);
    284   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    285     if (NULL == contract_terms)
    286       return TALER_MHD_reply_with_error (connection,
    287                                          MHD_HTTP_NOT_FOUND,
    288                                          TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_NOT_FOUND,
    289                                          order_id);
    290     /* already claimed! */
    291     json_decref (contract_terms);
    292     return TALER_MHD_reply_with_error (connection,
    293                                        MHD_HTTP_CONFLICT,
    294                                        TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED,
    295                                        order_id);
    296   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    297     GNUNET_assert (NULL != contract_terms);
    298     break; /* Good! return signature (below) */
    299   }
    300 
    301   /* create contract signature */
    302   {
    303     struct TALER_PrivateContractHashP hash;
    304     struct TALER_MerchantSignatureP merchant_sig;
    305 
    306     /**
    307      * Hash of the JSON contract in UTF-8 including 0-termination,
    308      * using JSON_COMPACT | JSON_SORT_KEYS
    309      */
    310 
    311     if (GNUNET_OK !=
    312         TALER_JSON_contract_hash (contract_terms,
    313                                   &hash))
    314     {
    315       GNUNET_break (0);
    316       json_decref (contract_terms);
    317       return TALER_MHD_reply_with_error (connection,
    318                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    319                                          TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
    320                                          NULL);
    321     }
    322 
    323     TALER_merchant_contract_sign (&hash,
    324                                   &hc->instance->merchant_priv,
    325                                   &merchant_sig);
    326     return TALER_MHD_REPLY_JSON_PACK (
    327       connection,
    328       MHD_HTTP_OK,
    329       GNUNET_JSON_pack_object_steal ("contract_terms",
    330                                      contract_terms),
    331       GNUNET_JSON_pack_data_auto ("sig",
    332                                   &merchant_sig));
    333   }
    334 }
    335 
    336 
    337 /* end of taler-merchant-httpd_post-orders-ORDER_ID-claim.c */