merchant

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

commit 60e73284d4930e8e396fe6e92d55dfbdbc683887
parent 287d4ce69a87b92f7b4ca4e4e668cb97c641b236
Author: Christian Grothoff <christian@grothoff.org>
Date:   Mon, 23 Mar 2026 14:14:54 +0100

more adaptations to latest bank API for v34

Diffstat:
Msrc/backend/Makefile.am | 4++--
Msrc/backend/taler-merchant-httpd.c | 2+-
Msrc/backend/taler-merchant-httpd_dispatcher.c | 2+-
Asrc/backend/taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.c | 743+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.h | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/backend/taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.c | 725-------------------------------------------------------------------------------
Dsrc/backend/taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.h | 54------------------------------------------------------
7 files changed, 801 insertions(+), 783 deletions(-)

diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am @@ -229,8 +229,8 @@ taler_merchant_httpd_SOURCES = \ taler-merchant-httpd_post-private-transfers.h \ taler-merchant-httpd_post-private-webhooks.c \ taler-merchant-httpd_post-private-webhooks.h \ - taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.c \ - taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.h \ + taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.c \ + taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.h \ taler-merchant-httpd_post-challenge-ID.c \ taler-merchant-httpd_post-challenge-ID.h \ taler-merchant-httpd_post-challenge-ID-confirm.c \ diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c @@ -50,7 +50,7 @@ #include "taler-merchant-httpd_get-private-orders.h" #include "taler-merchant-httpd_post-orders-ORDER_ID-pay.h" #include "taler-merchant-httpd_post-orders-ORDER_ID-refund.h" -#include "taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.h" +#include "taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.h" /** * Backlog for listen operation on unix-domain sockets. diff --git a/src/backend/taler-merchant-httpd_dispatcher.c b/src/backend/taler-merchant-httpd_dispatcher.c @@ -93,7 +93,7 @@ #include "taler-merchant-httpd_post-private-tokenfamilies.h" #include "taler-merchant-httpd_post-private-transfers.h" #include "taler-merchant-httpd_post-private-webhooks.h" -#include "taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.h" +#include "taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.h" #include "taler-merchant-httpd_post-challenge-ID.h" #include "taler-merchant-httpd_post-challenge-ID-confirm.h" #include "taler-merchant-httpd_post-orders-ORDER_ID-abort.h" diff --git a/src/backend/taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.c b/src/backend/taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.c @@ -0,0 +1,743 @@ +/* + This file is part of TALER + (C) 2026 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 General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.c + * @brief Handle POST /private/accounts/$H_WIRE/kycauth requests + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> +#include <gnunet/gnunet_json_lib.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_mhd_lib.h> +#include <taler/taler_bank_service.h> +#include "taler-merchant-httpd.h" +#include "taler-merchant-httpd_exchanges.h" +#include "taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.h" + + +/** + * Processing phases for a kycauth POST request. + */ +enum KycAuthPhase +{ + /** + * Parse the request body and URL path. + */ + KP_PARSE = 0, + + /** + * Fetch exchange /keys (and optionally register with bank gateway). + * The connection is suspended in this phase. + */ + KP_CHECK_EXCHANGES, + + /** + * Return the prepared response to the client. + */ + KP_RETURN_RESPONSE, + + /** + * Return MHD_YES to finish handling. + */ + KP_END_YES, + + /** + * Return MHD_NO to close the connection. + */ + KP_END_NO +}; + + +struct ExchangeAccount +{ + + /** + * Payto URI of the exchange, saved from /keys. + */ + struct TALER_FullPayto payto; + + /** + * Pending bank gateway registration handle, or NULL. + */ + struct TALER_BANK_RegistrationHandle *brh; + + /** + * Context we are operating in. + */ + struct KycAuthContext *kac; + + /** + * Transfer subjects to use for the account. + */ + struct TALER_BANK_TransferSubject *subjects; + + /** + * Length of the @e subjects array. + */ + unsigned int num_subjects; + + /** + * Expiration time for the given @e ts. + */ + struct GNUNET_TIME_Timestamp expiration; +}; + + +/** + * Per-request context for POST /private/accounts/$H_WIRE/kycauth. + */ +struct KycAuthContext +{ + + /** + * Kept in doubly-linked list. + */ + struct KycAuthContext *next; + + /** + * Kept in doubly-linked list. + */ + struct KycAuthContext *prev; + + /** + * MHD connection we are handling. + */ + struct MHD_Connection *connection; + + /** + * Handler context for this request. + */ + struct TMH_HandlerContext *hc; + + /** + * Prepared HTTP response to return, or NULL. + */ + struct MHD_Response *response; + + /** + * Exchange URL from the request body. + */ + char *exchange_url; + + /** + * Payto URI of our merchant bank account (from DB). + */ + struct TALER_FullPayto payto_uri; + + /** + * Pending /keys lookup operation with the exchange. + */ + struct TMH_EXCHANGES_KeysOperation *fo; + + /** + * Tiny amount from exchange /keys, used as the transfer amount. + */ + struct TALER_Amount tiny_amount; + + /** + * Array of exchange wire account payto URIs, saved from /keys. + * Length is @e exchange_accounts_len. + */ + struct ExchangeAccount *exchange_accounts; + + /** + * Number of entries in @e exchange_accounts. + */ + unsigned int exchange_accounts_len; + + /** + * Number of active registrations in @e exchange_accounts. + */ + unsigned int active_registrations; + + /** + * HTTP status code to send with @e response. + * UINT_MAX indicates a hard error (return MHD_NO). + */ + unsigned int response_code; + + /** + * Current processing phase. + */ + enum KycAuthPhase phase; + + /** + * #GNUNET_NO if the connection is not suspended, + * #GNUNET_YES if the connection is suspended, + * #GNUNET_SYSERR if suspended and being force-resumed during shutdown. + */ + enum GNUNET_GenericReturnValue suspended; + +}; + + +/** + * Head of the doubly-linked list of active kycauth contexts. + */ +static struct KycAuthContext *kac_head; + +/** + * Tail of the doubly-linked list of active kycauth contexts. + */ +static struct KycAuthContext *kac_tail; + + +void +TMH_force_kac_resume (void) +{ + for (struct KycAuthContext *kac = kac_head; + NULL != kac; + kac = kac->next) + { + if (NULL != kac->fo) + { + TMH_EXCHANGES_keys4exchange_cancel (kac->fo); + kac->fo = NULL; + } + if (GNUNET_YES == kac->suspended) + { + kac->suspended = GNUNET_SYSERR; + MHD_resume_connection (kac->connection); + } + } +} + + +/** + * Resume processing of @a kac after an async operation completed. + * + * @param[in,out] kac context to resume + */ +static void +kac_resume (struct KycAuthContext *kac) +{ + GNUNET_assert (GNUNET_YES == kac->suspended); + kac->suspended = GNUNET_NO; + MHD_resume_connection (kac->connection); + TALER_MHD_daemon_trigger (); +} + + +/** + * Clean up a kycauth context, freeing all resources. + * + * @param cls a `struct KycAuthContext` to clean up + */ +static void +kac_cleanup (void *cls) +{ + struct KycAuthContext *kac = cls; + + if (NULL != kac->fo) + { + TMH_EXCHANGES_keys4exchange_cancel (kac->fo); + kac->fo = NULL; + } + if (NULL != kac->response) + { + MHD_destroy_response (kac->response); + kac->response = NULL; + } + GNUNET_free (kac->exchange_url); + GNUNET_free (kac->payto_uri.full_payto); + for (unsigned int i = 0; i < kac->exchange_accounts_len; i++) + { + struct ExchangeAccount *acc = &kac->exchange_accounts[i]; + + if (NULL != acc->brh) + { + TALER_BANK_registration_cancel (acc->brh); + acc->brh = NULL; + } + for (unsigned int j = 0; j<acc->num_subjects; j++) + TALER_BANK_transfer_subject_free (&acc->subjects[j]); + GNUNET_free (acc->subjects); + GNUNET_free (acc->payto.full_payto); + } + GNUNET_free (kac->exchange_accounts); + GNUNET_CONTAINER_DLL_remove (kac_head, + kac_tail, + kac); + GNUNET_free (kac); +} + + +/** + * Convert a @a ts (transfer subject) to a JSON object. + * The resulting object has a "type" field discriminating the format, + * matching the JSON format returned by the bank gateway's /registration. + * + * @param acc general account details + * @param ts the specific transfer subject to convert + * @return freshly allocated JSON object, or NULL on error + */ +static json_t * +transfer_subject_to_json (const struct ExchangeAccount *acc, + const struct TALER_BANK_TransferSubject *ts) +{ + struct KycAuthContext *kac = acc->kac; + + switch (ts->format) + { + case TALER_BANK_SUBJECT_FORMAT_SIMPLE: + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "SIMPLE"), + TALER_JSON_pack_amount ("credit_amount", + &kac->tiny_amount), + GNUNET_JSON_pack_data_auto ("subject", + &kac->hc->instance->merchant_pub)); + case TALER_BANK_SUBJECT_FORMAT_URI: + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "URI"), + TALER_JSON_pack_amount ("credit_amount", + &kac->tiny_amount), + GNUNET_JSON_pack_string ("uri", + ts->details.uri.uri)); + case TALER_BANK_SUBJECT_FORMAT_CH_QR_BILL: + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "CH_QR_BILL"), + TALER_JSON_pack_amount ("credit_amount", + &ts->details.ch_qr_bill.credit_amount), + GNUNET_JSON_pack_string ("qr_reference_number", + ts->details.ch_qr_bill.qr_reference_number)); + } + GNUNET_break (0); + return NULL; +} + + +/** + * Generate the final "OK" response for the request in @a kac. + * Builds the wire_instructions JSON array from the saved exchange accounts + * and the given transfer subject. + * + * @param[in,out] request with all the details ready to build a response + */ +static void +generate_ok_response (struct KycAuthContext *kac) +{ + json_t *arr; + + arr = json_array (); + GNUNET_assert (NULL != arr); + for (unsigned int i = 0; i < kac->exchange_accounts_len; i++) + { + const struct ExchangeAccount *acc = &kac->exchange_accounts[i]; + json_t *subj; + json_t *entry; + + for (unsigned int j = 0; j < acc->num_subjects; j++) + { + subj = transfer_subject_to_json (acc, + &acc->subjects[j]); + GNUNET_assert (NULL != subj); + entry = GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("amount", + &kac->tiny_amount), + TALER_JSON_pack_full_payto ("target_payto", + acc->payto), + GNUNET_JSON_pack_object_steal ("subject", + subj), + GNUNET_JSON_pack_timestamp ("expiration", + acc->expiration)); + GNUNET_assert (NULL != entry); + GNUNET_assert (0 == + json_array_append_new (arr, + entry)); + } + } + kac->response_code = MHD_HTTP_OK; + kac->response = TALER_MHD_MAKE_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("wire_instructions", + arr)); + kac->phase = KP_RETURN_RESPONSE; + kac_resume (kac); +} + + +/** + * Callback invoked with the result of the bank gateway /registration request. + * + * @param cls our `struct KycAuthContext` + * @param rr response details + */ +static void +registration_cb (void *cls, + const struct TALER_BANK_RegistrationResponse *rr) +{ + struct ExchangeAccount *acc = cls; + struct KycAuthContext *kac = acc->kac; + + acc->brh = NULL; + kac->active_registrations--; + if (MHD_HTTP_OK != rr->http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Bank gateway registration failed with HTTP status %u\n", + rr->http_status); + kac->response_code = MHD_HTTP_BAD_GATEWAY; + kac->response = TALER_MHD_make_error ( + TALER_EC_MERCHANT_POST_ACCOUNTS_KYCAUTH_BANK_GATEWAY_UNREACHABLE, + "bank gateway /registration failed"); + } + GNUNET_free (acc->subjects); + acc->num_subjects = rr->details.ok.num_subjects; + acc->subjects = GNUNET_new_array (acc->num_subjects, + struct TALER_BANK_TransferSubject); + for (unsigned int i = 0; i<acc->num_subjects; i++) + { + TALER_BANK_transfer_subject_copy (&acc->subjects[i], + &rr->details.ok.subjects[i]); + } + acc->expiration = rr->details.ok.expiration; + if (0 != kac->active_registrations) + return; /* wait for more replies */ + generate_ok_response (kac); +} + + +/** + * Callback invoked with the /keys of the selected exchange. + * + * Saves tiny_amount and exchange account payto URIs from the keys, then + * either calls the bank gateway /registration (if wire_transfer_gateway is + * present) or directly constructs the SIMPLE-format response. + * + * @param cls our `struct KycAuthContext` + * @param keys the exchange's key material, or NULL on failure + * @param exchange internal exchange handle (unused) + */ +static void +process_keys_cb (void *cls, + struct TALER_EXCHANGE_Keys *keys, + struct TMH_Exchange *exchange) +{ + struct KycAuthContext *kac = cls; + struct TMH_MerchantInstance *mi = kac->hc->instance; + + (void) exchange; + kac->fo = NULL; + if (NULL == keys) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Could not get /keys from exchange `%s'\n", + kac->exchange_url); + kac->response_code = MHD_HTTP_BAD_GATEWAY; + kac->response = TALER_MHD_make_error ( + TALER_EC_MERCHANT_POST_ACCOUNTS_KYCAUTH_EXCHANGE_UNREACHABLE, + kac->exchange_url); + kac->phase = KP_RETURN_RESPONSE; + kac_resume (kac); + return; + } + if (! keys->tiny_amount_available) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Exchange `%s' did not provide tiny_amount in /keys\n", + kac->exchange_url); + kac->response_code = MHD_HTTP_BAD_GATEWAY; + kac->response = TALER_MHD_make_error ( + TALER_EC_MERCHANT_POST_ACCOUNTS_EXCHANGE_TOO_OLD, + "exchange /keys missing tiny_amount"); + kac->phase = KP_RETURN_RESPONSE; + kac_resume (kac); + return; + } + + /* Save tiny_amount and exchange wire accounts from keys */ + kac->tiny_amount = keys->tiny_amount; + kac->exchange_accounts_len = keys->accounts_len; + if (0 == keys->accounts_len) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Exchange `%s' did not provide any wire accounts in /keys\n", + kac->exchange_url); + kac->response_code = MHD_HTTP_BAD_GATEWAY; + kac->response = TALER_MHD_make_error ( + TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS, + "exchange /keys missing wire accounts"); + kac->phase = KP_RETURN_RESPONSE; + kac_resume (kac); + return; + } + + kac->exchange_accounts + = GNUNET_new_array (keys->accounts_len, + struct ExchangeAccount); + for (unsigned int i = 0; i < keys->accounts_len; i++) + { + struct ExchangeAccount *acc = &kac->exchange_accounts[i]; + struct TALER_ReserveMapAuthorizationPrivateKeyP apk; + + GNUNET_CRYPTO_eddsa_key_create (&apk.eddsa_priv); + acc->kac = kac; + acc->payto.full_payto + = GNUNET_strdup (keys->accounts[i].fpayto_uri.full_payto); + acc->subjects = GNUNET_new (struct TALER_BANK_TransferSubject); + acc->num_subjects = 1; + acc->subjects[0].format = TALER_BANK_SUBJECT_FORMAT_SIMPLE; + acc->expiration = GNUNET_TIME_UNIT_FOREVER_TS; + if (NULL == keys->accounts[i].wire_transfer_gateway) + continue; + acc->brh = TALER_BANK_registration ( + TMH_curl_ctx, + keys->accounts[i].wire_transfer_gateway, + &kac->tiny_amount, + TALER_BANK_REGISTRATION_TYPE_KYC, + (const union TALER_AccountPublicKeyP *) &mi->merchant_pub, + // FIXME: turn this into an option when modernizing the BANK API... + &apk, + false, /* recurrent */ + &registration_cb, + acc); + if (NULL == acc->brh) + { + GNUNET_break (0); + kac->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; + kac->response = TALER_MHD_make_error ( + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "could not start bank gateway registration"); + kac->phase = KP_RETURN_RESPONSE; + kac_resume (kac); + return; + } + kac->active_registrations++; + } + if (0 != kac->active_registrations) + return; + generate_ok_response (kac); +} + + +/** + * Process the PARSE phase: validate the URL path, parse the request body, + * and look up the merchant account in the database. + * + * @param[in,out] kac context to process + */ +static void +phase_parse (struct KycAuthContext *kac) +{ + const char *h_wire_s = kac->hc->infix; + struct TALER_MerchantWireHashP h_wire; + const char *exchange_url; + enum GNUNET_GenericReturnValue res; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("exchange_url", + &exchange_url), + GNUNET_JSON_spec_end () + }; + + GNUNET_assert (NULL != h_wire_s); + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (h_wire_s, + strlen (h_wire_s), + &h_wire, + sizeof (h_wire))) + { + GNUNET_break_op (0); + kac->response_code = MHD_HTTP_BAD_REQUEST; + kac->response = TALER_MHD_make_error ( + TALER_EC_MERCHANT_GENERIC_H_WIRE_MALFORMED, + h_wire_s); + kac->phase = KP_RETURN_RESPONSE; + return; + } + + res = TALER_MHD_parse_json_data (kac->connection, + kac->hc->request_body, + spec); + if (GNUNET_OK != res) + { + kac->phase = (GNUNET_NO == res) ? KP_END_YES : KP_END_NO; + return; + } + + /* Look up the merchant account in the database */ + { + struct TALER_MERCHANTDB_AccountDetails ad = { 0 }; + enum GNUNET_DB_QueryStatus qs; + + qs = TMH_db->select_account (TMH_db->cls, + kac->hc->instance->settings.id, + &h_wire, + &ad); + if (0 > qs) + { + GNUNET_break (0); + GNUNET_JSON_parse_free (spec); + kac->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; + kac->response = TALER_MHD_make_error ( + TALER_EC_GENERIC_DB_FETCH_FAILED, + "select_account"); + kac->phase = KP_RETURN_RESPONSE; + return; + } + if (0 == qs) + { + GNUNET_JSON_parse_free (spec); + kac->response_code = MHD_HTTP_NOT_FOUND; + kac->response = TALER_MHD_make_error ( + TALER_EC_MERCHANT_GENERIC_ACCOUNT_UNKNOWN, + h_wire_s); + kac->phase = KP_RETURN_RESPONSE; + return; + } + kac->payto_uri.full_payto = GNUNET_strdup (ad.payto_uri.full_payto); + /* Free fields we do not need */ + GNUNET_free (ad.payto_uri.full_payto); + GNUNET_free (ad.credit_facade_url); + GNUNET_free (ad.extra_wire_subject_metadata); + json_decref (ad.credit_facade_credentials); + } + + kac->exchange_url = GNUNET_strdup (exchange_url); + GNUNET_JSON_parse_free (spec); + kac->phase = KP_CHECK_EXCHANGES; +} + + +/** + * Process the CHECK_EXCHANGES phase: start an async /keys fetch from + * the requested exchange and suspend the connection. + * + * @param[in,out] kac context to process + */ +static void +phase_check_exchanges (struct KycAuthContext *kac) +{ + kac->fo = TMH_EXCHANGES_keys4exchange (kac->exchange_url, + false, + &process_keys_cb, + kac); + if (NULL == kac->fo) + { + GNUNET_break_op (0); + kac->response_code = MHD_HTTP_BAD_REQUEST; + kac->response = TALER_MHD_make_error ( + TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED, + kac->exchange_url); + kac->phase = KP_RETURN_RESPONSE; + return; + } + MHD_suspend_connection (kac->connection); + kac->suspended = GNUNET_YES; +} + + +/** + * Process the RETURN_RESPONSE phase: queue the prepared response into MHD. + * + * @param[in,out] kac context to process + */ +static void +phase_return_response (struct KycAuthContext *kac) +{ + MHD_RESULT ret; + + if (UINT_MAX == kac->response_code) + { + GNUNET_break (0); + kac->phase = KP_END_NO; + return; + } + GNUNET_assert (0 != kac->response_code); + ret = MHD_queue_response (kac->connection, + kac->response_code, + kac->response); + kac->phase = (MHD_YES == ret) ? KP_END_YES : KP_END_NO; +} + + +MHD_RESULT +TMH_private_post_accounts_H_WIRE_kycauth ( + const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + struct KycAuthContext *kac = hc->ctx; + + (void) rh; + GNUNET_assert (NULL != hc->infix); + if (NULL == kac) + { + kac = GNUNET_new (struct KycAuthContext); + kac->connection = connection; + kac->hc = hc; + hc->ctx = kac; + hc->cc = &kac_cleanup; + GNUNET_CONTAINER_DLL_insert (kac_head, + kac_tail, + kac); + } + + while (1) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Processing POST /private/accounts/$H_WIRE/kycauth" + " in phase %d\n", + (int) kac->phase); + switch (kac->phase) + { + case KP_PARSE: + phase_parse (kac); + break; + case KP_CHECK_EXCHANGES: + phase_check_exchanges (kac); + break; + case KP_RETURN_RESPONSE: + phase_return_response (kac); + break; + case KP_END_YES: + return MHD_YES; + case KP_END_NO: + return MHD_NO; + default: + GNUNET_assert (0); + return MHD_NO; + } + + switch (kac->suspended) + { + case GNUNET_SYSERR: + /* Shutdown in progress */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Processing POST /private/accounts/$H_WIRE/kycauth" + " ends due to shutdown in phase %d\n", + (int) kac->phase); + return MHD_NO; + case GNUNET_NO: + /* Not suspended, continue with next phase */ + break; + case GNUNET_YES: + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Processing POST /private/accounts/$H_WIRE/kycauth" + " suspended in phase %d\n", + (int) kac->phase); + return MHD_YES; + } + } + GNUNET_assert (0); + return MHD_YES; +} + + +/* end of taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.c */ diff --git a/src/backend/taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.h b/src/backend/taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.h @@ -0,0 +1,54 @@ +/* + This file is part of TALER + (C) 2026 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 General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.h + * @brief Handle POST /private/accounts/$H_WIRE/kycauth requests + * @author Christian Grothoff + */ +#ifndef TALER_MERCHANT_HTTPD_POST_PRIVATE_ACCOUNTS_H_WIRE_KYCAUTH_H +#define TALER_MERCHANT_HTTPD_POST_PRIVATE_ACCOUNTS_H_WIRE_KYCAUTH_H + +#include "taler-merchant-httpd.h" + + +/** + * Force all suspended kycauth handler connections to resume (during shutdown). + */ +void +TMH_force_kac_resume (void); + + +/** + * Handle a POST /private/accounts/$H_WIRE/kycauth request. + * + * Returns wire transfer instructions for performing a KYC authentication + * transfer to a specific exchange on behalf of the merchant account + * identified by @e H_WIRE. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_post_accounts_H_WIRE_kycauth ( + const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); + + +/* end of taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.h */ +#endif diff --git a/src/backend/taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.c b/src/backend/taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.c @@ -1,725 +0,0 @@ -/* - This file is part of TALER - (C) 2026 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 General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.c - * @brief Handle POST /private/accounts/$H_WIRE/kycauth requests - * @author Christian Grothoff - */ -#include "platform.h" -#include <jansson.h> -#include <microhttpd.h> -#include <gnunet/gnunet_json_lib.h> -#include <taler/taler_json_lib.h> -#include <taler/taler_mhd_lib.h> -#include <taler/taler_bank_service.h> -#include "taler-merchant-httpd.h" -#include "taler-merchant-httpd_exchanges.h" -#include "taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.h" -#include "taler_merchantdb_plugin.h" - - -/** - * Processing phases for a kycauth POST request. - */ -enum KycAuthPhase -{ - /** - * Parse the request body and URL path. - */ - KP_PARSE = 0, - - /** - * Fetch exchange /keys (and optionally register with bank gateway). - * The connection is suspended in this phase. - */ - KP_CHECK_EXCHANGES, - - /** - * Return the prepared response to the client. - */ - KP_RETURN_RESPONSE, - - /** - * Return MHD_YES to finish handling. - */ - KP_END_YES, - - /** - * Return MHD_NO to close the connection. - */ - KP_END_NO -}; - - -struct ExchangeAccount -{ - - /** - * Payto URI of the exchange, saved from /keys. - */ - struct TALER_FullPayto payto; - - /** - * Pending bank gateway registration handle, or NULL. - */ - struct TALER_BANK_RegistrationHandle *brh; - - /** - * Context we are operating in. - */ - struct KycAuthContext *kac; - - /** - * Transfer subject to use for the account. - */ - struct TALER_BANK_TransferSubject ts; - - /** - * Expiration time for the given @e ts. - */ - struct GNUNET_TIME_Timestamp expiration; -}; - - -/** - * Per-request context for POST /private/accounts/$H_WIRE/kycauth. - */ -struct KycAuthContext -{ - - /** - * Kept in doubly-linked list. - */ - struct KycAuthContext *next; - - /** - * Kept in doubly-linked list. - */ - struct KycAuthContext *prev; - - /** - * MHD connection we are handling. - */ - struct MHD_Connection *connection; - - /** - * Handler context for this request. - */ - struct TMH_HandlerContext *hc; - - /** - * Prepared HTTP response to return, or NULL. - */ - struct MHD_Response *response; - - /** - * Exchange URL from the request body. - */ - char *exchange_url; - - /** - * Payto URI of our merchant bank account (from DB). - */ - struct TALER_FullPayto payto_uri; - - /** - * Pending /keys lookup operation with the exchange. - */ - struct TMH_EXCHANGES_KeysOperation *fo; - - /** - * Tiny amount from exchange /keys, used as the transfer amount. - */ - struct TALER_Amount tiny_amount; - - /** - * Array of exchange wire account payto URIs, saved from /keys. - * Length is @e exchange_accounts_len. - */ - struct ExchangeAccount *exchange_accounts; - - /** - * Number of entries in @e exchange_accounts. - */ - unsigned int exchange_accounts_len; - - /** - * Number of active registrations in @e exchange_accounts. - */ - unsigned int active_registrations; - - /** - * HTTP status code to send with @e response. - * UINT_MAX indicates a hard error (return MHD_NO). - */ - unsigned int response_code; - - /** - * Current processing phase. - */ - enum KycAuthPhase phase; - - /** - * #GNUNET_NO if the connection is not suspended, - * #GNUNET_YES if the connection is suspended, - * #GNUNET_SYSERR if suspended and being force-resumed during shutdown. - */ - enum GNUNET_GenericReturnValue suspended; - -}; - - -/** - * Head of the doubly-linked list of active kycauth contexts. - */ -static struct KycAuthContext *kac_head; - -/** - * Tail of the doubly-linked list of active kycauth contexts. - */ -static struct KycAuthContext *kac_tail; - - -void -TMH_force_kac_resume (void) -{ - for (struct KycAuthContext *kac = kac_head; - NULL != kac; - kac = kac->next) - { - if (NULL != kac->fo) - { - TMH_EXCHANGES_keys4exchange_cancel (kac->fo); - kac->fo = NULL; - } - if (GNUNET_YES == kac->suspended) - { - kac->suspended = GNUNET_SYSERR; - MHD_resume_connection (kac->connection); - } - } -} - - -/** - * Resume processing of @a kac after an async operation completed. - * - * @param[in,out] kac context to resume - */ -static void -kac_resume (struct KycAuthContext *kac) -{ - GNUNET_assert (GNUNET_YES == kac->suspended); - kac->suspended = GNUNET_NO; - MHD_resume_connection (kac->connection); - TALER_MHD_daemon_trigger (); -} - - -/** - * Clean up a kycauth context, freeing all resources. - * - * @param cls a `struct KycAuthContext` to clean up - */ -static void -kac_cleanup (void *cls) -{ - struct KycAuthContext *kac = cls; - - if (NULL != kac->fo) - { - TMH_EXCHANGES_keys4exchange_cancel (kac->fo); - kac->fo = NULL; - } - if (NULL != kac->response) - { - MHD_destroy_response (kac->response); - kac->response = NULL; - } - GNUNET_free (kac->exchange_url); - GNUNET_free (kac->payto_uri.full_payto); - for (unsigned int i = 0; i < kac->exchange_accounts_len; i++) - { - struct ExchangeAccount *acc = &kac->exchange_accounts[i]; - - if (NULL != acc->brh) - { - TALER_BANK_registration_cancel (acc->brh); - acc->brh = NULL; - } - TALER_BANK_transfer_subject_free (&acc->ts); - GNUNET_free (acc->payto.full_payto); - } - GNUNET_free (kac->exchange_accounts); - GNUNET_CONTAINER_DLL_remove (kac_head, - kac_tail, - kac); - GNUNET_free (kac); -} - - -/** - * Convert a @a ts (transfer subject) to a JSON object. - * The resulting object has a "type" field discriminating the format, - * matching the JSON format returned by the bank gateway's /registration. - * - * @param ts the transfer subject to convert - * @param tiny_amount amount to include for formats that require it but - * do not embed it (e.g. URI format) - * @return freshly allocated JSON object, or NULL on error - */ -static json_t * -transfer_subject_to_json (const struct ExchangeAccount *acc) -{ - struct KycAuthContext *kac = acc->kac; - - switch (acc->ts.format) - { - case TALER_BANK_SUBJECT_FORMAT_SIMPLE: - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("type", - "SIMPLE"), - TALER_JSON_pack_amount ("credit_amount", - &kac->tiny_amount), - GNUNET_JSON_pack_data_auto ("subject", - &kac->hc->instance->merchant_pub)); - case TALER_BANK_SUBJECT_FORMAT_URI: - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("type", - "URI"), - TALER_JSON_pack_amount ("credit_amount", - &kac->tiny_amount), - GNUNET_JSON_pack_string ("uri", - acc->ts.details.uri.uri)); - case TALER_BANK_SUBJECT_FORMAT_CH_QR_BILL: - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("type", - "CH_QR_BILL"), - TALER_JSON_pack_amount ("credit_amount", - &acc->ts.details.ch_qr_bill.credit_amount), - GNUNET_JSON_pack_string ("qr_reference_number", - acc->ts.details.ch_qr_bill.qr_reference_number)); - } - GNUNET_break (0); - return NULL; -} - - -/** - * Generate the final "OK" response for the request in @a kac. - * Builds the wire_instructions JSON array from the saved exchange accounts - * and the given transfer subject. - * - * @param[in,out] request with all the details ready to build a response - */ -static void -generate_ok_response (struct KycAuthContext *kac) -{ - json_t *arr; - - arr = json_array (); - GNUNET_assert (NULL != arr); - for (unsigned int i = 0; i < kac->exchange_accounts_len; i++) - { - const struct ExchangeAccount *acc = &kac->exchange_accounts[i]; - json_t *subj; - json_t *entry; - - subj = transfer_subject_to_json (acc); - GNUNET_assert (NULL != subj); - entry = GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("amount", - &kac->tiny_amount), - TALER_JSON_pack_full_payto ("target_payto", - acc->payto), - GNUNET_JSON_pack_object_steal ("subject", - subj), - GNUNET_JSON_pack_timestamp ("expiration", - acc->expiration)); - GNUNET_assert (NULL != entry); - GNUNET_assert (0 == - json_array_append_new (arr, - entry)); - } - kac->response_code = MHD_HTTP_OK; - kac->response = TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_array_steal ("wire_instructions", - arr)); - kac->phase = KP_RETURN_RESPONSE; - kac_resume (kac); -} - - -/** - * Callback invoked with the result of the bank gateway /registration request. - * - * @param cls our `struct KycAuthContext` - * @param rr response details - */ -static void -registration_cb (void *cls, - const struct TALER_BANK_RegistrationResponse *rr) -{ - struct ExchangeAccount *acc = cls; - struct KycAuthContext *kac = acc->kac; - - acc->brh = NULL; - kac->active_registrations--; - if (MHD_HTTP_OK != rr->http_status) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Bank gateway registration failed with HTTP status %u\n", - rr->http_status); - kac->response_code = MHD_HTTP_BAD_GATEWAY; - kac->response = TALER_MHD_make_error ( - TALER_EC_MERCHANT_POST_ACCOUNTS_KYCAUTH_BANK_GATEWAY_UNREACHABLE, - "bank gateway /registration failed"); - } - TALER_BANK_transfer_subject_copy (&acc->ts, - &rr->details.ok.subject); - acc->expiration = rr->details.ok.expiration; - if (0 != kac->active_registrations) - return; /* wait for more replies */ - generate_ok_response (kac); -} - - -/** - * Callback invoked with the /keys of the selected exchange. - * - * Saves tiny_amount and exchange account payto URIs from the keys, then - * either calls the bank gateway /registration (if wire_transfer_gateway is - * present) or directly constructs the SIMPLE-format response. - * - * @param cls our `struct KycAuthContext` - * @param keys the exchange's key material, or NULL on failure - * @param exchange internal exchange handle (unused) - */ -static void -process_keys_cb (void *cls, - struct TALER_EXCHANGE_Keys *keys, - struct TMH_Exchange *exchange) -{ - struct KycAuthContext *kac = cls; - struct TMH_MerchantInstance *mi = kac->hc->instance; - - (void) exchange; - kac->fo = NULL; - if (NULL == keys) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Could not get /keys from exchange `%s'\n", - kac->exchange_url); - kac->response_code = MHD_HTTP_BAD_GATEWAY; - kac->response = TALER_MHD_make_error ( - TALER_EC_MERCHANT_POST_ACCOUNTS_KYCAUTH_EXCHANGE_UNREACHABLE, - kac->exchange_url); - kac->phase = KP_RETURN_RESPONSE; - kac_resume (kac); - return; - } - if (! keys->tiny_amount_available) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Exchange `%s' did not provide tiny_amount in /keys\n", - kac->exchange_url); - kac->response_code = MHD_HTTP_BAD_GATEWAY; - kac->response = TALER_MHD_make_error ( - TALER_EC_MERCHANT_POST_ACCOUNTS_EXCHANGE_TOO_OLD, - "exchange /keys missing tiny_amount"); - kac->phase = KP_RETURN_RESPONSE; - kac_resume (kac); - return; - } - - /* Save tiny_amount and exchange wire accounts from keys */ - kac->tiny_amount = keys->tiny_amount; - kac->exchange_accounts_len = keys->accounts_len; - if (0 == keys->accounts_len) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Exchange `%s' did not provide any wire accounts in /keys\n", - kac->exchange_url); - kac->response_code = MHD_HTTP_BAD_GATEWAY; - kac->response = TALER_MHD_make_error ( - TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS, - "exchange /keys missing wire accounts"); - kac->phase = KP_RETURN_RESPONSE; - kac_resume (kac); - return; - } - - kac->exchange_accounts - = GNUNET_new_array (keys->accounts_len, - struct ExchangeAccount); - for (unsigned int i = 0; i < keys->accounts_len; i++) - { - struct ExchangeAccount *acc = &kac->exchange_accounts[i]; - struct TALER_ReserveMapAuthorizationPrivateKeyP apk; - - GNUNET_CRYPTO_eddsa_key_create (&apk.eddsa_priv); - acc->kac = kac; - acc->payto.full_payto - = GNUNET_strdup (keys->accounts[i].fpayto_uri.full_payto); - acc->ts.format = TALER_BANK_SUBJECT_FORMAT_SIMPLE; - acc->expiration = GNUNET_TIME_UNIT_FOREVER_TS; - if (NULL == keys->accounts[i].wire_transfer_gateway) - continue; - acc->brh = TALER_BANK_registration ( - TMH_curl_ctx, - keys->accounts[i].wire_transfer_gateway, - TALER_BANK_SUBJECT_FORMAT_SIMPLE, - &kac->tiny_amount, - TALER_BANK_REGISTRATION_TYPE_KYC, - (const union TALER_AccountPublicKeyP *) &mi->merchant_pub, - // FIXME: turn this into an option when modernizing the BANK API... - &apk, - false, /* recurrent */ - &registration_cb, - acc); - if (NULL == acc->brh) - { - GNUNET_break (0); - kac->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; - kac->response = TALER_MHD_make_error ( - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "could not start bank gateway registration"); - kac->phase = KP_RETURN_RESPONSE; - kac_resume (kac); - return; - } - kac->active_registrations++; - } - if (0 != kac->active_registrations) - return; - generate_ok_response (kac); -} - - -/** - * Process the PARSE phase: validate the URL path, parse the request body, - * and look up the merchant account in the database. - * - * @param[in,out] kac context to process - */ -static void -phase_parse (struct KycAuthContext *kac) -{ - const char *h_wire_s = kac->hc->infix; - struct TALER_MerchantWireHashP h_wire; - const char *exchange_url; - enum GNUNET_GenericReturnValue res; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("exchange_url", - &exchange_url), - GNUNET_JSON_spec_end () - }; - - GNUNET_assert (NULL != h_wire_s); - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (h_wire_s, - strlen (h_wire_s), - &h_wire, - sizeof (h_wire))) - { - GNUNET_break_op (0); - kac->response_code = MHD_HTTP_BAD_REQUEST; - kac->response = TALER_MHD_make_error ( - TALER_EC_MERCHANT_GENERIC_H_WIRE_MALFORMED, - h_wire_s); - kac->phase = KP_RETURN_RESPONSE; - return; - } - - res = TALER_MHD_parse_json_data (kac->connection, - kac->hc->request_body, - spec); - if (GNUNET_OK != res) - { - kac->phase = (GNUNET_NO == res) ? KP_END_YES : KP_END_NO; - return; - } - - /* Look up the merchant account in the database */ - { - struct TALER_MERCHANTDB_AccountDetails ad = { 0 }; - enum GNUNET_DB_QueryStatus qs; - - qs = TMH_db->select_account (TMH_db->cls, - kac->hc->instance->settings.id, - &h_wire, - &ad); - if (0 > qs) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - kac->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; - kac->response = TALER_MHD_make_error ( - TALER_EC_GENERIC_DB_FETCH_FAILED, - "select_account"); - kac->phase = KP_RETURN_RESPONSE; - return; - } - if (0 == qs) - { - GNUNET_JSON_parse_free (spec); - kac->response_code = MHD_HTTP_NOT_FOUND; - kac->response = TALER_MHD_make_error ( - TALER_EC_MERCHANT_GENERIC_ACCOUNT_UNKNOWN, - h_wire_s); - kac->phase = KP_RETURN_RESPONSE; - return; - } - kac->payto_uri.full_payto = GNUNET_strdup (ad.payto_uri.full_payto); - /* Free fields we do not need */ - GNUNET_free (ad.payto_uri.full_payto); - GNUNET_free (ad.credit_facade_url); - GNUNET_free (ad.extra_wire_subject_metadata); - json_decref (ad.credit_facade_credentials); - } - - kac->exchange_url = GNUNET_strdup (exchange_url); - GNUNET_JSON_parse_free (spec); - kac->phase = KP_CHECK_EXCHANGES; -} - - -/** - * Process the CHECK_EXCHANGES phase: start an async /keys fetch from - * the requested exchange and suspend the connection. - * - * @param[in,out] kac context to process - */ -static void -phase_check_exchanges (struct KycAuthContext *kac) -{ - kac->fo = TMH_EXCHANGES_keys4exchange (kac->exchange_url, - false, - &process_keys_cb, - kac); - if (NULL == kac->fo) - { - GNUNET_break_op (0); - kac->response_code = MHD_HTTP_BAD_REQUEST; - kac->response = TALER_MHD_make_error ( - TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED, - kac->exchange_url); - kac->phase = KP_RETURN_RESPONSE; - return; - } - MHD_suspend_connection (kac->connection); - kac->suspended = GNUNET_YES; -} - - -/** - * Process the RETURN_RESPONSE phase: queue the prepared response into MHD. - * - * @param[in,out] kac context to process - */ -static void -phase_return_response (struct KycAuthContext *kac) -{ - MHD_RESULT ret; - - if (UINT_MAX == kac->response_code) - { - GNUNET_break (0); - kac->phase = KP_END_NO; - return; - } - GNUNET_assert (0 != kac->response_code); - ret = MHD_queue_response (kac->connection, - kac->response_code, - kac->response); - kac->phase = (MHD_YES == ret) ? KP_END_YES : KP_END_NO; -} - - -MHD_RESULT -TMH_private_post_accounts_H_WIRE_kycauth ( - const struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - struct TMH_HandlerContext *hc) -{ - struct KycAuthContext *kac = hc->ctx; - - (void) rh; - GNUNET_assert (NULL != hc->infix); - if (NULL == kac) - { - kac = GNUNET_new (struct KycAuthContext); - kac->connection = connection; - kac->hc = hc; - hc->ctx = kac; - hc->cc = &kac_cleanup; - GNUNET_CONTAINER_DLL_insert (kac_head, - kac_tail, - kac); - } - - while (1) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Processing POST /private/accounts/$H_WIRE/kycauth" - " in phase %d\n", - (int) kac->phase); - switch (kac->phase) - { - case KP_PARSE: - phase_parse (kac); - break; - case KP_CHECK_EXCHANGES: - phase_check_exchanges (kac); - break; - case KP_RETURN_RESPONSE: - phase_return_response (kac); - break; - case KP_END_YES: - return MHD_YES; - case KP_END_NO: - return MHD_NO; - default: - GNUNET_assert (0); - return MHD_NO; - } - - switch (kac->suspended) - { - case GNUNET_SYSERR: - /* Shutdown in progress */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Processing POST /private/accounts/$H_WIRE/kycauth" - " ends due to shutdown in phase %d\n", - (int) kac->phase); - return MHD_NO; - case GNUNET_NO: - /* Not suspended, continue with next phase */ - break; - case GNUNET_YES: - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Processing POST /private/accounts/$H_WIRE/kycauth" - " suspended in phase %d\n", - (int) kac->phase); - return MHD_YES; - } - } - GNUNET_assert (0); - return MHD_YES; -} - - -/* end of taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.c */ diff --git a/src/backend/taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.h b/src/backend/taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.h @@ -1,54 +0,0 @@ -/* - This file is part of TALER - (C) 2026 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 General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.h - * @brief Handle POST /private/accounts/$H_WIRE/kycauth requests - * @author Christian Grothoff - */ -#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_ACCOUNTS_H_WIRE_KYCAUTH_H -#define TALER_MERCHANT_HTTPD_PRIVATE_POST_ACCOUNTS_H_WIRE_KYCAUTH_H - -#include "taler-merchant-httpd.h" - - -/** - * Force all suspended kycauth handler connections to resume (during shutdown). - */ -void -TMH_force_kac_resume (void); - - -/** - * Handle a POST /private/accounts/$H_WIRE/kycauth request. - * - * Returns wire transfer instructions for performing a KYC authentication - * transfer to a specific exchange on behalf of the merchant account - * identified by @e H_WIRE. - * - * @param rh context of the handler - * @param connection the MHD connection to handle - * @param[in,out] hc context with further information about the request - * @return MHD result code - */ -MHD_RESULT -TMH_private_post_accounts_H_WIRE_kycauth ( - const struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - struct TMH_HandlerContext *hc); - - -/* end of taler-merchant-httpd_private-post-accounts-H_WIRE-kycauth.h */ -#endif