merchant

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

commit 9e90a4432b414382344ea525a34af27e661523f4
parent 3493795065b8fde49a5a4c06e5302b2afd03685a
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat,  2 May 2020 22:03:52 +0200

fix/complete inventory management logic

Diffstat:
Msrc/backend/taler-merchant-httpd_exchanges.c | 143+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/backend/taler-merchant-httpd_post-orders-ID-abort.c | 4++--
Msrc/backend/taler-merchant-httpd_post-orders-ID-claim.c | 11-----------
Msrc/backenddb/plugin_merchantdb_postgres.c | 57++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/include/taler_merchantdb_plugin.h | 4+++-
Msrc/lib/merchant_api_post_order_abort.c | 24+++++++++++++++++-------
6 files changed, 169 insertions(+), 74 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c @@ -22,6 +22,7 @@ #include "platform.h" #include <taler/taler_json_lib.h> #include "taler-merchant-httpd_exchanges.h" +#include "taler-merchant-httpd.h" /** @@ -727,6 +728,70 @@ wire_task_cb (void *cls) /** + * We failed downloading /keys from @a exchange. Tell clients + * about our failure, abort pending operations and retry later. + * + * @param exchange exchange that failed + * @param hr details about the HTTP reply + * @param compat version compatibility data + */ +static void +fail_and_retry (struct Exchange *exchange, + const struct TALER_EXCHANGE_HttpResponse *hr, + enum TALER_EXCHANGE_VersionCompatibility compat) +{ + struct TMH_EXCHANGES_FindOperation *fo; + + exchange->pending = GNUNET_YES; + if (NULL != exchange->wire_request) + { + TALER_EXCHANGE_wire_cancel (exchange->wire_request); + exchange->wire_request = NULL; + } + if (NULL != exchange->wire_task) + { + GNUNET_SCHEDULER_cancel (exchange->wire_task); + exchange->wire_task = NULL; + } + while (NULL != (fo = exchange->fo_head)) + { + fo->fc (fo->fc_cls, + hr, + NULL, + NULL, + GNUNET_NO); + TMH_EXCHANGES_find_exchange_cancel (fo); + } + if (TALER_EXCHANGE_VC_INCOMPATIBLE_NEWER == compat) + { + /* Log hard error: we likely need admin help! */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Exchange `%s' runs an incompatible more recent version of the Taler protocol. Will not retry. This client may need to be updated.\n", + exchange->url); + /* Theoretically, the exchange could downgrade, + but let's not be too aggressive about retries + on this one. */ + exchange->retry_delay = GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_HOURS, + exchange->retry_delay); + } + exchange->retry_delay = RETRY_BACKOFF (exchange->retry_delay); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to fetch /keys from `%s': %d/%u, retrying in %s\n", + exchange->url, + (int) hr->ec, + hr->http_status, + GNUNET_STRINGS_relative_time_to_string (exchange->retry_delay, + GNUNET_YES)); + GNUNET_assert (NULL == exchange->retry_task); + exchange->first_retry = GNUNET_TIME_relative_to_absolute ( + exchange->retry_delay); + exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay, + &retry_exchange, + exchange); +} + + +/** * Function called with information about who is auditing * a particular exchange and what key the exchange is using. * @@ -754,54 +819,9 @@ keys_mgmt_cb (void *cls, if (NULL == keys) { - struct TMH_EXCHANGES_FindOperation *fo; - - exchange->pending = GNUNET_YES; - if (NULL != exchange->wire_request) - { - TALER_EXCHANGE_wire_cancel (exchange->wire_request); - exchange->wire_request = NULL; - } - if (NULL != exchange->wire_task) - { - GNUNET_SCHEDULER_cancel (exchange->wire_task); - exchange->wire_task = NULL; - } - while (NULL != (fo = exchange->fo_head)) - { - fo->fc (fo->fc_cls, - hr, - NULL, - NULL, - GNUNET_NO); - TMH_EXCHANGES_find_exchange_cancel (fo); - } - if (TALER_EXCHANGE_VC_INCOMPATIBLE_NEWER == compat) - { - /* Log hard error: we likely need admin help! */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Exchange `%s' runs an incompatible more recent version of the Taler protocol. Will not retry. This client may need to be updated.\n", - exchange->url); - /* Theoretically, the exchange could downgrade, - but let's not be too aggressive about retries - on this one. */ - exchange->retry_delay = GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_HOURS, - exchange->retry_delay); - } - exchange->retry_delay = RETRY_BACKOFF (exchange->retry_delay); - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to fetch /keys from `%s': %d/%u, retrying in %s\n", - exchange->url, - (int) hr->ec, - hr->http_status, - GNUNET_STRINGS_relative_time_to_string (exchange->retry_delay, - GNUNET_YES)); - GNUNET_assert (NULL == exchange->retry_task); - exchange->first_retry = GNUNET_TIME_relative_to_absolute ( - exchange->retry_delay); - exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay, - &retry_exchange, - exchange); + fail_and_retry (exchange, + hr, + compat); return; } if ( (GNUNET_YES == exchange->trusted) && @@ -830,8 +850,31 @@ keys_mgmt_cb (void *cls, exchange->url); } } - // FIXME: go over keys->sign_keys and STORE - // all of those into our database! + + /* store exchange online signing keys in our DB */ + for (unsigned int i = 0; i<keys->num_sign_keys; i++) + { + struct TALER_EXCHANGE_SigningPublicKey *sign_key = &keys->sign_keys[i]; + enum GNUNET_DB_QueryStatus qs; + + TMH_db->preflight (TMH_db->cls); + qs = TMH_db->insert_exchange_signkey (TMH_db->cls, + &keys->master_pub, + &sign_key->key, + sign_key->valid_from, + sign_key->valid_until, + sign_key->valid_legal, + &sign_key->master_sig); + /* 0 is OK, we may already have the key in the DB! */ + if (0 > qs) + { + GNUNET_break (0); + fail_and_retry (exchange, + hr, + compat); + return; + } + } expire = TALER_EXCHANGE_check_keys_current (exchange->conn, GNUNET_NO, diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c @@ -741,7 +741,7 @@ begin_transaction (struct AbortContext *ac) /* Payment is complete, refuse to abort. */ TMH_db->rollback (TMH_db->cls); resume_abort_with_error (ac, - MHD_HTTP_FORBIDDEN, + MHD_HTTP_PRECONDITION_FAILED, TALER_EC_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE, "Payment was complete, refusing to abort"); return; @@ -753,7 +753,7 @@ begin_transaction (struct AbortContext *ac) { GNUNET_break_op (0); resume_abort_with_error (ac, - MHD_HTTP_BAD_REQUEST, + MHD_HTTP_FORBIDDEN, TALER_EC_ABORT_CONTRACT_HASH_MISSMATCH, "Provided hash does not match order on file"); return; diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-claim.c b/src/backend/taler-merchant-httpd_post-orders-ID-claim.c @@ -103,17 +103,6 @@ claim_order (const char *instance_id, *contract_terms = NULL; return qs; } - qs = TMH_db->delete_order (TMH_db->cls, - instance_id, - order_id); - if (0 >= qs) - { - GNUNET_break (0); - TMH_db->rollback (TMH_db->cls); - json_decref (*contract_terms); - *contract_terms = NULL; - return qs; - } qs = TMH_db->commit (TMH_db->cls); if (0 > qs) return qs; diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c @@ -1863,7 +1863,8 @@ postgres_lookup_refunds (void *cls, /** * Mark contract as paid and store the current @a session_id - * for which the contract was paid. + * for which the contract was paid. Deletes the underlying order + * and marks the locked stocks of the order as sold. * * @param cls closure * @param instance_id instance to mark contract as paid for @@ -1884,6 +1885,12 @@ postgres_mark_contract_paid (void *cls, GNUNET_PQ_query_param_string (session_id), GNUNET_PQ_query_param_end }; + struct GNUNET_PQ_QueryParam uparams[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs; /* no preflight check here, run in transaction by caller! */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, @@ -1891,9 +1898,19 @@ postgres_mark_contract_paid (void *cls, GNUNET_h2s (h_contract_terms), instance_id, session_id); + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "mark_contract_paid", + params); + if (qs <= 0) + return qs; + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "mark_inventory_sold", + uparams); + if (qs < 0) + return qs; /* 0: no inventory management, that's OK! */ return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "mark_contract_paid", - params); + "delete_completed_order", + uparams); } @@ -5472,6 +5489,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) " FULL OUTER JOIN ed ON TRUE" " WHERE h_contract_terms=$2", 16), + /* for postgres_lookup_refunds() */ GNUNET_PQ_make_prepare ("lookup_refunds", "SELECT" " coin_pub" @@ -5487,6 +5505,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) " FROM merchant_instances" " WHERE merchant_id=$1))", 2), + /* for postgres_mark_contract_paid() */ GNUNET_PQ_make_prepare ("mark_contract_paid", "UPDATE merchant_contract_terms SET" " paid=TRUE" @@ -5497,6 +5516,38 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) " FROM merchant_instances" " WHERE merchant_id=$1)", 3), + /* for postgres_mark_contract_paid() */ + GNUNET_PQ_make_prepare ("mark_inventory_sold", + "UPDATE merchant_inventory SET" + " total_sold=total_sold + order_locks.total_locked" + " FROM (SELECT total_locked,product_serial" + " FROM merchant_order_locks" + " WHERE order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE h_contract_terms=$2" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1))" + " ) AS order_locks" + " WHERE merchant_inventory.product_serial" + " =order_locks.product_serial", + 2), + /* for postgres_mark_contract_paid() */ + GNUNET_PQ_make_prepare ("delete_completed_order", + "WITH md AS" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1) " + "DELETE" + " FROM merchant_orders" + " WHERE order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " JOIN md USING (merchant_serial)" + " WHERE h_contract_terms=$2)", + 2), /* for postgres_refund_coin() */ GNUNET_PQ_make_prepare ("refund_coin", "INSERT INTO merchant_refunds" diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -779,6 +779,7 @@ struct TALER_MERCHANTDB_Plugin const char *instance_id, const char *order_id); + /** * Retrieve order given its @a order_id and the @a instance_id. * @@ -1016,7 +1017,8 @@ struct TALER_MERCHANTDB_Plugin /** * Mark contract as paid and store the current @a session_id - * for which the contract was paid. + * for which the contract was paid. Deletes the underlying order + * and marks the locked stocks of the order as sold. * * @param cls closure * @param instance_id instance to mark contract as paid for diff --git a/src/lib/merchant_api_post_order_abort.c b/src/lib/merchant_api_post_order_abort.c @@ -122,8 +122,13 @@ check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *ph, GNUNET_break_op (0); return GNUNET_SYSERR; } + if (! json_is_array (refunds)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } num_refunds = json_array_size (refunds); - // FIXME: test for array first! { struct TALER_MERCHANT_AbortedCoin res[GNUNET_NZL (num_refunds)]; @@ -380,11 +385,18 @@ handle_abort_finished (void *cls, merchant is buggy (or API version conflict); just pass JSON reply to the application */ break; - case MHD_HTTP_CONFLICT: + case MHD_HTTP_FORBIDDEN: hr.ec = TALER_JSON_get_error_code (json); hr.hint = TALER_JSON_get_error_hint (json); break; - case MHD_HTTP_FORBIDDEN: + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the + application */ + break; + case MHD_HTTP_REQUEST_TIMEOUT: hr.ec = TALER_JSON_get_error_code (json); hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says one of @@ -392,12 +404,10 @@ handle_abort_finished (void *cls, this should never happen, we should pass the JSON reply to the application */ break; - case MHD_HTTP_NOT_FOUND: + case MHD_HTTP_PRECONDITION_FAILED: + /* Our *payment* already succeeded fully. */ hr.ec = TALER_JSON_get_error_code (json); hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the - application */ break; case MHD_HTTP_FAILED_DEPENDENCY: TALER_MERCHANT_parse_error_details_ (json,