merchant

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

commit 6d41df4ffbc0d921aecb265ecb49b15df5c7178e
parent 110b61ff12208ffbb022296097b85d28cc1cfe7e
Author: Christian Grothoff <christian@grothoff.org>
Date:   Fri, 22 Mar 2024 14:05:42 +0100

add logic to preserve and possibly update minimum_age in contract terms

Diffstat:
Msrc/backend/taler-merchant-httpd_private-post-orders.c | 15+++++++++++++++
Msrc/include/taler_merchant_service.h | 44++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/taler_merchant_testing_lib.h | 25++++++++++++++++++++++++-
Msrc/lib/merchant_api_post_products.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/testing/test_merchant_api.c | 33+++++++++++++++++++++++++++++++++
Msrc/testing/testing_api_cmd_merchant_get_order.c | 61++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/testing/testing_api_cmd_post_products.c | 28+++++++++++++++++++---------
7 files changed, 245 insertions(+), 19 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -330,6 +330,11 @@ struct OrderContext */ const json_t *extra; + /** + * Minimum age required by the order. + */ + uint32_t minimum_age; + } parse_order; /** @@ -1322,6 +1327,9 @@ serialize_order (struct OrderContext *oc) GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_url", oc->parse_order.fulfillment_url)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_uint64 ("minimum_age", + oc->parse_order.minimum_age)), GNUNET_JSON_pack_array_incref ("products", oc->merge_inventory.products), GNUNET_JSON_pack_data_auto ("h_wire", @@ -1576,6 +1584,10 @@ parse_order (struct OrderContext *oc) &oc->parse_order.delivery_date), NULL), GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("minimum_age", + &oc->parse_order.minimum_age), + NULL), + GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_relative_time ("auto_refund", &oc->parse_order.auto_refund), NULL), @@ -1996,6 +2008,9 @@ merge_inventory (struct OrderContext *oc) ip->product_id); return; } + oc->parse_order.minimum_age + = GNUNET_MAX (oc->parse_order.minimum_age, + pd.minimum_age); { json_t *p; diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h @@ -1663,6 +1663,50 @@ TALER_MERCHANT_products_post ( /** + * Make a POST /products request to add a product to the + * inventory. + * + * @param ctx the context + * @param backend_url HTTP base URL for the backend + * @param product_id identifier to use for the product + * @param description description of the product + * @param description_i18n Map from IETF BCP 47 language tags to localized descriptions + * @param unit unit in which the product is measured (liters, kilograms, packages, etc.) + * @param price the price for one @a unit of the product, zero is used to imply that + * this product is not sold separately or that the price is not fixed and + * must be supplied by the front-end. If non-zero, price must include + * applicable taxes. + * @param image base64-encoded product image + * @param taxes list of taxes paid by the merchant + * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic books) + * @param address where the product is in stock + * @param next_restock when the next restocking is expected to happen, 0 for unknown, + * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'. + * @param minimum_age minimum age the buyer must have + * @param cb function to call with the backend's result + * @param cb_cls closure for @a cb + * @return the request handle; NULL upon error + */ +struct TALER_MERCHANT_ProductsPostHandle * +TALER_MERCHANT_products_post2 ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *product_id, + const char *description, + const json_t *description_i18n, + const char *unit, + const struct TALER_Amount *price, + const char *image, + const json_t *taxes, + int64_t total_stock, + const json_t *address, + struct GNUNET_TIME_Timestamp next_restock, + uint32_t minimum_age, + TALER_MERCHANT_ProductsPostCallback cb, + void *cb_cls); + + +/** * Cancel POST /products operation. * * @param pph operation to cancel diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h @@ -315,9 +315,10 @@ TALER_TESTING_cmd_merchant_delete_instance (const char *label, * @param image base64-encoded product image * @param taxes list of taxes paid by the merchant * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic books) + * @param minimum_age minimum age required for buying this product * @param address where the product is in stock * @param next_restock when the next restocking is expected to happen, 0 for unknown, - * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'. + * #GNUNET_TIME_UNIT_FOREVER_TS for 'never'. * @param http_status expected HTTP response code. * @return the command. */ @@ -333,6 +334,7 @@ TALER_TESTING_cmd_merchant_post_products2 ( const char *image, json_t *taxes, int64_t total_stock, + uint32_t minimum_age, json_t *address, struct GNUNET_TIME_Timestamp next_restock, unsigned int http_status); @@ -866,6 +868,27 @@ TALER_TESTING_cmd_merchant_get_order3 ( /** + * Define a GET /private/orders/$ORDER_ID CMD. + * + * @param label the command label + * @param merchant_url base URL of the merchant which will + * serve the request. + * @param order_reference reference to a command that created an order. + * @param osc expected order status + * @param expected_min_age expected minimum age for the contract + * @param expected_http_status expected HTTP response code for the request. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_merchant_get_order4 ( + const char *label, + const char *merchant_url, + const char *order_reference, + enum TALER_MERCHANT_OrderStatusCode osc, + uint32_t expected_min_age, + unsigned int expected_http_status); + + +/** * Start a long poll for GET /private/orders/$ORDER_ID. */ struct TALER_TESTING_Command diff --git a/src/lib/merchant_api_post_products.c b/src/lib/merchant_api_post_products.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020-2021 Taler Systems SA + Copyright (C) 2020-2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -159,7 +159,7 @@ handle_post_products_finished (void *cls, struct TALER_MERCHANT_ProductsPostHandle * -TALER_MERCHANT_products_post ( +TALER_MERCHANT_products_post2 ( struct GNUNET_CURL_Context *ctx, const char *backend_url, const char *product_id, @@ -172,6 +172,7 @@ TALER_MERCHANT_products_post ( int64_t total_stock, const json_t *address, struct GNUNET_TIME_Timestamp next_restock, + uint32_t minimum_age, TALER_MERCHANT_ProductsPostCallback cb, void *cb_cls) { @@ -183,20 +184,26 @@ TALER_MERCHANT_products_post ( product_id), GNUNET_JSON_pack_string ("description", description), - GNUNET_JSON_pack_object_incref ("description_i18n", - (json_t *) description_i18n), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("description_i18n", + (json_t *) description_i18n)), GNUNET_JSON_pack_string ("unit", unit), TALER_JSON_pack_amount ("price", price), GNUNET_JSON_pack_string ("image", image), - GNUNET_JSON_pack_array_incref ("taxes", - (json_t *) taxes), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_incref ("taxes", + (json_t *) taxes)), GNUNET_JSON_pack_uint64 ("total_stock", total_stock), - GNUNET_JSON_pack_object_incref ("address", - (json_t *) address), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_uint64 ("minimum_age", + minimum_age)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("address", + (json_t *) address)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_timestamp ("next_restock", next_restock))); @@ -235,6 +242,41 @@ TALER_MERCHANT_products_post ( } +struct TALER_MERCHANT_ProductsPostHandle * +TALER_MERCHANT_products_post ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *product_id, + const char *description, + const json_t *description_i18n, + const char *unit, + const struct TALER_Amount *price, + const char *image, + const json_t *taxes, + int64_t total_stock, + const json_t *address, + struct GNUNET_TIME_Timestamp next_restock, + TALER_MERCHANT_ProductsPostCallback cb, + void *cb_cls) +{ + return TALER_MERCHANT_products_post2 (ctx, + backend_url, + product_id, + description, + description_i18n, + unit, + price, + image, + taxes, + total_stock, + address, + next_restock, + 0, + cb, + cb_cls); +} + + void TALER_MERCHANT_products_post_cancel ( struct TALER_MERCHANT_ProductsPostHandle *pph) diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c @@ -599,6 +599,20 @@ run (void *cls, "a product", "EUR:1", MHD_HTTP_NO_CONTENT), + TALER_TESTING_cmd_merchant_post_products2 ("post-products-p4", + merchant_url, + "product-4age", + "an age-restricted product", + NULL, + "unit", + "EUR:1", + "", /* image */ + NULL, + 4, + 16 /* minimum age */, + NULL, + GNUNET_TIME_UNIT_FOREVER_TS, + MHD_HTTP_NO_CONTENT), TALER_TESTING_cmd_merchant_patch_product ("patch-products-p3", merchant_url, "product-3", @@ -673,6 +687,25 @@ run (void *cls, "product-3/3", "lock-product-p3", NULL), + TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-p4-age", + cred.cfg, + merchant_url, + MHD_HTTP_OK, + "order-p4-age", + GNUNET_TIME_UNIT_ZERO_TS, + GNUNET_TIME_UNIT_FOREVER_TS, + false, + "EUR:5.0", + "x-taler-bank", + "product-4age", + "", /* locks */ + NULL), + TALER_TESTING_cmd_merchant_get_order4 ("get-order-merchant-p4-age", + merchant_url, + "create-proposal-p4-age", + TALER_MERCHANT_OSC_CLAIMED, + 16, + MHD_HTTP_OK), TALER_TESTING_cmd_merchant_delete_order ("delete-order-paid", merchant_url, "1", diff --git a/src/testing/testing_api_cmd_merchant_get_order.c b/src/testing/testing_api_cmd_merchant_get_order.c @@ -106,6 +106,11 @@ struct MerchantGetOrderState const char *repurchase_order_ref; /** + * Expected minimum age. + */ + unsigned int expected_min_age; + + /** * True if we should pass the 'allow_refunded_for_repurchase' flag. */ bool allow_refunded_for_repurchase; @@ -176,7 +181,9 @@ merchant_get_order_cb ( if (gos->osc != osr->details.ok.status) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Order paid does not match\n"); + "Order paid does not match: %d vs %d\n", + gos->osc, + osr->details.ok.status); TALER_TESTING_interpreter_fail (gos->is); return; } @@ -187,6 +194,17 @@ merchant_get_order_cb ( const struct TALER_TESTING_Command *order_cmd; struct TALER_Amount refunded_total; + if ( (0 != gos->expected_min_age) && + (gos->expected_min_age != + json_integer_value ( + json_object_get ( + osr->details.ok.details.paid.contract_terms, + "minimum_age"))) ) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (gos->is); + return; + } order_cmd = TALER_TESTING_interpreter_lookup_command ( gos->is, gos->order_reference); @@ -422,6 +440,17 @@ merchant_get_order_cb ( break; case TALER_MERCHANT_OSC_CLAIMED: /* FIXME: Check contract terms... */ + if ( (0 != gos->expected_min_age) && + (gos->expected_min_age != + json_integer_value ( + json_object_get ( + osr->details.ok.details.claimed.contract_terms, + "minimum_age"))) ) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (gos->is); + return; + } break; case TALER_MERCHANT_OSC_UNPAID: { @@ -754,6 +783,36 @@ TALER_TESTING_cmd_merchant_get_order3 ( } +struct TALER_TESTING_Command +TALER_TESTING_cmd_merchant_get_order4 ( + const char *label, + const char *merchant_url, + const char *order_reference, + enum TALER_MERCHANT_OrderStatusCode osc, + uint32_t expected_min_age, + unsigned int expected_http_status) +{ + struct MerchantGetOrderState *gos; + + gos = GNUNET_new (struct MerchantGetOrderState); + gos->merchant_url = merchant_url; + gos->order_reference = order_reference; + gos->osc = osc; + gos->expected_min_age = expected_min_age; + gos->http_status = expected_http_status; + { + struct TALER_TESTING_Command cmd = { + .cls = gos, + .label = label, + .run = &merchant_get_order_run, + .cleanup = &merchant_get_order_cleanup + }; + + return cmd; + } +} + + struct MerchantPollOrderConcludeState { /** diff --git a/src/testing/testing_api_cmd_post_products.c b/src/testing/testing_api_cmd_post_products.c @@ -95,6 +95,11 @@ struct PostProductsState json_t *address; /** + * Minimum age requirement to use for the product. + */ + unsigned int minimum_age; + + /** * when the next restocking is expected to happen, 0 for unknown, */ struct GNUNET_TIME_Timestamp next_restock; @@ -168,7 +173,7 @@ post_products_run (void *cls, struct PostProductsState *pis = cls; pis->is = is; - pis->iph = TALER_MERCHANT_products_post ( + pis->iph = TALER_MERCHANT_products_post2 ( TALER_TESTING_interpreter_get_context (is), pis->merchant_url, pis->product_id, @@ -181,6 +186,7 @@ post_products_run (void *cls, pis->total_stock, pis->address, pis->next_restock, + pis->minimum_age, &post_products_cb, pis); GNUNET_assert (NULL != pis->iph); @@ -197,7 +203,7 @@ post_products_run (void *cls, * @param index index number of the object to extract. * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue post_products_traits (void *cls, const void **ret, const char *trait, @@ -265,6 +271,7 @@ TALER_TESTING_cmd_merchant_post_products2 ( const char *image, json_t *taxes, int64_t total_stock, + uint32_t minimum_age, json_t *address, struct GNUNET_TIME_Timestamp next_restock, unsigned int http_status) @@ -288,6 +295,7 @@ TALER_TESTING_cmd_merchant_post_products2 ( pis->image = GNUNET_strdup (image); pis->taxes = taxes; /* ownership taken */ pis->total_stock = total_stock; + pis->minimum_age = minimum_age; pis->address = address; /* ownership taken */ pis->next_restock = next_restock; { @@ -305,12 +313,13 @@ TALER_TESTING_cmd_merchant_post_products2 ( struct TALER_TESTING_Command -TALER_TESTING_cmd_merchant_post_products (const char *label, - const char *merchant_url, - const char *product_id, - const char *description, - const char *price, - unsigned int http_status) +TALER_TESTING_cmd_merchant_post_products ( + const char *label, + const char *merchant_url, + const char *product_id, + const char *description, + const char *price, + unsigned int http_status) { return TALER_TESTING_cmd_merchant_post_products2 ( label, @@ -322,7 +331,8 @@ TALER_TESTING_cmd_merchant_post_products (const char *label, price, "", json_array (), - 4, + 4, /* total stock */ + 0, /* minimum age */ json_pack ("{s:s}", "street", "my street"), GNUNET_TIME_UNIT_ZERO_TS, http_status);