From 8edd5641430d7e352cf9d14edd3e57b6f75642a3 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 20 Nov 2021 21:22:01 +0100 Subject: implement #7052 --- src/backend/taler-merchant-httpd.c | 13 +- src/backend/taler-merchant-httpd.h | 9 + .../taler-merchant-httpd_post-orders-ID-pay.c | 316 ++++++++++++++++++++- 3 files changed, 328 insertions(+), 10 deletions(-) diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index 9f460df1..73d3327f 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -199,13 +199,8 @@ TMH_compute_auth (const char *token, } -/** - * Decrement reference counter of @a mi, and free if it hits zero. - * - * @param[in,out] mi merchant instance to update and possibly free - */ -static void -instance_decref (struct TMH_MerchantInstance *mi) +void +TMH_instance_decref (struct TMH_MerchantInstance *mi) { struct TMH_WireMethod *wm; @@ -252,7 +247,7 @@ TMH_instance_free_cb (void *cls, GNUNET_CONTAINER_multihashmap_remove (TMH_by_id_map, &mi->h_instance, mi)); - instance_decref (mi); + TMH_instance_decref (mi); return GNUNET_YES; } @@ -360,7 +355,7 @@ handle_mhd_completion_callback (void *cls, if (NULL != hc->request_body) json_decref (hc->request_body); if (NULL != hc->instance) - instance_decref (hc->instance); + TMH_instance_decref (hc->instance); GNUNET_free (hc); *con_cls = NULL; } diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h index 7fbc8bb5..f81b15aa 100644 --- a/src/backend/taler-merchant-httpd.h +++ b/src/backend/taler-merchant-httpd.h @@ -639,6 +639,15 @@ enum GNUNET_GenericReturnValue TMH_add_instance (struct TMH_MerchantInstance *mi); +/** + * Decrement reference counter of @a mi, and free if it hits zero. + * + * @param[in,out] mi merchant instance to update and possibly free + */ +void +TMH_instance_decref (struct TMH_MerchantInstance *mi); + + /** * Lookup a merchant instance by its instance ID. * diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c index fcdb9821..3baf7293 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -46,6 +46,13 @@ */ #define MAX_COIN_ALLOWED_COINS 1024 +/** + * How often do we ask the exchange again about our + * KYC status? Very rarely, as if the user actively + * changes it, we should usually notice anyway. + */ +#define KYC_RETRY_FREQUENCY GNUNET_TIME_UNIT_WEEKS + /** * Information we keep for an individual call to the pay handler. */ @@ -359,6 +366,69 @@ struct PayContext }; +/** + * Active KYC operation with an exchange. + */ +struct KycContext +{ + /** + * Kept in a DLL. + */ + struct KycContext *next; + + /** + * Kept in a DLL. + */ + struct KycContext *prev; + + /** + * Looking for the exchange. + */ + struct TMH_EXCHANGES_FindOperation *fo; + + /** + * Exchange this is about. + */ + char *exchange_url; + + /** + * Merchant instance this is for. + */ + struct TMH_MerchantInstance *mi; + + /** + * Wire method we are checking the status of. + */ + struct TMH_WireMethod *wm; + + /** + * Handle for the GET /deposits operation. + */ + struct TALER_EXCHANGE_DepositGetHandle *dg; + + /** + * Contract we are looking up. + */ + struct TALER_PrivateContractHash h_contract_terms; + + /** + * Coin we are looking up. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Initial DB timestamp. + */ + struct GNUNET_TIME_Absolute kyc_timestamp; + + /** + * Initial KYC status. + */ + bool kyc_ok; + +}; + + /** * Head of active pay context DLL. */ @@ -369,6 +439,44 @@ static struct PayContext *pc_head; */ static struct PayContext *pc_tail; +/** + * Head of active KYC context DLL. + */ +static struct KycContext *kc_head; + +/** + * Tail of active KYC context DLL. + */ +static struct KycContext *kc_tail; + + +/** + * Free resources used by @a kc. + * + * @param[in] kc object to free + */ +static void +destroy_kc (struct KycContext *kc) +{ + if (NULL != kc->fo) + { + TMH_EXCHANGES_find_exchange_cancel (kc->fo); + kc->fo = NULL; + } + if (NULL != kc->dg) + { + TALER_EXCHANGE_deposits_get_cancel (kc->dg); + kc->dg = NULL; + } + TMH_instance_decref (kc->mi); + kc->mi = NULL; + GNUNET_free (kc->exchange_url); + GNUNET_CONTAINER_DLL_remove (kc_head, + kc_tail, + kc); + GNUNET_free (kc); +} + /** * Compute the timeout for a /pay request based on the number of coins @@ -419,6 +527,15 @@ abort_active_deposits (struct PayContext *pc) void TMH_force_pc_resume () { + struct KycContext *kc; + + while (NULL != (kc = kc_head)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Aborting KYC check at %s\n", + kc->exchange_url); + destroy_kc (kc); + } for (struct PayContext *pc = pc_head; NULL != pc; pc = pc->next) @@ -555,6 +672,200 @@ static void execute_pay_transaction (struct PayContext *pc); +/** + * Function called with detailed wire transfer data. + * + * @param cls a `struct KycContext *` + * @param hr HTTP response data + * @param dd details about the deposit (NULL on errors) + */ +static void +deposit_get_callback ( + void *cls, + const struct TALER_EXCHANGE_GetDepositResponse *dr) +{ + struct KycContext *kc = cls; + enum GNUNET_DB_QueryStatus qs; + + kc->dg = NULL; + switch (dr->hr.http_status) + { + case MHD_HTTP_OK: + qs = TMH_db->account_kyc_set_status ( + TMH_db->cls, + kc->mi->settings.id, + &kc->wm->h_wire, + kc->exchange_url, + dr->details.success.payment_target_uuid, + NULL, /* no signature */ + NULL, /* no signature */ + GNUNET_TIME_absolute_get (), + true); + GNUNET_break (qs > 0); + break; + case MHD_HTTP_ACCEPTED: + qs = TMH_db->account_kyc_set_status ( + TMH_db->cls, + kc->mi->settings.id, + &kc->wm->h_wire, + kc->exchange_url, + dr->details.accepted.payment_target_uuid, + NULL, /* no signature */ + NULL, /* no signature */ + GNUNET_TIME_absolute_get (), + dr->details.accepted.kyc_ok); + GNUNET_break (qs > 0); + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "KYC check failed at %s with unexpected status %u\n", + kc->exchange_url, + dr->hr.http_status); + } + destroy_kc (kc); +} + + +/** + * Function called with the result of our exchange lookup. + * + * @param cls the `struct KycContext` + * @param hr HTTP response details + * @param exchange_handle NULL if exchange was not found to be acceptable + * @param payto_uri payto://-URI of the exchange + * @param wire_fee current applicable fee for dealing with @a exchange_handle, + * NULL if not available + * @param exchange_trusted true if this exchange is + * trusted by config + */ +static void +process_kyc_with_exchange (void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr, + struct TALER_EXCHANGE_Handle *exchange_handle, + const char *payto_uri, + const struct TALER_Amount *wire_fee, + bool exchange_trusted) +{ + struct KycContext *kc = cls; + + kc->fo = NULL; + if (NULL == exchange_handle) + { + destroy_kc (kc); + return; + } + kc->dg = TALER_EXCHANGE_deposits_get (exchange_handle, + &kc->mi->merchant_priv, + &kc->wm->h_wire, + &kc->h_contract_terms, + &kc->coin_pub, + &deposit_get_callback, + kc); + if (NULL == kc->dg) + { + GNUNET_break (0); + destroy_kc (kc); + } +} + + +/** + * Function called from ``account_kyc_get_status`` + * with KYC status information for this merchant. + * + * @param cls a `struct KycContext *` + * @param h_wire hash of the wire account + * @param exchange_kyc_serial serial number for the KYC process at the exchange, 0 if unknown + * @param payto_uri payto:// URI of the merchant's bank account + * @param exchange_url base URL of the exchange for which this is a status + * @param last_check when did we last get an update on our KYC status from the exchange + * @param kyc_ok true if we satisfied the KYC requirements + */ +static void +kyc_cb ( + void *cls, + const struct TALER_MerchantWireHash *h_wire, + uint64_t exchange_kyc_serial, + const char *payto_uri, + const char *exchange_url, + struct GNUNET_TIME_Absolute last_check, + bool kyc_ok) +{ + struct KycContext *kc = cls; + + kc->kyc_timestamp = last_check; + kc->kyc_ok = kyc_ok; +} + + +/** + * Check for our KYC status at @a exchange_url for the + * payment of @a pc. First checks if we already have a + * positive result from the exchange, and if not checks + * with the exchange. + * + * @param pc payment context to use as starting point + * @param dc deposit confirmation we are triggering on + */ +static void +check_kyc (struct PayContext *pc, + const struct DepositConfirmation *dc) +{ + enum GNUNET_DB_QueryStatus qs; + struct KycContext *kc; + + kc = GNUNET_new (struct KycContext); + qs = TMH_db->account_kyc_get_status (TMH_db->cls, + pc->hc->instance->settings.id, + &pc->wm->h_wire, + dc->exchange_url, + &kyc_cb, + kc); + if (qs < 0) + { + GNUNET_break (0); + GNUNET_free (kc); + return; + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + if (kc->kyc_ok) + { + GNUNET_free (kc); + return; /* we are done */ + } + if (GNUNET_TIME_absolute_get_duration (kc->kyc_timestamp).rel_value_us < + KYC_RETRY_FREQUENCY.rel_value_us) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Not re-checking KYC status at `%s', as we already recently asked\n", + dc->exchange_url); + GNUNET_free (kc); + return; + } + } + kc->mi = pc->hc->instance; + kc->mi->rc++; + kc->wm = pc->wm; + kc->exchange_url = GNUNET_strdup (dc->exchange_url); + kc->h_contract_terms = pc->h_contract_terms; + kc->coin_pub = dc->coin_pub; + GNUNET_CONTAINER_DLL_insert (kc_head, + kc_tail, + kc); + kc->fo = TMH_EXCHANGES_find_exchange (dc->exchange_url, + NULL, + GNUNET_NO, + &process_kyc_with_exchange, + kc); + if (NULL == kc->fo) + { + GNUNET_break (0); + destroy_kc (kc); + } +} + + /** * Callback to handle a deposit permission's response. * @@ -629,6 +940,8 @@ deposit_cb (void *cls, if (0 != pc->pending_at_ce) return; /* still more to do with current exchange */ + check_kyc (pc, + dc); find_next_exchange (pc); return; } @@ -1337,8 +1650,9 @@ trigger_payment_notification (struct PayContext *pc) /** + * Actually perform the payment transaction. * - * + * @param pc payment transaction to run */ static void execute_pay_transaction (struct PayContext *pc) -- cgit v1.2.3