merchant

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

commit e28c2fc790a07dda17de2794ce2de8a5ee422529
parent 598f13b038827f45d61c6c1f9810e14ad286568d
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Sun, 22 Jun 2025 12:29:05 +0200

update to better match the current pay logic

Diffstat:
Msrc/backenddb/Makefile.am | 1+
Msrc/include/taler_merchant_pay_service.h | 2+-
Msrc/lib/taler_merchant_pay_service.c | 520+++++++++++++++++++++++++++++++++++++++++++------------------------------------
3 files changed, 286 insertions(+), 237 deletions(-)

diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am @@ -35,6 +35,7 @@ sql_DATA = \ merchant-0016.sql \ merchant-0017.sql \ merchant-0018.sql \ + merchant-0019.sql \ drop.sql BUILT_SOURCES = \ diff --git a/src/include/taler_merchant_pay_service.h b/src/include/taler_merchant_pay_service.h @@ -240,7 +240,7 @@ 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); +TALER_MERCHANT_order_pay_cancel1 (struct TALER_MERCHANT_OrderPayHandle *ph); #endif /* TALER_MERCHANT_PAY_SERVICE_H */ \ No newline at end of file diff --git a/src/lib/taler_merchant_pay_service.c b/src/lib/taler_merchant_pay_service.c @@ -14,10 +14,12 @@ 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 + * @file taler_merchant_pay_service.c + * @brief Implementation of the the ideology + * from the pay_service as copy of + * merchant_api_post_order_pay.c + * @author Bohdan Potuzhnyi */ - #include "platform.h" #include <curl/curl.h> #include <gnunet/gnunet_common.h> @@ -36,15 +38,21 @@ #include <taler/taler_exchange_service.h> #include <taler/taler_curl_lib.h> -/* ------------------------------------------------------------------------- */ -/* internal helpers */ -/* ------------------------------------------------------------------------- */ - -/** Opaque handle implementation. */ +/** + * @brief A Pay Handle + */ struct TALER_MERCHANT_OrderPayHandle { struct GNUNET_CURL_Context *ctx; + + /** + * Function to call with the result in "pay" @e mode. + */ TALER_MERCHANT_OrderPayCallback cb; + + /** + * Closure for @a pay_cb. + */ TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE *cb_cls; /* mandatory scalars: */ @@ -97,224 +105,16 @@ struct TALER_MERCHANT_OrderPayHandle bool field_seen[TALER_MERCHANT_OrderPayOptionType_LENGTH]; - /** - * Function to call with the result in "pay" @e mode. - */ - TALER_MERCHANT_OrderPayCallback pay_cb; - bool am_wallet; }; -/* 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_cancel1 (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: - { - ph->h_wire = o->details.h_wire; - ph->has_h_wire = true; - - 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 ============= */ - +/** + * Parse blindly signed output tokens from response. + * + * @param token_sigs the JSON array with the token signatures. Can be NULL. + * @param tokens where to store the parsed tokens. + * @param num_tokens where to store the length of the @a tokens array. + */ static enum GNUNET_GenericReturnValue parse_tokens (const json_t *token_sigs, struct TALER_MERCHANT_OutputToken **tokens, @@ -354,10 +154,18 @@ parse_tokens (const json_t *token_sigs, return GNUNET_YES; } +/** + * Function called when we're done processing the + * HTTP /pay request. + * + * @param cls the `struct TALER_MERCHANT_Pay` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ static void handle_finished (void *cls, - long response_code, - const void *resp) + long response_code, + const void *resp) { struct TALER_MERCHANT_OrderPayHandle *oph = cls; const json_t *json = resp; @@ -535,7 +343,7 @@ handle_finished (void *cls, = (unsigned int) alen; pr.details.unavailable_for_legal_reasons.exchanges = ebua; - oph->pay_cb (oph->cb_cls, + oph->cb (oph->cb_cls, &pr); TALER_MERCHANT_order_pay_cancel1 (oph); return; @@ -582,28 +390,266 @@ handle_finished (void *cls, GNUNET_break_op (0); break; } - oph->pay_cb (oph->cb_cls, + oph->cb (oph->cb_cls, &pr); TALER_MERCHANT_order_pay_cancel1 (oph); } +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_cancel1 (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); +} + +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: + { + ph->h_wire = o->details.h_wire; + ph->has_h_wire = true; + + 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; +} + + 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; + { + 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 (NULL == ph->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + json_decref (ph->body); + GNUNET_free (ph); + return TALER_MERCHANT_OPOEC_INVALID_VALUE; + } + +// ph->num_coins = num_coins; +// ph->coins = GNUNET_new_array (num_coins, +// struct TALER_MERCHANT_PaidCoin); +// GNUNET_memcpy (ph->coins, +// coins, +// num_coins * sizeof (struct TALER_MERCHANT_PaidCoin)); CURL *eh = TALER_MERCHANT_curl_easy_get_ (ph->url); - if (GNUNET_OK != TALER_curl_easy_post (&ph->post_ctx, eh, ph->body)) { + if (GNUNET_OK != + TALER_curl_easy_post (&ph->post_ctx, + eh, + ph->body)) + { + GNUNET_break (0); curl_easy_cleanup (eh); + json_decref (ph->body); + GNUNET_free (ph->url); + GNUNET_free (ph); + //TODO: Replace with proper error code return TALER_MERCHANT_OPOEC_INVALID_VALUE; } - - ph->job = GNUNET_CURL_job_add2 (ph->ctx, eh, ph->post_ctx.headers, - &handle_finished, ph); + json_decref (ph->body); + ph->job = GNUNET_CURL_job_add2 (ph->ctx, + eh, + ph->post_ctx.headers, + &handle_finished, + ph); return TALER_MERCHANT_OPOEC_OK; } @@ -731,5 +777,7 @@ TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph) ); } + + //TODO: Change the usage of this function to how it is in the api_post_order_pay.c in the order_pay_frontend return fire_request (ph); }