merchant

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

insert_transfer_details.c (8308B)


      1 /*
      2    This file is part of TALER
      3    Copyright (C) 2022, 2026 Taler Systems SA
      4 
      5    TALER is free software; you can redistribute it and/or modify it under the
      6    terms of the GNU General Public License as published by the Free Software
      7    Foundation; either version 3, or (at your option) any later version.
      8 
      9    TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11    A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13    You should have received a copy of the GNU General Public License along with
     14    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 /**
     17  * @file src/backenddb/insert_transfer_details.c
     18  * @brief Implementation of the insert_transfer_details function for Postgres
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include <taler/taler_pq_lib.h>
     23 #include <taler/taler_dbevents.h>
     24 #include "merchant-database/start.h"
     25 #include "merchant-database/insert_transfer_details.h"
     26 #include "helper.h"
     27 
     28 
     29 /**
     30  * How often do we re-try if we run into a DB serialization error?
     31  */
     32 #define MAX_RETRIES 3
     33 
     34 
     35 enum GNUNET_DB_QueryStatus
     36 TALER_MERCHANTDB_insert_transfer_details (
     37   struct TALER_MERCHANTDB_PostgresContext *pg,
     38   const char *instance_id,
     39   const char *exchange_url,
     40   struct TALER_FullPayto payto_uri,
     41   const struct TALER_WireTransferIdentifierRawP *wtid,
     42   const struct TALER_EXCHANGE_TransferData *td,
     43   unsigned int *num_settled_orders)
     44 {
     45   unsigned int len = td->details_length;
     46   struct TALER_Amount coin_values[GNUNET_NZL (len)];
     47   struct TALER_Amount deposit_fees[GNUNET_NZL (len)];
     48   const struct TALER_CoinSpendPublicKeyP *coin_pubs[GNUNET_NZL (len)];
     49   const struct TALER_PrivateContractHashP *contract_terms[GNUNET_NZL (len)];
     50   enum GNUNET_DB_QueryStatus qs;
     51   bool duplicate = false;
     52 
     53   if (NULL != num_settled_orders)
     54     *num_settled_orders = 0;
     55   GNUNET_assert (NULL != pg->current_merchant_id);
     56   GNUNET_assert (0 == strcmp (instance_id,
     57                               pg->current_merchant_id));
     58   if (0 == len)
     59   {
     60     /* Legal on the wire: the exchange may sign a transfer with an
     61        empty list of deposits. Nothing to associate with the transfer,
     62        but we still store the (signed) transfer details below. */
     63     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     64                 "Exchange `%s' reported wire transfer without any deposits\n",
     65                 exchange_url);
     66   }
     67 
     68   for (unsigned int i = 0; i<len; i++)
     69   {
     70     const struct TALER_TrackTransferDetails *tdd = &td->details[i];
     71 
     72     coin_values[i] = tdd->coin_value;
     73     deposit_fees[i] = tdd->coin_fee;
     74     coin_pubs[i] = &tdd->coin_pub;
     75     contract_terms[i] = &tdd->h_contract_terms;
     76   }
     77 
     78   // FIXME: why do we retry here, but not most other SQL statements?
     79   // We need a slightly more consistent strategy...
     80   for (unsigned int retries = 0;
     81        retries < MAX_RETRIES;
     82        retries++)
     83   {
     84     if (GNUNET_OK !=
     85         TALER_MERCHANTDB_start (pg,
     86                                 "insert transfer details"))
     87     {
     88       GNUNET_break (0);
     89       return GNUNET_DB_STATUS_HARD_ERROR;
     90     }
     91 
     92     {
     93       struct GNUNET_PQ_QueryParam params[] = {
     94         GNUNET_PQ_query_param_uint64 (&pg->current_merchant_serial),
     95         GNUNET_PQ_query_param_string (exchange_url),
     96         GNUNET_PQ_query_param_string (payto_uri.full_payto),
     97         GNUNET_PQ_query_param_auto_from_type (wtid),
     98         GNUNET_PQ_query_param_timestamp (&td->execution_time),
     99         GNUNET_PQ_query_param_auto_from_type (&td->exchange_pub),
    100         GNUNET_PQ_query_param_auto_from_type (&td->exchange_sig),
    101         TALER_PQ_query_param_amount_with_currency (pg->conn,
    102                                                    &td->total_amount),
    103         TALER_PQ_query_param_amount_with_currency (pg->conn,
    104                                                    &td->wire_fee),
    105         TALER_PQ_query_param_array_amount_with_currency (
    106           len,
    107           coin_values,
    108           "merchant",
    109           pg->conn),
    110         TALER_PQ_query_param_array_amount_with_currency (
    111           len,
    112           deposit_fees,
    113           "merchant",
    114           pg->conn),
    115         GNUNET_PQ_query_param_array_ptrs_auto_from_type (
    116           len,
    117           coin_pubs,
    118           pg->conn),
    119         GNUNET_PQ_query_param_array_ptrs_auto_from_type (
    120           len,
    121           contract_terms,
    122           pg->conn),
    123         GNUNET_PQ_query_param_end
    124       };
    125       bool no_account;
    126       bool no_exchange;
    127       bool conflict;
    128       size_t num_order_ids = 0;
    129       char *order_ids = NULL;
    130       struct GNUNET_PQ_ResultSpec rs[] = {
    131         GNUNET_PQ_result_spec_bool ("out_no_account",
    132                                     &no_account),
    133         GNUNET_PQ_result_spec_bool ("out_no_exchange",
    134                                     &no_exchange),
    135         GNUNET_PQ_result_spec_bool ("out_duplicate",
    136                                     &duplicate),
    137         GNUNET_PQ_result_spec_bool ("out_conflict",
    138                                     &conflict),
    139         GNUNET_PQ_result_spec_array_string (pg->conn,
    140                                             "out_order_ids",
    141                                             &num_order_ids,
    142                                             &order_ids),
    143         GNUNET_PQ_result_spec_end
    144       };
    145 
    146       TMH_PQ_prepare_anon (pg,
    147                            "SELECT"
    148                            " out_no_account"
    149                            ",out_no_exchange"
    150                            ",out_duplicate"
    151                            ",out_conflict"
    152                            ",out_order_ids"
    153                            " FROM merchant_do_insert_transfer_details"
    154                            " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13);");
    155       qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
    156                                                      "",
    157                                                      params,
    158                                                      rs);
    159       GNUNET_PQ_cleanup_query_params_closures (params);
    160       if (0 >= qs)
    161       {
    162         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    163         TALER_MERCHANTDB_rollback (pg);
    164         if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    165           continue;
    166         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    167                     "'insert_transfer_details' failed with status %d\n",
    168                     qs);
    169         return qs;
    170       }
    171       {
    172         /* One aggregated transfer can settle many orders; notify
    173            long-pollers about every single one of them. */
    174         const char *pos = order_ids;
    175 
    176         if (NULL != num_settled_orders)
    177           *num_settled_orders = (unsigned int) num_order_ids;
    178         for (size_t k = 0; k<num_order_ids; k++)
    179         {
    180           const char *order_id = pos;
    181           struct TMH_OrderPayEventP pay_eh = {
    182             .header.size = htons (sizeof (pay_eh)),
    183             .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_STATUS_CHANGED),
    184             .merchant_pub = pg->current_merchant_pub
    185           };
    186 
    187           pos += strlen (order_id) + 1;
    188           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    189                       "Notifying clients about status change of order %s\n",
    190                       order_id);
    191           GNUNET_CRYPTO_hash (order_id,
    192                               strlen (order_id),
    193                               &pay_eh.h_order_id);
    194           GNUNET_PQ_event_notify (pg->conn,
    195                                   &pay_eh.header,
    196                                   NULL,
    197                                   0);
    198         }
    199         GNUNET_free (order_ids);
    200       }
    201       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    202                   "Transfer details inserted: %s%s%s%s\n",
    203                   no_account ? "no account " : "",
    204                   no_exchange ? "no exchange ": "",
    205                   duplicate ? "duplicate ": "",
    206                   conflict ? "conflict" : "");
    207     }
    208 
    209     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    210                 "Committing transaction...\n");
    211     qs = TALER_MERCHANTDB_commit (pg);
    212     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    213       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    214     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    215     if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
    216       break;
    217   }
    218   if (duplicate)
    219     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    220   return qs;
    221 }