commit 5364d9627a134d4af5dd8e90919556871f1e9d0e
parent 00cdf3f8ed2acd83208227aab90bf8652a837d44
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 21 Jun 2026 17:02:51 +0200
implementation for #11520 (untested)
Diffstat:
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);