merchant

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

commit 059988ea264a795e5e6ead7b5943dca6f7d1f40a
parent 67a3a3b409e01268bbf2dffbac7887b8bd56aec7
Author: Iván Ávalos <avalos@disroot.org>
Date:   Wed,  5 Mar 2025 14:04:14 +0100

add contract v1 support to private/orders/ID

Diffstat:
Msrc/backend/taler-merchant-httpd_private-get-orders-ID.c | 220+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
1 file changed, 131 insertions(+), 89 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_private-get-orders-ID.c b/src/backend/taler-merchant-httpd_private-get-orders-ID.c @@ -18,20 +18,20 @@ * @brief implementation of GET /private/orders/ID handler * @author Florian Dold * @author Christian Grothoff + * @author Bohdan Potuzhnyi + * @author Iván Ávalos */ #include "platform.h" -#include "taler-merchant-httpd_private-get-orders-ID.h" -#include "taler-merchant-httpd_get-orders-ID.h" -#include <gnunet/gnunet_json_lib.h> -#include <jansson.h> -#include <stdint.h> #include <taler/taler_json_lib.h> #include <taler/taler_dbevents.h> -#include "taler-merchant-httpd_mhd.h" -#include "taler-merchant-httpd_exchanges.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_util.h> +#include <gnunet/gnunet_common.h> +#include <gnunet/gnunet_json_lib.h> +#include "taler_merchant_util.h" #include "taler-merchant-httpd_helper.h" #include "taler-merchant-httpd_private-get-orders.h" - +#include "taler-merchant-httpd_private-get-orders-ID.h" /** * Data structure we keep for a check payment request. @@ -206,13 +206,6 @@ struct GetOrderRequestContext const char *session_id; /** - * Fulfillment URL extracted from the contract. For repurchase detection. - * Only valid as long as @e contract_terms is valid! NULL if there is - * no fulfillment URL in the contract. - */ - const char *fulfillment_url; - - /** * Kept in a DLL while suspended on exchange. */ struct GetOrderRequestContext *next; @@ -253,17 +246,17 @@ struct GetOrderRequestContext * Contract terms of the payment we are checking. NULL when they * are not (yet) known. */ - json_t *contract_terms; + json_t *contract_terms_json; /** - * Claim token of the order. + * Parsed contract terms, NULL when parsing failed */ - struct TALER_ClaimTokenP claim_token; + struct TALER_MERCHANT_Contract *contract_terms; /** - * Timestamp from the @e contract_terms. + * Claim token of the order. */ - struct GNUNET_TIME_Timestamp timestamp; + struct TALER_ClaimTokenP claim_token; /** * Timestamp of the last payment. @@ -271,11 +264,6 @@ struct GetOrderRequestContext struct GNUNET_TIME_Timestamp last_payment; /** - * Order summary. Pointer into @e contract_terms. - */ - const char *summary; - - /** * Wire details for the payment, to be returned in the reply. NULL * if not available. */ @@ -287,6 +275,11 @@ struct GetOrderRequestContext json_t *refund_details; /** + * Amount of the order, unset for unpaid v1 orders. + */ + struct TALER_Amount contract_amount; + + /** * Hash over the @e contract_terms. */ struct TALER_PrivateContractHashP h_contract_terms; @@ -309,11 +302,6 @@ struct GetOrderRequestContext struct TALER_Amount value_total; /** - * Total we were to be paid under the contract, excluding refunds. - */ - struct TALER_Amount contract_amount; - - /** * Serial ID of the order. */ uint64_t order_serial; @@ -487,8 +475,13 @@ gorc_cleanup (void *cls) { struct GetOrderRequestContext *gorc = cls; + if (NULL != gorc->contract_terms_json) + json_decref (gorc->contract_terms_json); if (NULL != gorc->contract_terms) - json_decref (gorc->contract_terms); + { + TALER_MERCHANT_contract_free (gorc->contract_terms); + gorc->contract_terms = NULL; + } if (NULL != gorc->wire_details) json_decref (gorc->wire_details); if (NULL != gorc->refund_details) @@ -563,7 +556,7 @@ phase_init (struct GetOrderRequestContext *gorc) &resume_by_event, gorc); if ( (NULL != gorc->session_id) && - (NULL != gorc->fulfillment_url) ) + (NULL != gorc->contract_terms->fulfillment_url) ) { struct TMH_SessionEventP session_eh = { .header.size = htons (sizeof (session_eh)), @@ -577,8 +570,8 @@ phase_init (struct GetOrderRequestContext *gorc) GNUNET_CRYPTO_hash (gorc->session_id, strlen (gorc->session_id), &session_eh.h_session_id); - GNUNET_CRYPTO_hash (gorc->fulfillment_url, - strlen (gorc->fulfillment_url), + GNUNET_CRYPTO_hash (gorc->contract_terms->fulfillment_url, + strlen (gorc->contract_terms->fulfillment_url), &session_eh.h_fulfillment_url); gorc->session_eh = TMH_db->event_listen ( @@ -603,7 +596,7 @@ phase_fetch_contract (struct GetOrderRequestContext *gorc) struct TMH_HandlerContext *hc = gorc->hc; enum GNUNET_DB_QueryStatus qs; - if (NULL != gorc->contract_terms) + if (NULL != gorc->contract_terms_json) { /* Free memory filled with old contract terms before fetching the latest ones from the DB. Note that we cannot simply skip the database @@ -612,10 +605,8 @@ phase_fetch_contract (struct GetOrderRequestContext *gorc) invocation of this function and we are back here due to long polling) and thus the contract terms could have changed during claiming. Thus, we need to fetch the latest contract terms from the DB again. */ - json_decref (gorc->contract_terms); - gorc->contract_terms = NULL; - gorc->fulfillment_url = NULL; - gorc->summary = NULL; + json_decref (gorc->contract_terms_json); + gorc->contract_terms_json = NULL; gorc->order_only = false; } TMH_db->preflight (TMH_db->cls); @@ -623,7 +614,7 @@ phase_fetch_contract (struct GetOrderRequestContext *gorc) hc->instance->settings.id, hc->infix, gorc->session_id, - &gorc->contract_terms, + &gorc->contract_terms_json, &gorc->order_serial, &gorc->paid, &gorc->wired, @@ -672,7 +663,7 @@ phase_fetch_contract (struct GetOrderRequestContext *gorc) hc->infix, &gorc->claim_token, &unused, - &gorc->contract_terms); + &gorc->contract_terms_json); } if (0 > qs) { @@ -711,39 +702,67 @@ static void phase_parse_contract (struct GetOrderRequestContext *gorc) { struct TMH_HandlerContext *hc = gorc->hc; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("amount", - &gorc->contract_amount), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("fulfillment_url", - &gorc->fulfillment_url), - NULL), - GNUNET_JSON_spec_string ("summary", - &gorc->summary), - GNUNET_JSON_spec_timestamp ("timestamp", - &gorc->timestamp), - GNUNET_JSON_spec_end () - }; - if (GNUNET_OK != - GNUNET_JSON_parse (gorc->contract_terms, - spec, - NULL, NULL)) + if (NULL == gorc->contract_terms) { - GNUNET_break (0); - phase_end (gorc, - TALER_MHD_reply_with_error ( - gorc->sc.con, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID, - hc->infix)); - return; + gorc->contract_terms = TALER_MERCHANT_contract_parse ( + gorc->contract_terms_json, + true); + + if (NULL == gorc->contract_terms) + { + GNUNET_break (0); + phase_end (gorc, + TALER_MHD_reply_with_error ( + gorc->sc.con, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID, + hc->infix)); + return; + } } - if (! gorc->order_only) + + switch (gorc->contract_terms->version) + { + case TALER_MERCHANT_CONTRACT_VERSION_0: + gorc->contract_amount = gorc->contract_terms->details.v0.brutto; + break; + case TALER_MERCHANT_CONTRACT_VERSION_1: + if (gorc->choice_index >= 0) + { + if (gorc->choice_index >= + gorc->contract_terms->details.v1.choices_len) + { + GNUNET_break (0); + phase_end (gorc, + TALER_MHD_reply_with_error ( + gorc->sc.con, MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_INVARIANT_FAILURE, + NULL)); + return; + } + + gorc->contract_amount = + gorc->contract_terms->details.v1.choices[gorc->choice_index].amount; + } + break; + default: + { + GNUNET_break (0); + phase_end (gorc, + TALER_MHD_reply_with_error ( + gorc->sc.con, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION, + NULL)); + return; + } + } + + if (! gorc->order_only && GNUNET_OK != + TALER_JSON_contract_hash (gorc->contract_terms_json, + &gorc->h_contract_terms)) { - if (GNUNET_OK != - TALER_JSON_contract_hash (gorc->contract_terms, - &gorc->h_contract_terms)) { GNUNET_break (0); phase_end (gorc, @@ -754,6 +773,7 @@ phase_parse_contract (struct GetOrderRequestContext *gorc) return; } } + GNUNET_assert (NULL != gorc->contract_terms_json); GNUNET_assert (NULL != gorc->contract_terms); gorc->phase++; } @@ -819,7 +839,7 @@ phase_check_repurchase (struct GetOrderRequestContext *gorc) MHD_RESULT ret; if ( (gorc->paid) || - (NULL == gorc->fulfillment_url) || + (NULL == gorc->contract_terms->fulfillment_url) || (NULL == gorc->session_id) ) { /* Repurchase cannot apply */ @@ -829,10 +849,11 @@ phase_check_repurchase (struct GetOrderRequestContext *gorc) GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Running re-purchase detection for %s/%s\n", gorc->session_id, - gorc->fulfillment_url); + gorc->contract_terms->fulfillment_url); qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls, hc->instance->settings.id, - gorc->fulfillment_url, + gorc->contract_terms-> + fulfillment_url, gorc->session_id, TALER_EXCHANGE_YNA_NO != gorc->allow_refunded_for_repurchase, @@ -854,7 +875,7 @@ phase_check_repurchase (struct GetOrderRequestContext *gorc) GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No already paid order for %s/%s\n", gorc->session_id, - gorc->fulfillment_url); + gorc->contract_terms->fulfillment_url); gorc->phase++; return; } @@ -900,13 +921,15 @@ phase_check_repurchase (struct GetOrderRequestContext *gorc) GNUNET_JSON_pack_string ("already_paid_order_id", already_paid_order_id), GNUNET_JSON_pack_string ("already_paid_fulfillment_url", - gorc->fulfillment_url), - TALER_JSON_pack_amount ("total_amount", - &gorc->contract_amount), + gorc->contract_terms->fulfillment_url), + /* undefined for unpaid v1 contracts */ + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_amount ("total_amount", + &gorc->contract_amount)), GNUNET_JSON_pack_string ("summary", - gorc->summary), + gorc->contract_terms->summary), GNUNET_JSON_pack_timestamp ("creation_time", - gorc->timestamp)); + gorc->contract_terms->timestamp)); GNUNET_free (taler_pay_uri); GNUNET_free (already_paid_order_id); phase_end (gorc, @@ -965,7 +988,7 @@ phase_unpaid_finish (struct GetOrderRequestContext *gorc) GNUNET_JSON_pack_string ("order_status_url", order_status_url), GNUNET_JSON_pack_object_incref ("contract_terms", - gorc->contract_terms), + gorc->contract_terms_json), GNUNET_JSON_pack_string ("order_status", "claimed"))); return; @@ -984,12 +1007,14 @@ phase_unpaid_finish (struct GetOrderRequestContext *gorc) order_status_url), GNUNET_JSON_pack_string ("order_status", "unpaid"), - TALER_JSON_pack_amount ("total_amount", - &gorc->contract_amount), + /* undefined for unpaid v1 contracts */ + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_amount ("total_amount", + &gorc->contract_amount)), GNUNET_JSON_pack_string ("summary", - gorc->summary), + gorc->contract_terms->summary), GNUNET_JSON_pack_timestamp ("creation_time", - gorc->timestamp)); + gorc->contract_terms->timestamp)); GNUNET_free (taler_pay_uri); GNUNET_free (order_status_url); phase_end (gorc, @@ -1100,10 +1125,12 @@ phase_check_refunds (struct GetOrderRequestContext *gorc) GNUNET_assert (! gorc->order_only); GNUNET_assert (gorc->paid); + /* Accumulate refunds, if any. */ GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (gorc->contract_amount.currency, &gorc->refund_amount)); + qs = TMH_db->lookup_refunds_detailed ( TMH_db->cls, hc->instance->settings.id, @@ -1199,6 +1226,13 @@ deposit_cb ( static void phase_check_deposits (struct GetOrderRequestContext *gorc) { + GNUNET_assert (! gorc->order_only); + GNUNET_assert (gorc->paid); + + /* amount must be always valid for paid orders */ + GNUNET_assert (GNUNET_OK == + TALER_amount_is_valid (&gorc->contract_amount)); + GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (gorc->contract_amount.currency, &gorc->deposits_total)); @@ -1292,12 +1326,16 @@ phase_check_local_transfers (struct GetOrderRequestContext *gorc) struct TMH_HandlerContext *hc = gorc->hc; enum GNUNET_DB_QueryStatus qs; + GNUNET_assert (gorc->paid); + GNUNET_assert (! gorc->order_only); + GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (gorc->contract_amount.currency, &gorc->deposits_total)); GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (gorc->contract_amount.currency, &gorc->deposit_fees_total)); + qs = TMH_db->lookup_transfer_details_by_order (TMH_db->cls, gorc->order_serial, &process_transfer_details, @@ -1369,7 +1407,7 @@ phase_check_local_transfers (struct GetOrderRequestContext *gorc) TMH_notify_order_change (hc->instance, TMH_OSF_PAID | TMH_OSF_WIRED, - gorc->timestamp, + gorc->contract_terms->timestamp, gorc->order_serial); } } @@ -1390,6 +1428,9 @@ phase_reply_result (struct GetOrderRequestContext *gorc) char *order_status_url; json_t *choice_index; + GNUNET_assert (gorc->paid); + GNUNET_assert (! gorc->order_only); + { struct TALER_PrivateContractHashP *h_contract = NULL; @@ -1414,7 +1455,7 @@ phase_reply_result (struct GetOrderRequestContext *gorc) { GNUNET_break (GNUNET_YES == TALER_amount_is_zero (&gorc->contract_amount)); - gorc->last_payment = gorc->timestamp; + gorc->last_payment = gorc->contract_terms->timestamp; } if (-1 != gorc->choice_index) { @@ -1446,7 +1487,7 @@ phase_reply_result (struct GetOrderRequestContext *gorc) TALER_JSON_pack_amount ("deposit_total", &gorc->deposits_total), GNUNET_JSON_pack_object_incref ("contract_terms", - gorc->contract_terms), + gorc->contract_terms_json), GNUNET_JSON_pack_string ("order_status", "paid"), GNUNET_JSON_pack_timestamp ("last_payment", @@ -1457,8 +1498,9 @@ phase_reply_result (struct GetOrderRequestContext *gorc) gorc->wired), GNUNET_JSON_pack_bool ("refund_pending", gorc->refund_pending), - TALER_JSON_pack_amount ("refund_amount", - &gorc->refund_amount), + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_amount ("refund_amount", + &gorc->refund_amount)), GNUNET_JSON_pack_array_steal ("wire_details", gorc->wire_details), GNUNET_JSON_pack_array_steal ("refund_details",