LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_post-orders-ORDER_ID-claim.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 69.5 % 105 73
Test Date: 2026-09-04 23:42:01 Functions: 100.0 % 2 2

            Line data    Source code
       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          105 : 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          105 :   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          105 :   if (GNUNET_OK !=
      60          105 :       TALER_MERCHANTDB_start (TMH_db,
      61              :                               "claim order"))
      62              :   {
      63            0 :     GNUNET_break (0);
      64            0 :     return GNUNET_DB_STATUS_HARD_ERROR;
      65              :   }
      66          105 :   qs = TALER_MERCHANTDB_get_contract_terms (TMH_db,
      67              :                                             instance_id,
      68              :                                             order_id,
      69              :                                             contract_terms,
      70              :                                             &order_serial,
      71              :                                             NULL);
      72          105 :   if (0 > qs)
      73              :   {
      74            0 :     TALER_MERCHANTDB_rollback (TMH_db);
      75            0 :     return qs;
      76              :   }
      77              : 
      78          105 :   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           19 :       GNUNET_JSON_spec_fixed_auto ("nonce",
      84              :                                    &stored_nonce),
      85           19 :       GNUNET_JSON_spec_end ()
      86              :     };
      87              : 
      88           19 :     TALER_MERCHANTDB_rollback (TMH_db);
      89           19 :     GNUNET_assert (NULL != *contract_terms);
      90              : 
      91           19 :     if (GNUNET_OK !=
      92           19 :         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            0 :       GNUNET_break (0);
     100            0 :       json_decref (*contract_terms);
     101            0 :       *contract_terms = NULL;
     102            0 :       return GNUNET_DB_STATUS_HARD_ERROR;
     103              :     }
     104              : 
     105           19 :     if (0 !=
     106           19 :         GNUNET_memcmp (&stored_nonce,
     107              :                        nonce))
     108              :     {
     109            4 :       GNUNET_JSON_parse_free (spec);
     110            4 :       return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
     111              :     }
     112           15 :     GNUNET_JSON_parse_free (spec);
     113           15 :     return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     114              :   }
     115              : 
     116           86 :   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           86 :       GNUNET_JSON_spec_timestamp ("timestamp",
     124              :                                   &timestamp),
     125           86 :       GNUNET_JSON_spec_end ()
     126              :     };
     127              : 
     128              :     /* see if we have this order in our table of unclaimed orders */
     129           86 :     qs = TALER_MERCHANTDB_get_order (TMH_db,
     130              :                                      instance_id,
     131              :                                      order_id,
     132              :                                      &order_ct,
     133              :                                      &unused,
     134              :                                      contract_terms);
     135           86 :     if (0 >= qs)
     136              :     {
     137            2 :       TALER_MERCHANTDB_rollback (TMH_db);
     138            2 :       return qs;
     139              :     }
     140           84 :     GNUNET_assert (NULL != *contract_terms);
     141           84 :     if (GNUNET_OK !=
     142           84 :         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            0 :       GNUNET_break (0);
     150            0 :       TALER_MERCHANTDB_rollback (TMH_db);
     151            0 :       json_decref (*contract_terms);
     152            0 :       *contract_terms = NULL;
     153            0 :       return GNUNET_DB_STATUS_HARD_ERROR;
     154              :     }
     155              : 
     156           84 :     GNUNET_assert (0 ==
     157              :                    json_object_set_new (
     158              :                      *contract_terms,
     159              :                      "nonce",
     160              :                      GNUNET_JSON_from_data_auto (nonce)));
     161           84 :     if (0 != GNUNET_memcmp_priv (&order_ct,
     162              :                                  claim_token))
     163              :     {
     164            0 :       TALER_MERCHANTDB_rollback (TMH_db);
     165            0 :       json_decref (*contract_terms);
     166            0 :       *contract_terms = NULL;
     167            0 :       return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
     168              :     }
     169           84 :     qs = TALER_MERCHANTDB_insert_contract_terms (TMH_db,
     170              :                                                  instance_id,
     171              :                                                  order_id,
     172              :                                                  *contract_terms,
     173              :                                                  &order_serial);
     174           84 :     if (0 >= qs)
     175              :     {
     176            0 :       TALER_MERCHANTDB_rollback (TMH_db);
     177            0 :       json_decref (*contract_terms);
     178            0 :       *contract_terms = NULL;
     179            0 :       return qs;
     180              :     }
     181              :     // FIXME: unify notifications? or do we need both?
     182           84 :     TMH_notify_order_change (TMH_lookup_instance (instance_id),
     183              :                              TMH_OSF_CLAIMED,
     184              :                              timestamp,
     185              :                              order_serial);
     186              :     {
     187           84 :       struct TMH_OrderPayEventP pay_eh = {
     188           84 :         .header.size = htons (sizeof (pay_eh)),
     189           84 :         .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_STATUS_CHANGED),
     190           84 :         .merchant_pub = hc->instance->merchant_pub
     191              :       };
     192              : 
     193           84 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     194              :                   "Notifying clients about status change of order %s\n",
     195              :                   order_id);
     196           84 :       GNUNET_CRYPTO_hash (order_id,
     197              :                           strlen (order_id),
     198              :                           &pay_eh.h_order_id);
     199           84 :       TALER_MERCHANTDB_event_notify (TMH_db,
     200              :                                      &pay_eh.header,
     201              :                                      NULL,
     202              :                                      0);
     203              :     }
     204           84 :     qs = TALER_MERCHANTDB_commit (TMH_db);
     205           84 :     if (0 > qs)
     206            0 :       return qs;
     207           84 :     return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     208              :   }
     209              : }
     210              : 
     211              : 
     212              : enum MHD_Result
     213           95 : TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh,
     214              :                           struct MHD_Connection *connection,
     215              :                           struct TMH_HandlerContext *hc)
     216              : {
     217           95 :   const char *order_id = hc->infix;
     218              :   struct GNUNET_CRYPTO_EddsaPublicKey nonce;
     219              :   enum GNUNET_DB_QueryStatus qs;
     220              :   json_t *contract_terms;
     221           95 :   struct TALER_ClaimTokenP claim_token = { 0 };
     222              : 
     223              :   {
     224              :     struct GNUNET_JSON_Specification spec[] = {
     225           95 :       GNUNET_JSON_spec_fixed_auto ("nonce",
     226              :                                    &nonce),
     227           95 :       GNUNET_JSON_spec_mark_optional (
     228              :         GNUNET_JSON_spec_fixed_auto ("token",
     229              :                                      &claim_token),
     230              :         NULL),
     231           95 :       GNUNET_JSON_spec_end ()
     232              :     };
     233              :     enum GNUNET_GenericReturnValue res;
     234              : 
     235           95 :     res = TALER_MHD_parse_json_data (connection,
     236           95 :                                      hc->request_body,
     237              :                                      spec);
     238           95 :     if (GNUNET_OK != res)
     239              :     {
     240            0 :       GNUNET_break_op (0);
     241            0 :       json_dumpf (hc->request_body,
     242              :                   stderr,
     243              :                   JSON_INDENT (2));
     244              :       return (GNUNET_NO == res)
     245              :              ? MHD_YES
     246            0 :              : MHD_NO;
     247              :     }
     248              :   }
     249           95 :   contract_terms = NULL;
     250           95 :   for (unsigned int i = 0; i<MAX_RETRIES; i++)
     251              :   {
     252           95 :     TALER_MERCHANTDB_preflight (TMH_db);
     253           95 :     qs = TMH_claim_order (hc,
     254              :                           order_id,
     255              :                           &nonce,
     256              :                           &claim_token,
     257              :                           &contract_terms);
     258           95 :     if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
     259           95 :       break;
     260              :   }
     261           95 :   switch (qs)
     262              :   {
     263            0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     264            0 :     return TALER_MHD_reply_with_error (connection,
     265              :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     266              :                                        TALER_EC_GENERIC_DB_COMMIT_FAILED,
     267              :                                        NULL);
     268            0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     269            0 :     return TALER_MHD_reply_with_error (connection,
     270              :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
     271              :                                        TALER_EC_GENERIC_DB_SOFT_FAILURE,
     272              :                                        NULL);
     273            4 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     274            4 :     if (NULL == contract_terms)
     275            2 :       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            2 :     json_decref (contract_terms);
     281            2 :     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           91 :   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     286           91 :     GNUNET_assert (NULL != contract_terms);
     287           91 :     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           91 :     if (GNUNET_OK !=
     301           91 :         TALER_JSON_contract_hash (contract_terms,
     302              :                                   &hash))
     303              :     {
     304            0 :       GNUNET_break (0);
     305            0 :       json_decref (contract_terms);
     306            0 :       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           91 :     TALER_merchant_contract_sign (&hash,
     313           91 :                                   &hc->instance->merchant_priv,
     314              :                                   &merchant_sig);
     315           91 :     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 */
        

Generated by: LCOV version 2.0-1