merchant

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

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


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