merchant

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

commit 5364d9627a134d4af5dd8e90919556871f1e9d0e
parent 00cdf3f8ed2acd83208227aab90bf8652a837d44
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 21 Jun 2026 17:02:51 +0200

implementation for #11520 (untested)

Diffstat:
Msrc/backend/taler-merchant-httpd_get-private-kyc.c | 306+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 275 insertions(+), 31 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_get-private-kyc.c b/src/backend/taler-merchant-httpd_get-private-kyc.c @@ -28,6 +28,7 @@ #include "taler-merchant-httpd_helper.h" #include "taler-merchant-httpd_get-exchanges.h" #include <taler/taler_json_lib.h> +#include <taler/taler_bank_service.h> #include <taler/taler_templating_lib.h> #include <taler/taler_dbevents.h> #include <regex.h> @@ -43,6 +44,40 @@ struct KycContext; /** + * Handle for a request to the bank to provide a short + * wire transfer subject. + */ +struct AccountShortenRequest +{ + /** + * Kept in a DLL. + */ + struct AccountShortenRequest *next; + + /** + * Kept in a DLL. + */ + struct AccountShortenRequest *prev; + + /** + * Exchange KYC request this is part of. + */ + struct ExchangeKycRequest *ekr; + + /** + * Handle to shorten request. + */ + struct TALER_BANK_RegistrationHandle *req; + + /** + * Full payto URI of the exchange account, based on /keys. + * Pointer into the ``keys`` field of @a ekr. + */ + const char *exchange_account_payto; +}; + + +/** * Structure for tracking requests to the exchange's * ``/kyc-check`` API. */ @@ -64,6 +99,16 @@ struct ExchangeKycRequest struct TMH_EXCHANGES_KeysOperation *fo; /** + * Head of DLL of active requests to shorten wire transfer subjects. + */ + struct AccountShortenRequest *asr_head; + + /** + * Tail of DLL of active requests to shorten wire transfer subjects. + */ + struct AccountShortenRequest *asr_tail; + + /** * JSON array of payto-URIs with KYC auth wire transfer * instructions. Provided if @e auth_ok is false and * @e kyc_auth_conflict is false. @@ -113,6 +158,13 @@ struct ExchangeKycRequest struct GNUNET_TIME_Timestamp last_check; /** + * Amount suggested by the exchange for the KYC process. + * Set in kyc_with_exchange() based on @e keys. Zero if + * the exchange did not provide any amount. + */ + struct TALER_Amount kyc_amount; + + /** * Last HTTP status code obtained via /kyc-check from the exchange. */ unsigned int last_http_status; @@ -214,6 +266,11 @@ struct KycContext const char *exchange_url; /** + * Our merchant public key, as a string. + */ + char merchant_pub_str[sizeof (struct TALER_MerchantPublicKeyP) * 2]; + + /** * How long are we willing to wait for the exchange(s)? * Based on "timeout_ms" query parameter. */ @@ -354,6 +411,24 @@ TMH_force_kyc_resume () /** + * Release resources of @a asr + * + * @param[in] asr shorten request data to clean up + */ +static void +asr_cleanup (struct AccountShortenRequest *asr) +{ + struct ExchangeKycRequest *ekr = asr->ekr; + + GNUNET_CONTAINER_DLL_remove (ekr->asr_head, + ekr->asr_tail, + asr); + TALER_BANK_registration_cancel (asr->req); + GNUNET_free (asr); +} + + +/** * Release resources of @a ekr * * @param[in] ekr key request data to clean up @@ -363,6 +438,8 @@ ekr_cleanup (struct ExchangeKycRequest *ekr) { struct KycContext *kc = ekr->kc; + while (NULL != ekr->asr_head) + asr_cleanup (ekr->asr_head); GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head, kc->exchange_pending_tail, ekr); @@ -1046,6 +1123,142 @@ ekr_finished (struct ExchangeKycRequest *ekr) /** + * Function called with result from attempt to shorten + * wire transfer subject. + * + * @param cls closure with the `struct AccountShortenRequest *` + * @param resp response on the shortening request + */ +static void +asr_cb (void *cls, + const struct TALER_BANK_RegistrationResponse *resp) +{ + struct AccountShortenRequest *asr = cls; + struct ExchangeKycRequest *ekr = asr->ekr; + struct KycContext *kc = ekr->kc; + + asr->req = NULL; + GNUNET_CONTAINER_DLL_remove (ekr->asr_head, + ekr->asr_tail, + asr); + switch (resp->http_status) + { + case MHD_HTTP_OK: + for (size_t i = 0; i<resp->details.ok.num_subjects; i++) + { + const struct TALER_BANK_TransferSubject *subject + = &resp->details.ok.subjects[i]; + + switch (subject->format) + { + case TALER_BANK_SUBJECT_FORMAT_SIMPLE: + { + const struct TALER_Amount *credit_amount + = &subject->details.simple.credit_amount; + char *payto_kycauth; + + if (TALER_amount_is_zero (credit_amount)) + GNUNET_asprintf (&payto_kycauth, + "%s%cmessage=KYC:%s", + asr->exchange_account_payto, + (NULL == strchr (asr->exchange_account_payto, + '?')) + ? '?' + : '&', + kc->merchant_pub_str); + else + GNUNET_asprintf (&payto_kycauth, + "%s%camount=%s&message=%s", + asr->exchange_account_payto, + (NULL == strchr (asr->exchange_account_payto, + '?')) + ? '?' + : '&', + TALER_amount2s (credit_amount), + subject->details.simple.subject); + GNUNET_assert (0 == + json_array_append_new ( + ekr->pkaa, + json_string (payto_kycauth))); + GNUNET_free (payto_kycauth); + } + continue; + case TALER_BANK_SUBJECT_FORMAT_URI: + GNUNET_assert (0 == + json_array_append_new ( + ekr->pkaa, + json_string (subject->details.uri.uri))); + continue; + case TALER_BANK_SUBJECT_FORMAT_CH_QR_BILL: + { + const struct TALER_Amount *credit_amount + = &subject->details.ch_qr_bill.credit_amount; + char *payto_kycauth; + + if (TALER_amount_is_zero (credit_amount)) + GNUNET_asprintf ( + &payto_kycauth, + "%s%cch_qrr=%s", + asr->exchange_account_payto, + (NULL == strchr (asr->exchange_account_payto, + '?')) + ? '?' + : '&', + subject->details.ch_qr_bill.qr_reference_number); + else + GNUNET_asprintf ( + &payto_kycauth, + "%s%camount=%s&ch_qrr=%s", + asr->exchange_account_payto, + (NULL == strchr (asr->exchange_account_payto, + '?')) + ? '?' + : '&', + TALER_amount2s (credit_amount), + subject->details.ch_qr_bill.qr_reference_number); + GNUNET_assert (0 == + json_array_append_new ( + ekr->pkaa, + json_string (payto_kycauth))); + GNUNET_free (payto_kycauth); + } + continue; + } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Skipping unsupported wire transfer subject %d\n", + subject->format); + } + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to register wire transfer subject: %u (%s)\n", + resp->http_status, + TALER_ErrorCode_get_hint (resp->ec)); + break; + } + GNUNET_free (asr); + + if (NULL != ekr->asr_head) + { + /* more requests running */ + return; + } + if (0 == json_array_size (ekr->pkaa)) + { + /* No KYC auth wire transfers are possible to this exchange from + our merchant bank account, so we cannot use this account with + this exchange if it has any KYC requirements! */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC auth to `%s' impossible for merchant account `%s'\n", + ekr->exchange_url, + ekr->payto_uri.full_payto); + ekr->kyc_auth_conflict = true; + } + ekr_finished (ekr); +} + + +/** * Figure out which exchange accounts from @a keys could * be used for a KYC auth wire transfer from the account * that @a ekr is checking. Will set the "pkaa" array @@ -1059,32 +1272,8 @@ determine_eligible_accounts ( { struct KycContext *kc = ekr->kc; const struct TALER_EXCHANGE_Keys *keys = ekr->keys; - struct TALER_Amount kyc_amount; - char *merchant_pub_str; struct TALER_NormalizedPayto np; - { - const struct TALER_EXCHANGE_GlobalFee *gf; - - gf = TALER_EXCHANGE_get_global_fee (keys, - GNUNET_TIME_timestamp_get ()); - if (NULL == gf) - { - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (keys->currency, - &kyc_amount)); - } - else - { - /* FIXME-#9427: history fee should be globally renamed to KYC fee... */ - kyc_amount = gf->fees.history; - } - } - - merchant_pub_str - = GNUNET_STRINGS_data_to_string_alloc ( - &kc->mi->merchant_pub, - sizeof (kc->mi->merchant_pub)); /* For all accounts of the exchange */ np = TALER_payto_normalize (ekr->payto_uri); for (unsigned int i = 0; i<keys->accounts_len; i++) @@ -1102,14 +1291,39 @@ determine_eligible_accounts ( np)) continue; /* exchange account is allowed, add it */ - // FIXME: #11520: support short wire transfer subjects! - // if (NULL != account->prepared_transfer_url) // ... + if (NULL != account->prepared_transfer_url) + { + struct AccountShortenRequest *asr; + union TALER_AccountPublicKeyP account_pub; + struct TALER_ReserveMapAuthorizationPrivateKeyP authorization_priv; + + account_pub.merchant_pub = kc->mi->merchant_pub; + GNUNET_CRYPTO_random_block (&authorization_priv, + sizeof (authorization_priv)); + asr = GNUNET_new (struct AccountShortenRequest); + asr->ekr = ekr; + asr->exchange_account_payto = account->fpayto_uri.full_payto; + GNUNET_CONTAINER_DLL_insert (ekr->asr_head, + ekr->asr_tail, + asr); + asr->req = TALER_BANK_registration ( + TMH_curl_ctx, + account->prepared_transfer_url, + &ekr->kyc_amount, + TALER_BANK_REGISTRATION_TYPE_KYC, + &account_pub, + &authorization_priv, + false, /* not recurrent */ + &asr_cb, + asr); + continue; + } { const char *exchange_account_payto = account->fpayto_uri.full_payto; char *payto_kycauth; - if (TALER_amount_is_zero (&kyc_amount)) + if (TALER_amount_is_zero (&ekr->kyc_amount)) GNUNET_asprintf (&payto_kycauth, "%s%cmessage=KYC:%s", exchange_account_payto, @@ -1117,7 +1331,7 @@ determine_eligible_accounts ( '?')) ? '?' : '&', - merchant_pub_str); + kc->merchant_pub_str); else GNUNET_asprintf (&payto_kycauth, "%s%camount=%s&message=KYC:%s", @@ -1126,8 +1340,8 @@ determine_eligible_accounts ( '?')) ? '?' : '&', - TALER_amount2s (&kyc_amount), - merchant_pub_str); + TALER_amount2s (&ekr->kyc_amount), + kc->merchant_pub_str); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Found account %s where KYC auth is possible\n", payto_kycauth); @@ -1138,7 +1352,6 @@ determine_eligible_accounts ( } } GNUNET_free (np.normalized_payto); - GNUNET_free (merchant_pub_str); } @@ -1172,11 +1385,34 @@ kyc_with_exchange (void *cls, "Got /keys for `%s'\n", ekr->exchange_url); ekr->keys = TALER_EXCHANGE_keys_incref (keys); + { + const struct TALER_EXCHANGE_GlobalFee *gf; + + gf = TALER_EXCHANGE_get_global_fee (keys, + GNUNET_TIME_timestamp_get ()); + if (NULL == gf) + { + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (keys->currency, + &ekr->kyc_amount)); + } + else + { + /* FIXME-#9427: history fee should be globally renamed to KYC fee... */ + ekr->kyc_amount = gf->fees.history; + } + } + if (! ekr->auth_ok) { ekr->pkaa = json_array (); GNUNET_assert (NULL != ekr->pkaa); determine_eligible_accounts (ekr); + if (NULL != ekr->asr_head) + { + /* continued asynchronously */ + return; + } if (0 == json_array_size (ekr->pkaa)) { /* No KYC auth wire transfers are possible to this exchange from @@ -1700,10 +1936,18 @@ get_instances_ID_kyc ( if (NULL == kc) { + char *end; + kc = GNUNET_new (struct KycContext); kc->mi = mi; hc->ctx = kc; hc->cc = &kyc_context_cleanup; + end = GNUNET_STRINGS_data_to_string (&mi->merchant_pub, + sizeof (mi->merchant_pub), + kc->merchant_pub_str, + sizeof (kc->merchant_pub_str)); + GNUNET_assert (NULL != end); + *end = '\0'; GNUNET_CONTAINER_DLL_insert (kc_head, kc_tail, kc);