merchant

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

commit d1f0265a59c858eb03157fb83daf0a5bfebaf004
parent b63af4a1ddb829714189b34f56d0582f4a9ee54f
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Wed, 23 Apr 2025 14:24:10 +0200

playing around with pay function

Diffstat:
Msrc/include/Makefile.am | 1+
Asrc/include/cutted_from_service | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/include/taler_merchant_pay_service.h | 247+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/taler_merchant_service.h | 138-------------------------------------------------------------------------------
Msrc/lib/Makefile.am | 3++-
Asrc/lib/taler_merchant_pay_service.c | 469+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/testing/testing_api_cmd_pay_order.c | 4+---
7 files changed, 876 insertions(+), 142 deletions(-)

diff --git a/src/include/Makefile.am b/src/include/Makefile.am @@ -10,6 +10,7 @@ talerinclude_HEADERS = \ taler_merchantdb_plugin.h \ taler_merchant_util.h \ taler_merchant_service.h \ + taler_merchant_pay_service.h \ taler_merchant_testing_lib.h if HAVE_DONAU diff --git a/src/include/cutted_from_service b/src/include/cutted_from_service @@ -0,0 +1,155 @@ +///* PART FROM WHICH IDEAS FOR NEW IMPLEMENTATION HAVE BEEN DEFINED*/ +// +///* application *before* including this header should do: */ +//#define TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE struct PayState +// +// +//#ifndef TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE +//#define TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE void +//#endif +// +//struct TALER_MERCHANT_OrderPayHandle * +//TALER_MERCHANT_order_pay_create ( +// struct GNUNET_CURL_Context *ctx, +// TALER_MERCHANT_OrderPayCallback pay_cb, +// TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE *pay_cb_cls) +//{ +// ph->body = json_object (); // json_t * +//} +// +// +//struct TALER_MERCHANT_OrderPayOption +//{ +// enum TALER_MERCHANT_OrderPayOptionType +// { +// TALER_MERCHANT_OrderPayOptionType_END = 0, +// TALER_MERCHANT_OrderPayOptionType_MERCHANT_URL, +// TALER_MERCHANT_OrderPayOptionType_SESSION_ID, +// TALER_MERCHANT_OrderPayOptionType_H_CONTRACT, +// TALER_MERCHANT_OrderPayOptionType_CHOICE_INDEX, +// TALER_MERCHANT_OrderPayOptionType_AMOUNT, +// TALER_MERCHANT_OrderPayOptionType_MAX_FEE, +// TALER_MERCHANT_OrderPayOptionType_MERCHANT_PUB, +// TALER_MERCHANT_OrderPayOptionType_TIMESTAMP, +// TALER_MERCHANT_OrderPayOptionType_REFUND_DEADLINE, +// TALER_MERCHANT_OrderPayOptionType_PAY_DEADLINE, +// TALER_MERCHANT_OrderPayOptionType_H_WIRE, +// TALER_MERCHANT_OrderPayOptionType_ORDER_ID, +// TALER_MERCHANT_OrderPayOptionType_COINS, +// TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS, +// TALER_MERCHANT_OrderPayOptionType_OUTPUT_TOKENS +// } ot; +// +// union +// { +// const char *merchant_url; +// const char *session_id; +// const struct TALER_PrivateContractHashP *h_contract; +// int choice_index; +// struct TALER_Amount amount; +// struct TALER_Amount max_fee; +// struct TALER_MerchantPublicKeyP merchant_pub; +// struct GNUNET_TIME_Timestamp timestamp; +// struct GNUNET_TIME_Timestamp refund_deadline; +// struct GNUNET_TIME_Timestamp pay_deadline; +// struct TALER_MerchantWireHashP h_wire; +// const char *order_id; +// struct +// { +// unsigned int num_coins; +// const struct TALER_MERCHANT_PayCoin *coins; +// } coins; +// struct +// { +// unsigned int num_tokens; +// const struct TALER_MERCHANT_UseToken *tokens; +// } input_tokens; +// struct +// { +// unsigned int num_output_tokens, +// const struct TALER_MERCHANT_OutputToken *output_tokens; +// } output_tokens; +// } details; +//}; +// +///*FIXME: THIS PARTS NEEDS TO BE DEFINED CLEARLY and ideally in new file with previous struct*/ +//enum TALER_MERCHANT_OrderPayOptionErrorCode +//TALER_MERCHANT_order_pay_set_options ( +// struct TALER_MERCHANT_OrderPayHandle *ph, +// struct TALER_MERCHANT_OrderPayOption options[], +// size_t max_options); +// +//{ +// for (i-- not 0 - terminateor && i < max_options; i++) +// switch (options[i].type) +// case ... +// GNUNET_assert (0 == +// json_object_set_new (ph->body, +// "field", +// json_string ("foo"))); +// return OK; +// case ... +// GNUNET_assert (0 == +// json_object_set_new (ph->body, +// "field", +// json_bool (true))); +// +// return OK; +// case ... +// GNUNET_assert (0 == +// json_object_set_new (ph->body, +// "wire_hash", +// GNUNET_JSON_PACK ( +// GNUNET_JSON_pack_data_auto (NULL, +// &options[i +// ].details. +// h_wire)))) +// return OK; +//} +// +//} +//return ERROR_UNKNOWN_OPTION; +//} +// +//#define TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE() \ +// (const struct TALER_MERCHANT_OrderPayOption) { \ +// .ot = TALER_MERCHANT_OrderPayOptionType_END \ +// } +// +// +//#define TALER_MERCHANT_ORDER_PAY_OPTION_SESSION_ID(session_id) \ +// (const struct TALER_MERCHANT_OrderPayOption) { \ +// .ot = TALER_MERCHANT_OrderPayOptionType_SESSION_ID, \ +// .details.session_id = session_id \ +// } +// +//#define TALER_MERCHANT_ORDER_PAY_SET_OPTIONS(ph,...) \ +// MHD_NOWARN_COMPOUND_LITERALS_ \ +// TALER_MERCHANT_order_pay_set_options ( \ +// daemon, \ +// ((const struct TALER_MERCHANT_OrderPayHandle[]) \ +// {__VA_ARGS__, TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE ()}), \ +// MHD_OPTIONS_ARRAY_MAX_SIZE) \ +// +///* END OF THE PART THAT NEEDS TO BE EXTENDED */ +// +//enum TALER_MERCHANT_OrderPayOptionErrorCode +//TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph); + +//#if APP /*SAMPLE OF USAGE OF PREVIOUS DEFINITIONS*/ +//{ +// struct TALER_MERCHANT_OrderPayHandle *ph; +// +// ph = TALER_MERCHANT_order_pay_create (struct GNUNET_CURL_Context *ctx, +// TALER_MERCHANT_OrderPayCallback pay_cb, +// pay_cb_cls); +// +// GNUNET_assert (OK == +// TALER_MERCHANT_ORDER_PAY_SET_OPTIONS ( +// ph, +// TALER_MERCHANT_ORDER_PAY_OPTION_SESSION_ID ("session"))); +// pay_start (ph); +//} +//#endif + +/* END OF LIST OF IDEAS */ +\ No newline at end of file diff --git a/src/include/taler_merchant_pay_service.h b/src/include/taler_merchant_pay_service.h @@ -0,0 +1,246 @@ +/* + This file is part of TALER + Copyright (C) 2014-2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING.LIB. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler_merchant_pay_service.h + * @brief Payment‑specific interface extracted from taler_merchant_service.h + * **Only** declarations and helper‑macros live here – the actual + * implementation must be provided in the accompanying *.c file. + * This header can be included by both merchant front‑ends and wallets + * that want to create or proxy /orders/$ID/pay requests. + */ +#ifndef TALER_MERCHANT_PAY_SERVICE_H +#define TALER_MERCHANT_PAY_SERVICE_H + +#include <taler/taler_util.h> +#include <taler/taler_signatures.h> +#include <taler/taler_error_codes.h> +#include <taler/taler_exchange_service.h> +#include "taler_merchant_service.h" +#include <gnunet/gnunet_time_lib.h> + + +/** + * seems a bit odd + */ +// #define TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE struct PayState + +#ifndef TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE +#define TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE void +#endif + +/** + * Opaque handle returned by the payment helper APIs declared below. + */ +struct TALER_MERCHANT_OrderPayHandle; + +/** + * Which option is being supplied to #TALER_MERCHANT_order_pay_set_options(). + */ +enum TALER_MERCHANT_OrderPayOptionType +{ + TALER_MERCHANT_OrderPayOptionType_END = 0, + TALER_MERCHANT_OrderPayOptionType_MERCHANT_URL, + TALER_MERCHANT_OrderPayOptionType_SESSION_ID, + TALER_MERCHANT_OrderPayOptionType_H_CONTRACT, + TALER_MERCHANT_OrderPayOptionType_CHOICE_INDEX, + TALER_MERCHANT_OrderPayOptionType_AMOUNT, + TALER_MERCHANT_OrderPayOptionType_MAX_FEE, + TALER_MERCHANT_OrderPayOptionType_MERCHANT_PUB, + TALER_MERCHANT_OrderPayOptionType_TIMESTAMP, + TALER_MERCHANT_OrderPayOptionType_REFUND_DEADLINE, + TALER_MERCHANT_OrderPayOptionType_PAY_DEADLINE, + TALER_MERCHANT_OrderPayOptionType_H_WIRE, + TALER_MERCHANT_OrderPayOptionType_ORDER_ID, + TALER_MERCHANT_OrderPayOptionType_COINS, + TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS, + TALER_MERCHANT_OrderPayOptionType_OUTPUT_TOKENS, + /*New objects go in here*/ + TALER_MERCHANT_OrderPayOptionType_LENGTH +}; + +/** + * Container describing *one* option for a payment request. + */ +struct TALER_MERCHANT_OrderPayOption +{ + enum TALER_MERCHANT_OrderPayOptionType ot; /**< Which field inside the union is valid */ + union + { + const char *merchant_url; + const char *session_id; + const struct TALER_PrivateContractHashP *h_contract; + int choice_index; + struct TALER_Amount amount; + struct TALER_Amount max_fee; + struct TALER_MerchantPublicKeyP merchant_pub; + struct GNUNET_TIME_Timestamp timestamp; + struct GNUNET_TIME_Timestamp refund_deadline; + struct GNUNET_TIME_Timestamp pay_deadline; + struct TALER_MerchantWireHashP h_wire; + const char *order_id; + struct { + unsigned int num_coins; + const struct TALER_MERCHANT_PayCoin *coins; + } coins; + struct { + unsigned int num_tokens; + const struct TALER_MERCHANT_UseToken *tokens; + } input_tokens; + struct { + unsigned int num_output_tokens; + const struct TALER_MERCHANT_OutputToken *output_tokens; + } output_tokens; + } details; +}; + + +/** + * Status codes returned from #TALER_MERCHANT_order_pay_set_options() and + * #TALER_MERCHANT_order_pay_start(). + */ +enum TALER_MERCHANT_OrderPayOptionErrorCode +{ + TALER_MERCHANT_OPOEC_OK = 0, /**< everything fine */ + TALER_MERCHANT_OPOEC_UNKNOWN_OPTION, /**< unrecognised option kind */ + TALER_MERCHANT_OPOEC_DUPLICATE_OPTION, /**< option given more than once */ + TALER_MERCHANT_OPOEC_INVALID_VALUE, /**< semantic/format error in value */ + TALER_MERCHANT_OPOEC_MISSING_MANDATORY /**< required field missing at start */ +}; + +#define TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE() \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_END \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_MERCHANT_URL(_url) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_MERCHANT_URL, \ + .details.merchant_url = (_url) \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_SESSION_ID(_sid) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_SESSION_ID,\ + .details.session_id = (_sid) \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_H_CONTRACT(_h) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_H_CONTRACT, \ + .details.h_contract = (_h) \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_CHOICE_INDEX(_idx) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_CHOICE_INDEX,\ + .details.choice_index = (_idx) \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_AMOUNT(_amt) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_AMOUNT,\ + .details.amount = *(_amt) \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_MAX_FEE(_fee) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_MAX_FEE,\ + .details.max_fee = *(_fee) \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_MERCHANT_PUB(_mpub) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_MERCHANT_PUB, \ + .details.merchant_pub = *(_mpub) \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_TIMESTAMP(_ts) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_TIMESTAMP, \ + .details.timestamp = (_ts) \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_REFUND_DEADLINE(_ts) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_REFUND_DEADLINE, \ + .details.refund_deadline = (_ts) \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_PAY_DEADLINE(_ts) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_PAY_DEADLINE, \ + .details.pay_deadline = (_ts) \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_H_WIRE(_hwire) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_H_WIRE, \ + .details.h_wire = *(_hwire) \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_ORDER_ID(_oid) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_ORDER_ID,\ + .details.order_id = (_oid) \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_COINS(_num,_coins) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_COINS, \ + .details.coins = { .num_coins = (_num), \ + .coins = (_coins) } \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_INPUT_TOKENS(_num,_tokens) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS, \ + .details.input_tokens = { .num_tokens = (_num), \ + .tokens = (_tokens) } \ + } + +#define TALER_MERCHANT_ORDER_PAY_OPTION_OUTPUT_TOKENS(_num,_otokens) \ + (struct TALER_MERCHANT_OrderPayOption){ \ + .ot = TALER_MERCHANT_OrderPayOptionType_OUTPUT_TOKENS, \ + .details.output_tokens = { .num_output_tokens = (_num), \ + .output_tokens = (_otokens) } \ + } + +#define TALER_MERCHANT_ORDER_PAY_SET_OPTIONS(ph,...) \ + MHD_NOWARN_COMPOUND_LITERALS_ \ + TALER_MERCHANT_order_pay_set_options ( \ + daemon, \ + ((const struct TALER_MERCHANT_OrderPayHandle[]) \ + {__VA_ARGS__, TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE ()}), \ + MHD_OPTIONS_ARRAY_MAX_SIZE) \ + +struct TALER_MERCHANT_OrderPayHandle * +TALER_MERCHANT_order_pay_create (struct GNUNET_CURL_Context *ctx, + TALER_MERCHANT_OrderPayCallback pay_cb, + TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE *pay_cb_cls); + +enum TALER_MERCHANT_OrderPayOptionErrorCode +TALER_MERCHANT_order_pay_set_options (struct TALER_MERCHANT_OrderPayHandle *ph, + const struct TALER_MERCHANT_OrderPayOption options[], + size_t max_options); + +enum TALER_MERCHANT_OrderPayOptionErrorCode +TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph); + +void +TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *ph); + + +#endif /* TALER_MERCHANT_PAY_SERVICE_H */ +\ No newline at end of file diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h @@ -3566,144 +3566,6 @@ TALER_MERCHANT_order_pay ( TALER_MERCHANT_OrderPayCallback pay_cb, void *pay_cb_cls); -/* application *before* including this header should do: */ -#define TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE struct PayState - - -#ifndef TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE -#define TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE void -#endif - -struct TALER_MERCHANT_OrderPayHandle * -TALER_MERCHANT_order_pay_create ( - struct GNUNET_CURL_Context *ctx, - TALER_MERCHANT_OrderPayCallback pay_cb, - TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE *pay_cb_cls) -{ - ph->body = json_object (); // json_t * -} - - -struct TALER_MERCHANT_OrderPayOption -{ - enum TALER_MERCHANT_OrderPayOptionType - { - TALER_MERCHANT_OrderPayOptionType_END = 0, - TALER_MERCHANT_OrderPayOptionType_MERCHANT_URL, - TALER_MERCHANT_OrderPayOptionType_SESSION_ID, - TALER_MERCHANT_OrderPayOptionType_H_CONTRACT, - TALER_MERCHANT_OrderPayOptionType_XXX, - } ot; - - union - { - const char *merchant_url; - const char *session_id; - const struct TALER_PrivateContractHashP *h_contract; - int choice_index; - struct TALER_Amount amount; - struct TALER_Amount max_fee; - struct TALER_MerchantPublicKeyP merchant_pub; - struct GNUNET_TIME_Timestamp timestamp; - struct GNUNET_TIME_Timestamp refund_deadline; - struct GNUNET_TIME_Timestamp pay_deadline; - struct TALER_MerchantWireHashP h_wire; - const char *order_id; - struct - { - unsigned int num_coins; - const struct TALER_MERCHANT_PayCoin *coins; - } coins; - struct - { - unsigned int num_tokens; - const struct TALER_MERCHANT_UseToken *tokens; - } input_tokens; - struct - { - unsigned int num_output_tokens, - const struct TALER_MERCHANT_OutputToken *output_tokens; - } output_tokens; - } details; -}; - -enum TALER_MERCHANT_OrderPayOptionErrorCode -TALER_MERCHANT_order_pay_set_options ( - struct TALER_MERCHANT_OrderPayHandle *ph, - struct TALER_MERCHANT_OrderPayOption options[], - size_t max_options); - -{ - for (i-- not 0 - terminateor && i < max_options; i++) - switch (options[i].type) - case ... - GNUNET_assert (0 == - json_object_set_new (ph->body, - "field", - json_string ("foo"))); - return OK; - case ... - GNUNET_assert (0 == - json_object_set_new (ph->body, - "field", - json_bool (true))); - - return OK; - case ... - GNUNET_assert (0 == - json_object_set_new (ph->body, - "wire_hash", - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto (NULL, - &options[i - ].details. - h_wire)))) - return OK; -} - -} -return ERROR_UNKNOWN_OPTION; -} - -#define TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE() \ - (const struct TALER_MERCHANT_OrderPayOption) { \ - .ot = TALER_MERCHANT_OrderPayOptionType_END \ - } - - -#define TALER_MERCHANT_ORDER_PAY_OPTION_SESSION_ID(session_id) \ - (const struct TALER_MERCHANT_OrderPayOption) { \ - .ot = TALER_MERCHANT_OrderPayOptionType_SESSION_ID, \ - .details.session_id = session_id \ - } - -#define TALER_MERCHANT_ORDER_PAY_SET_OPTIONS(ph,...) \ - MHD_NOWARN_COMPOUND_LITERALS_ \ - TALER_MERCHANT_order_pay_set_options ( \ - daemon, \ - ((const struct TALER_MERCHANT_OrderPayHandle[]) \ - {__VA_ARGS__, TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE ()}), \ - MHD_OPTIONS_ARRAY_MAX_SIZE) \ - -enum TALER_MERCHANT_OrderPayOptionErrorCode -TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph); - -#if APP -{ - struct TALER_MERCHANT_OrderPayHandle *ph; - - ph = TALER_MERCHANT_order_pay_create (struct GNUNET_CURL_Context *ctx, - TALER_MERCHANT_OrderPayCallback pay_cb, - pay_cb_cls); - - GNUNET_assert (OK == - TALER_MERCHANT_ORDER_PAY_SET_OPTIONS ( - ph, - TALER_MERCHANT_ORDER_PAY_OPTION_SESSION_ID ("session"))); - pay_start (ph); -} -#endif - /** * Cancel a POST /orders/$ID/pay request. Note that if you cancel a request * like this, you have no assurance that the request has not yet been diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am @@ -67,7 +67,8 @@ libtalermerchant_la_SOURCES = \ merchant_api_post_webhooks.c \ merchant_api_wallet_get_order.c \ merchant_api_wallet_get_template.c \ - merchant_api_wallet_post_order_refund.c + merchant_api_wallet_post_order_refund.c \ + taler_merchant_pay_service.c libtalermerchant_la_LIBADD = \ -ltalerexchange \ diff --git a/src/lib/taler_merchant_pay_service.c b/src/lib/taler_merchant_pay_service.c @@ -0,0 +1,469 @@ +/* + This file is part of TALER + Copyright (C) 2014-2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING.LIB. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler_merchant_pay_service.h + * @brief Implementation of the the ideology from the pay_service + */ + +#include "platform.h" +#include <curl/curl.h> +#include <gnunet/gnunet_common.h> +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "taler_merchant_pay_service.h" +#include "merchant_api_common.h" +#include "merchant_api_curl_defaults.h" +#include <stdio.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> +#include <taler/taler_exchange_service.h> +#include <taler/taler_curl_lib.h> + +/* ------------------------------------------------------------------------- */ +/* internal helpers */ +/* ------------------------------------------------------------------------- */ + +/** Opaque handle implementation. */ +struct TALER_MERCHANT_OrderPayHandle +{ + struct GNUNET_CURL_Context *ctx; + TALER_MERCHANT_OrderPayCallback cb; + TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE *cb_cls; + + /* mandatory scalars: */ + char *merchant_url; + char *order_id; + + struct GNUNET_TIME_Timestamp timestamp; + struct GNUNET_TIME_Timestamp refund_deadline; + + /* for wallet mode: */ + bool has_h_contract; + struct TALER_PrivateContractHashP h_contract_terms; + + bool has_merchant_pub; + struct TALER_MerchantPublicKeyP merchant_pub; + + bool has_choice_index; + int choice_index; + + /* raw arrays as passed in via set_options(): */ + struct { + unsigned int num_coins; + const struct TALER_MERCHANT_PayCoin *coins; + } coins; + + struct { + unsigned int num_tokens; + const struct TALER_MERCHANT_UseToken *tokens; + } input_tokens; + + struct { + unsigned int num_output_tokens; + const struct TALER_MERCHANT_OutputToken *output_tokens; + } output_tokens; + + /* computed once we see both choice_index and token_evs(outputs in the env): */ + json_t *wallet_data; + struct GNUNET_HashCode wallet_data_hash; + + /* the JSON body we’ll keep appending into… */ + json_t *body; + + /* final URL and CURL plumbing */ + char *url; + struct TALER_CURL_PostContext post_ctx; + struct GNUNET_CURL_Job *job; + + bool field_seen[TALER_MERCHANT_OrderPayOptionType_LENGTH]; +}; + +/* create / destroy */ + +struct TALER_MERCHANT_OrderPayHandle * +TALER_MERCHANT_order_pay_create (struct GNUNET_CURL_Context *ctx, + TALER_MERCHANT_OrderPayCallback cb, + TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE *cb_cls) +{ + struct TALER_MERCHANT_OrderPayHandle *ph = + GNUNET_new (struct TALER_MERCHANT_OrderPayHandle); + ph->ctx = ctx; + ph->cb = cb; + ph->cb_cls = cb_cls; + ph->body = json_object (); + GNUNET_assert (ph->body); + return ph; +} + +void +TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *ph) +{ + if (ph->job) + GNUNET_CURL_job_cancel (ph->job); + TALER_curl_easy_post_finished (&ph->post_ctx); + json_decref (ph->body); + if (ph->wallet_data) json_decref (ph->wallet_data); + GNUNET_free (ph->url); + GNUNET_free (ph->merchant_url); + GNUNET_free (ph->order_id); + GNUNET_free (ph); +} + +/* option setter */ + +static enum TALER_MERCHANT_OrderPayOptionErrorCode +store_json_option (struct TALER_MERCHANT_OrderPayHandle *ph, + enum TALER_MERCHANT_OrderPayOptionType ot, + json_t *snippet) +{ + if (ph->field_seen[ot]) { + json_decref (snippet); + return TALER_MERCHANT_OPOEC_DUPLICATE_OPTION; + } + ph->field_seen[ot] = true; + GNUNET_assert (0 == json_object_update (ph->body, snippet)); + json_decref (snippet); + return TALER_MERCHANT_OPOEC_OK; +} + +enum TALER_MERCHANT_OrderPayOptionErrorCode +TALER_MERCHANT_order_pay_set_options (struct TALER_MERCHANT_OrderPayHandle *ph, + const struct TALER_MERCHANT_OrderPayOption options[], + size_t max_options) +{ + for (size_t i = 0; i < max_options + && options[i].ot != TALER_MERCHANT_OrderPayOptionType_END; i++) + { + const struct TALER_MERCHANT_OrderPayOption *o = &options[i]; + switch (o->ot) + { + case TALER_MERCHANT_OrderPayOptionType_MERCHANT_URL: + ph->merchant_url = GNUNET_strdup (o->details.merchant_url); + break; + + case TALER_MERCHANT_OrderPayOptionType_ORDER_ID: + ph->order_id = GNUNET_strdup (o->details.order_id); + break; + + case TALER_MERCHANT_OrderPayOptionType_H_CONTRACT: + ph->h_contract_terms = *o->details.h_contract; + ph->has_h_contract = true; + { + json_t *js = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("h_contract", o->details.h_contract) + ); + return store_json_option (ph, o->ot, js); + } + + case TALER_MERCHANT_OrderPayOptionType_CHOICE_INDEX: + ph->choice_index = o->details.choice_index; + ph->has_choice_index= true; + { + json_t *js = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_int64 ("choice_index", + o->details.choice_index) + ); + return store_json_option (ph, o->ot, js); + } + + case TALER_MERCHANT_OrderPayOptionType_AMOUNT: + { + json_t *js = GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("amount", + &o->details.amount) + ); + return store_json_option (ph, o->ot, js); + } + + case TALER_MERCHANT_OrderPayOptionType_MAX_FEE: + { + json_t *js = GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("max_fee", + &o->details.max_fee) + ); + return store_json_option (ph, o->ot, js); + } + + case TALER_MERCHANT_OrderPayOptionType_MERCHANT_PUB: + ph->merchant_pub = o->details.merchant_pub; + ph->has_merchant_pub = true; + { + json_t *js = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("merchant_pub", + &o->details.merchant_pub) + ); + return store_json_option (ph, o->ot, js); + } + + case TALER_MERCHANT_OrderPayOptionType_TIMESTAMP: + { + ph->timestamp = o->details.timestamp; + + json_t *js = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_timestamp ("timestamp", + o->details.timestamp) + ); + return store_json_option (ph, o->ot, js); + } + + case TALER_MERCHANT_OrderPayOptionType_REFUND_DEADLINE: + { + ph->refund_deadline = o->details.refund_deadline; + + //Do we really need to pack it? + json_t *js = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_timestamp ("refund_deadline", + o->details.refund_deadline) + ); + return store_json_option (ph, o->ot, js); + } + + case TALER_MERCHANT_OrderPayOptionType_PAY_DEADLINE: + { + + + json_t *js = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_timestamp ("pay_deadline", + o->details.pay_deadline) + ); + return store_json_option (ph, o->ot, js); + } + + case TALER_MERCHANT_OrderPayOptionType_H_WIRE: + { + json_t *js = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("h_wire", + &o->details.h_wire) + ); + return store_json_option (ph, o->ot, js); + } + + case TALER_MERCHANT_OrderPayOptionType_COINS: + /* stash for later signing */ + GNUNET_assert (o->details.coins.num_coins > 0); + ph->coins.num_coins = o->details.coins.num_coins; + ph->coins.coins = o->details.coins.coins; + ph->field_seen[o->ot] = true; + break; + + case TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS: + /* stash for later signing */ + ph->input_tokens.num_tokens = o->details.input_tokens.num_tokens; + ph->input_tokens.tokens = o->details.input_tokens.tokens; + ph->field_seen[o->ot] = true; + break; + + case TALER_MERCHANT_OrderPayOptionType_OUTPUT_TOKENS: + /* store JSON array directly, *and* stash for hash */ + ph->output_tokens.num_output_tokens + = o->details.output_tokens.num_output_tokens; + ph->output_tokens.output_tokens + = o->details.output_tokens.output_tokens; + { + /* build and store token_evs */ + json_t *arr = json_array (); + for (unsigned j = 0; j < ph->output_tokens.num_output_tokens; j++) { + const struct TALER_MERCHANT_OutputToken *otk = &ph->output_tokens.output_tokens[j]; + json_t *je = GNUNET_JSON_PACK ( + TALER_JSON_pack_token_envelope (NULL, &otk->envelope) + ); + json_array_append_new (arr, je); + } + json_t *js = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("token_evs", arr) + ); + store_json_option (ph, o->ot, js); + } + break; + + default: + return TALER_MERCHANT_OPOEC_UNKNOWN_OPTION; + } + } + return TALER_MERCHANT_OPOEC_OK; +} + +/* ============= network submission ============= */ + +static void +handle_finished (void *cls, long hc, const void *resp) +{ + struct TALER_MERCHANT_OrderPayHandle *ph = cls; + struct TALER_MERCHANT_PayResponse pr = { + .hr.http_status = (unsigned int) hc, + .hr.reply = resp ? (const json_t *)resp : NULL, + .hr.ec = (resp && json_is_object (resp)) + ? TALER_JSON_get_error_code ((const json_t *)resp) + : TALER_EC_NONE, + .hr.hint = (resp && json_is_object (resp)) + ? TALER_JSON_get_error_hint ((const json_t *)resp) + : NULL + }; + + ph->job = NULL; + ph->cb (ph->cb_cls, &pr); + TALER_MERCHANT_order_pay_cancel (ph); +} + +static enum TALER_MERCHANT_OrderPayOptionErrorCode +fire_request (struct TALER_MERCHANT_OrderPayHandle *ph) +{ + char *path; + GNUNET_asprintf (&path, "orders/%s/pay", ph->order_id); + ph->url = TALER_url_join (ph->merchant_url, path, NULL); + GNUNET_free (path); + if (!ph->url) return TALER_MERCHANT_OPOEC_INVALID_VALUE; + + CURL *eh = TALER_MERCHANT_curl_easy_get_ (ph->url); + if (GNUNET_OK != TALER_curl_easy_post (&ph->post_ctx, eh, ph->body)) { + curl_easy_cleanup (eh); + return TALER_MERCHANT_OPOEC_INVALID_VALUE; + } + + ph->job = GNUNET_CURL_job_add2 (ph->ctx, eh, ph->post_ctx.headers, + &handle_finished, ph); + return TALER_MERCHANT_OPOEC_OK; +} + +enum TALER_MERCHANT_OrderPayOptionErrorCode +TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph) +{ + /* all the old mandatory checks */ + if (!ph->merchant_url || !ph->order_id) + return TALER_MERCHANT_OPOEC_MISSING_MANDATORY; + if (!ph->field_seen[TALER_MERCHANT_OrderPayOptionType_COINS]) + return TALER_MERCHANT_OPOEC_MISSING_MANDATORY; + if (!ph->field_seen[TALER_MERCHANT_OrderPayOptionType_REFUND_DEADLINE]) + return TALER_MERCHANT_OPOEC_MISSING_MANDATORY; + + /* --- build wallet_data hash for signing coins & tokens --- */ + if (ph->has_choice_index) { + /* wallet_data = { choice_index: …, token_evs: … } */ + ph->wallet_data = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_int64("choice_index", ph->choice_index), + GNUNET_JSON_pack_allow_null( + GNUNET_JSON_pack_array_incref("token_evs", ph->body)) + ); + TALER_json_hash (ph->wallet_data, &ph->wallet_data_hash); + } + + /* --- sign & pack coins into paid_coins array in body --- */ + { + struct TALER_MERCHANT_PaidCoin pc[ph->coins.num_coins]; + for (unsigned i = 0; i < ph->coins.num_coins; i++) { + + const struct TALER_MERCHANT_PayCoin *coin = &ph->coins.coins[i]; + struct TALER_MERCHANT_PaidCoin *p = &pc[i]; + struct TALER_Amount fee; + + if (0 > + TALER_amount_subtract (&fee, + &coin->amount_with_fee, + &coin->amount_without_fee)) + { + /* Integer underflow, fee larger than total amount? + This should not happen (client violated API!) */ + GNUNET_break (0); + return TALER_MERCHANT_OPOEC_INVALID_VALUE; + } + + TALER_denom_pub_hash ( &coin->denom_pub, + &p->denom_pub); + TALER_wallet_deposit_sign ( &coin->amount_with_fee, + &fee, + &ph->h_wire, + &ph->h_contract_terms, /* contract */ + ph->has_choice_index + ? &ph->wallet_data_hash + : NULL, + coin->h_age_commitment, + NULL, /* extensions */ + &p->denom_pub, + ph->timestamp, + ph->refund_deadline, + &ph->merchant_pub, + &coin->coin_priv, + &p->coin_sig); + + p->denom_pub = coin->denom_pub; + p->denom_sig = coin->denom_sig; + p->denom_value = coin->denom_value; + GNUNET_CRYPTO_eddsa_key_get_public (&coin->coin_priv.eddsa_priv, + &p->coin_pub.eddsa_pub); + p->amount_with_fee = coin->amount_with_fee; + p->amount_without_fee = coin->amount_without_fee; + p->exchange_url = coin->exchange_url; + } + + /* now JSON-pack paid_coins[] into request body */ + json_t *arr = json_array (); + for (unsigned i = 0; i < ph->coins.num_coins; i++) { + struct TALER_MERCHANT_PaidCoin *p = &pc[i]; + json_t *je = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("coin_sig", &p->coin_sig), + GNUNET_JSON_pack_data_auto ("coin_pub", &p->coin_pub), + TALER_JSON_pack_amount ("amount_with_fee", &p->amount_with_fee), + TALER_JSON_pack_amount ("amount_without_fee",&p->amount_without_fee), + GNUNET_JSON_pack_string ("exchange_url", p->exchange_url) + ); + json_array_append_new (arr, je); + } + store_json_option (ph, + TALER_MERCHANT_OrderPayOptionType_COINS, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("paid_coins", arr) + ) + ); + } + + /* --- sign & pack input_tokens into used_tokens array in body --- */ + if (ph->input_tokens.num_tokens > 0) { + struct TALER_MERCHANT_UsedToken ut[ph->input_tokens.num_tokens]; + for (unsigned i = 0; i < ph->input_tokens.num_tokens; i++) { + const struct TALER_MERCHANT_UseToken *in = &ph->input_tokens.tokens[i]; + struct TALER_MERCHANT_UsedToken *t = &ut[i]; + TALER_wallet_token_use_sign (&ph->h_contract_terms, + &ph->wallet_data_hash, + &in->token_priv, + &t->token_sig); + t->ub_sig = in->ub_sig; + t->issue_pub = in->issue_pub; + } + + json_t *arr = json_array (); + for (unsigned i = 0; i < ph->input_tokens.num_tokens; i++) { + struct TALER_MERCHANT_UsedToken *t = &ut[i]; + json_t *je = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("token_sig", &t->token_sig), + GNUNET_JSON_pack_data_auto ("ub_sig", &t->ub_sig), + GNUNET_JSON_pack_data_auto ("issue_pub", &t->issue_pub) + ); + json_array_append_new (arr, je); + } + store_json_option (ph, + TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("used_tokens", arr) + ) + ); + } + + return fire_request (ph); +} diff --git a/src/testing/testing_api_cmd_pay_order.c b/src/testing/testing_api_cmd_pay_order.c @@ -1447,9 +1447,7 @@ TALER_TESTING_cmd_merchant_pay_order_donau (const char *label, { crypto_hash_sha512_state state; size_t tax_length = strlen (donor_tax_id); - unsigned int salt_length; - for (salt_length = 0; salt[salt_length]!= '\0'; ++salt_length) - ; + size_t salt_length = strlen (salt); crypto_hash_sha512_init (&state); crypto_hash_sha512_update (&state,