merchant

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

commit 6be0a920e35cea0dbbaf35fbc650321a3a87f6c1
parent d1f0265a59c858eb03157fb83daf0a5bfebaf004
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Wed, 23 Apr 2025 19:12:13 +0200

first part done, to be tested

Diffstat:
Msrc/lib/taler_merchant_pay_service.c | 300++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 283 insertions(+), 17 deletions(-)

diff --git a/src/lib/taler_merchant_pay_service.c b/src/lib/taler_merchant_pay_service.c @@ -54,6 +54,9 @@ struct TALER_MERCHANT_OrderPayHandle struct GNUNET_TIME_Timestamp timestamp; struct GNUNET_TIME_Timestamp refund_deadline; + struct TALER_MerchantWireHashP h_wire; + bool has_h_wire; + /* for wallet mode: */ bool has_h_contract; struct TALER_PrivateContractHashP h_contract_terms; @@ -93,6 +96,13 @@ struct TALER_MERCHANT_OrderPayHandle struct GNUNET_CURL_Job *job; 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 */ @@ -113,7 +123,7 @@ TALER_MERCHANT_order_pay_create (struct GNUNET_CURL_Context *ctx, } void -TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *ph) +TALER_MERCHANT_order_pay_cancel1 (struct TALER_MERCHANT_OrderPayHandle *ph) { if (ph->job) GNUNET_CURL_job_cancel (ph->job); @@ -248,6 +258,9 @@ TALER_MERCHANT_order_pay_set_options (struct TALER_MERCHANT_OrderPayHandle *ph, 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) @@ -302,24 +315,276 @@ TALER_MERCHANT_order_pay_set_options (struct TALER_MERCHANT_OrderPayHandle *ph, /* ============= network submission ============= */ +static enum GNUNET_GenericReturnValue +parse_tokens (const json_t *token_sigs, + struct TALER_MERCHANT_OutputToken **tokens, + unsigned int *num_tokens) +{ + GNUNET_array_grow (*tokens, + *num_tokens, + json_array_size (token_sigs)); + + for (unsigned int i = 0; i<(*num_tokens); i++) + { + struct TALER_MERCHANT_OutputToken *token = &(*tokens)[i]; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_blinded_token_issue_sig ("blind_sig", + &token->blinded_sig), + GNUNET_JSON_spec_end () + }; + const json_t *jtoken + = json_array_get (token_sigs, + i); + + if (NULL == jtoken) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_JSON_parse (jtoken, + spec, + NULL, NULL)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + + return GNUNET_YES; +} + static void -handle_finished (void *cls, long hc, const void *resp) +handle_finished (void *cls, + long response_code, + const void *resp) { - struct TALER_MERCHANT_OrderPayHandle *ph = cls; + struct TALER_MERCHANT_OrderPayHandle *oph = cls; + const json_t *json = resp; 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 + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; - ph->job = NULL; - ph->cb (ph->cb_cls, &pr); - TALER_MERCHANT_order_pay_cancel (ph); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received /pay response with status code %u\n", + (unsigned int) response_code); + + json_dumpf (json, + stderr, + JSON_INDENT (2)); + + oph->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "/pay completed with response code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case 0: + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (oph->am_wallet) + { + const json_t *token_sigs = NULL; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("sig", + &pr.details.ok.merchant_sig), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("pos_confirmation", + &pr.details.ok.pos_confirmation), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("token_sigs", + &token_sigs), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.http_status = 0; + pr.hr.hint = "sig field missing in response"; + break; + } + + if (GNUNET_OK != + parse_tokens (token_sigs, + &pr.details.ok.tokens, + &pr.details.ok.num_tokens)) + { + GNUNET_break_op (0); + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.http_status = 0; + pr.hr.hint = "failed to parse token_sigs field in response"; + break; + } + + if (GNUNET_OK != + TALER_merchant_pay_verify (&oph->h_contract_terms, + &oph->merchant_pub, + &pr.details.ok.merchant_sig)) + { + GNUNET_break_op (0); + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.http_status = 0; + pr.hr.hint = "signature invalid"; + } + } + break; + /* Tolerating Not Acceptable because sometimes + * - especially in tests - we might want to POST + * coins one at a time. */ + case MHD_HTTP_NOT_ACCEPTABLE: + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_BAD_REQUEST: + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); + /* This should never happen, either us + * or the merchant is buggy (or API version conflict); + * just pass JSON reply to the application */ + break; + case MHD_HTTP_PAYMENT_REQUIRED: + /* was originally paid, but then refunded */ + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_FORBIDDEN: + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the + application */ + break; + case MHD_HTTP_REQUEST_TIMEOUT: + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); + /* The merchant couldn't generate a timely response, likely because + it itself waited too long on the exchange. + Pass on to application. */ + break; + case MHD_HTTP_CONFLICT: + TALER_MERCHANT_parse_error_details_ (json, + MHD_HTTP_CONFLICT, + &pr.hr); + break; + case MHD_HTTP_GONE: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &pr.hr); + /* The merchant says we are too late, the offer has expired or some + denomination key of a coin involved has expired. + Might be a disagreement in timestamps? Still, pass on to application. */ + break; + case MHD_HTTP_PRECONDITION_FAILED: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &pr.hr); + /* Nothing really to verify, the merchant is blaming us for failing to + satisfy some constraint (likely it does not like our exchange because + of some disagreement on the PKI). We should pass the JSON reply to the + application */ + break; + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + { + json_t *ebus = json_object_get (json, + "exchange_base_urls"); + if (NULL == ebus) + { + GNUNET_break_op (0); + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.http_status = 0; + pr.hr.hint = "failed to parse exchange_base_urls field in response"; + break; + } + { + size_t alen = json_array_size (ebus); + const char *ebua[GNUNET_NZL (alen)]; + size_t idx; + json_t *jebu; + bool ok = true; + + GNUNET_assert (alen <= UINT_MAX); + json_array_foreach (ebus, idx, jebu) + { + ebua[idx] = json_string_value (jebu); + if (NULL == ebua[idx]) + { + GNUNET_break_op (0); + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.http_status = 0; + pr.hr.hint = "non-string value in exchange_base_urls in response"; + ok = false; + break; + } + } + if (! ok) + break; + pr.details.unavailable_for_legal_reasons.num_exchanges + = (unsigned int) alen; + pr.details.unavailable_for_legal_reasons.exchanges + = ebua; + oph->pay_cb (oph->cb_cls, + &pr); + TALER_MERCHANT_order_pay_cancel1 (oph); + return; + } + } + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &pr.hr); + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + break; + case MHD_HTTP_BAD_GATEWAY: + /* Nothing really to verify, the merchant is blaming the exchange. + We should pass the JSON reply to the application */ + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &pr.hr); + break; + case MHD_HTTP_SERVICE_UNAVAILABLE: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &pr.hr); + /* Exchange couldn't respond properly; the retry is + left to the application */ + break; + case MHD_HTTP_GATEWAY_TIMEOUT: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &pr.hr); + /* Exchange couldn't respond in a timely fashion; + the retry is left to the application */ + break; + default: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &pr.hr); + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) pr.hr.ec); + GNUNET_break_op (0); + break; + } + oph->pay_cb (oph->cb_cls, + &pr); + TALER_MERCHANT_order_pay_cancel1 (oph); } static enum TALER_MERCHANT_OrderPayOptionErrorCode @@ -372,6 +637,7 @@ TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph) const struct TALER_MERCHANT_PayCoin *coin = &ph->coins.coins[i]; struct TALER_MERCHANT_PaidCoin *p = &pc[i]; struct TALER_Amount fee; + struct TALER_DenominationHashP h_denom_pub; if (0 > TALER_amount_subtract (&fee, @@ -385,7 +651,7 @@ TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph) } TALER_denom_pub_hash ( &coin->denom_pub, - &p->denom_pub); + &h_denom_pub); TALER_wallet_deposit_sign ( &coin->amount_with_fee, &fee, &ph->h_wire, @@ -395,10 +661,10 @@ TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph) : NULL, coin->h_age_commitment, NULL, /* extensions */ - &p->denom_pub, + &h_denom_pub, ph->timestamp, - ph->refund_deadline, &ph->merchant_pub, + ph->refund_deadline, &coin->coin_priv, &p->coin_sig);