diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_refund.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_refund.c | 365 |
1 files changed, 96 insertions, 269 deletions
diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c index be8a88df2..b8bcf7c60 100644 --- a/src/exchange/taler-exchange-httpd_refund.c +++ b/src/exchange/taler-exchange-httpd_refund.c @@ -50,22 +50,18 @@ reply_refund_success (struct MHD_Connection *connection, { struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangeSignatureP sig; - struct TALER_RefundConfirmationPS rc = { - .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND), - .purpose.size = htonl (sizeof (rc)), - .h_contract_terms = refund->h_contract_terms, - .coin_pub = *coin_pub, - .merchant = refund->merchant_pub, - .rtransaction_id = GNUNET_htonll (refund->rtransaction_id) - }; enum TALER_ErrorCode ec; - TALER_amount_hton (&rc.refund_amount, - &refund->refund_amount); if (TALER_EC_NONE != - (ec = TEH_keys_exchange_sign (&rc, - &pub, - &sig))) + (ec = TALER_exchange_online_refund_confirmation_sign ( + &TEH_keys_exchange_sign_, + &refund->h_contract_terms, + coin_pub, + &refund->merchant_pub, + refund->rtransaction_id, + &refund->refund_amount, + &pub, + &sig))) { return TALER_MHD_reply_with_ec (connection, ec, @@ -82,6 +78,28 @@ reply_refund_success (struct MHD_Connection *connection, /** + * Closure for refund_transaction(). + */ +struct RefundContext +{ + /** + * Details about the deposit operation. + */ + const struct TALER_EXCHANGEDB_Refund *refund; + + /** + * Deposit fee of the coin. + */ + struct TALER_Amount deposit_fee; + + /** + * Unique ID of the coin in known_coins. + */ + uint64_t known_coin_id; +}; + + +/** * Execute a "/refund" transaction. Returns a confirmation that the * refund was successful, or a failure if we are not aware of a * matching /deposit or if it is too late to do the refund. @@ -103,255 +121,68 @@ refund_transaction (void *cls, struct MHD_Connection *connection, MHD_RESULT *mhd_ret) { - const struct TALER_EXCHANGEDB_Refund *refund = cls; - struct TALER_EXCHANGEDB_TransactionList *tl; /* head of original list */ - struct TALER_EXCHANGEDB_TransactionList *tlx; /* head of sublist that applies to merchant and contract */ - struct TALER_EXCHANGEDB_TransactionList *tln; /* next element, during iteration */ - struct TALER_EXCHANGEDB_TransactionList *tlp; /* previous element in 'tl' list, during iteration */ + struct RefundContext *rctx = cls; + const struct TALER_EXCHANGEDB_Refund *refund = rctx->refund; enum GNUNET_DB_QueryStatus qs; - bool deposit_found; /* deposit_total initialized? */ - bool refund_found; /* refund_total initialized? */ - struct TALER_Amount deposit_total; - struct TALER_Amount refund_total; + bool not_found; + bool refund_ok; + bool conflict; + bool gone; - tl = NULL; - qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, - &refund->coin.coin_pub, - GNUNET_NO, - &tl); + /* Finally, store new refund data */ + qs = TEH_plugin->do_refund (TEH_plugin->cls, + refund, + &rctx->deposit_fee, + rctx->known_coin_id, + ¬_found, + &refund_ok, + &gone, + &conflict); if (0 > qs) { if (GNUNET_DB_STATUS_HARD_ERROR == qs) *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "coin transactions"); + "do refund"); return qs; } - deposit_found = false; - refund_found = false; - tlx = NULL; /* relevant subset of transactions */ - tln = NULL; - tlp = NULL; - for (struct TALER_EXCHANGEDB_TransactionList *tli = tl; - NULL != tli; - tli = tln) - { - tln = tli->next; - switch (tli->type) - { - case TALER_EXCHANGEDB_TT_DEPOSIT: - { - const struct TALER_EXCHANGEDB_DepositListEntry *dep; - - dep = tli->details.deposit; - if ( (0 == GNUNET_memcmp (&dep->merchant_pub, - &refund->details.merchant_pub)) && - (0 == GNUNET_memcmp (&dep->h_contract_terms, - &refund->details.h_contract_terms)) ) - { - /* check if we already send the money for this /deposit */ - if (dep->done) - { - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tlx); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tln); - /* money was already transferred to merchant, can no longer refund */ - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_GONE, - TALER_EC_EXCHANGE_REFUND_MERCHANT_ALREADY_PAID, - NULL); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - /* deposit applies and was not yet wired; add to total (it is NOT - the case that multiple deposits of the same coin for the same - contract are really allowed (see UNIQUE constraint on 'deposits' - table), but in case this changes we tolerate it with this code - anyway). */// - if (deposit_found) - { - GNUNET_assert (0 <= - TALER_amount_add (&deposit_total, - &deposit_total, - &dep->amount_with_fee)); - } - else - { - deposit_total = dep->amount_with_fee; - deposit_found = true; - } - /* move 'tli' from 'tl' to 'tlx' list */ - if (NULL == tlp) - tl = tln; - else - tlp->next = tln; - tli->next = tlx; - tlx = tli; - break; - } - else - { - tlp = tli; - } - break; - } - case TALER_EXCHANGEDB_TT_MELT: - /* Melts cannot be refunded, ignore here */ - break; - case TALER_EXCHANGEDB_TT_REFUND: - { - const struct TALER_EXCHANGEDB_RefundListEntry *ref; - - ref = tli->details.refund; - if ( (0 != GNUNET_memcmp (&ref->merchant_pub, - &refund->details.merchant_pub)) || - (0 != GNUNET_memcmp (&ref->h_contract_terms, - &refund->details.h_contract_terms)) ) - { - tlp = tli; - break; /* refund does not apply to our transaction */ - } - /* Check if existing refund request matches in everything but the amount */ - if ( (ref->rtransaction_id == - refund->details.rtransaction_id) && - (0 != TALER_amount_cmp (&ref->refund_amount, - &refund->details.refund_amount)) ) - { - /* Generate precondition failed response, with ONLY the conflicting entry */ - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tlx); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tln); - tli->next = NULL; - *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_PRECONDITION_FAILED, - TALER_JSON_pack_amount ("detail", - &ref->refund_amount), - TALER_JSON_pack_ec (TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT), - GNUNET_JSON_pack_array_steal ("history", - TEH_RESPONSE_compile_transaction_history ( - &refund->coin.coin_pub, - tli))); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tli); - return GNUNET_DB_STATUS_HARD_ERROR; - } - /* Check if existing refund request matches in everything including the amount */ - if ( (ref->rtransaction_id == - refund->details.rtransaction_id) && - (0 == TALER_amount_cmp (&ref->refund_amount, - &refund->details.refund_amount)) ) - { - /* we can blanketly approve, as this request is identical to one - we saw before */ - *mhd_ret = reply_refund_success (connection, - &refund->coin.coin_pub, - ref); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tlx); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - /* we still abort the transaction, as there is nothing to be - committed! */ - return GNUNET_DB_STATUS_HARD_ERROR; - } - /* We have another refund, that relates, add to total */ - if (refund_found) - { - GNUNET_assert (0 <= - TALER_amount_add (&refund_total, - &refund_total, - &ref->refund_amount)); - } - else - { - refund_total = ref->refund_amount; - refund_found = true; - } - /* move 'tli' from 'tl' to 'tlx' list */ - if (NULL == tlp) - tl = tln; - else - tlp->next = tln; - tli->next = tlx; - tlx = tli; - break; - } - case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP: - /* Recoups cannot be refunded, ignore here */ - break; - case TALER_EXCHANGEDB_TT_RECOUP: - /* Recoups cannot be refunded, ignore here */ - break; - case TALER_EXCHANGEDB_TT_RECOUP_REFRESH: - /* Recoups cannot be refunded, ignore here */ - break; - } - } - /* no need for 'tl' anymore, everything we may still care about is in tlx now */ - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - /* handle if deposit was NOT found */ - if (! deposit_found) + if (gone) { - TALER_LOG_WARNING ("Deposit to /refund was not found\n"); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tlx); *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_REFUND_DEPOSIT_NOT_FOUND, + MHD_HTTP_GONE, + TALER_EC_EXCHANGE_REFUND_MERCHANT_ALREADY_PAID, NULL); return GNUNET_DB_STATUS_HARD_ERROR; } - - /* check total refund amount is sufficiently low */ - if (refund_found) - GNUNET_break (0 <= - TALER_amount_add (&refund_total, - &refund_total, - &refund->details.refund_amount)); - else - refund_total = refund->details.refund_amount; - - if (1 == TALER_amount_cmp (&refund_total, - &deposit_total) ) + if (conflict) { - *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( + GNUNET_break_op (0); + *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( connection, - MHD_HTTP_CONFLICT, - GNUNET_JSON_pack_string ("detail", - "total amount refunded exceeds total amount deposited for this coin"), - TALER_JSON_pack_ec ( - TALER_EC_EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT), - GNUNET_JSON_pack_array_steal ("history", - TEH_RESPONSE_compile_transaction_history ( - &refund->coin.coin_pub, - tlx))); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tlx); + TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT, + &refund->coin.denom_pub_hash, + &refund->coin.coin_pub); return GNUNET_DB_STATUS_HARD_ERROR; } - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tlx); - - - /* Finally, store new refund data */ - qs = TEH_plugin->insert_refund (TEH_plugin->cls, - refund); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) + if (not_found) { - TALER_LOG_WARNING ("Failed to store /refund information in database\n"); *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "refund"); - return qs; + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_REFUND_DEPOSIT_NOT_FOUND, + NULL); + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (! refund_ok) + { + *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( + connection, + TALER_EC_EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT, + &refund->coin.denom_pub_hash, + &refund->coin.coin_pub); + return GNUNET_DB_STATUS_HARD_ERROR; } - /* Success or soft failure */ return qs; } @@ -370,32 +201,24 @@ static MHD_RESULT verify_and_execute_refund (struct MHD_Connection *connection, struct TALER_EXCHANGEDB_Refund *refund) { - struct TALER_DenominationHash denom_hash; + struct RefundContext rctx = { + .refund = refund + }; + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; + if (GNUNET_OK != + TALER_merchant_refund_verify (&refund->coin.coin_pub, + &refund->details.h_contract_terms, + refund->details.rtransaction_id, + &refund->details.refund_amount, + &refund->details.merchant_pub, + &refund->details.merchant_sig)) { - struct TALER_RefundRequestPS rr = { - .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND), - .purpose.size = htonl (sizeof (rr)), - .h_contract_terms = refund->details.h_contract_terms, - .coin_pub = refund->coin.coin_pub, - .merchant = refund->details.merchant_pub, - .rtransaction_id = GNUNET_htonll (refund->details.rtransaction_id) - }; - - TALER_amount_hton (&rr.refund_amount, - &refund->details.refund_amount); - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND, - &rr, - &refund->details.merchant_sig.eddsa_sig, - &refund->details.merchant_pub.eddsa_pub)) - { - TALER_LOG_WARNING ("Invalid signature on refund request\n"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_REFUND_MERCHANT_SIGNATURE_INVALID, - NULL); - } + TALER_LOG_WARNING ("Invalid signature on refund request\n"); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_REFUND_MERCHANT_SIGNATURE_INVALID, + NULL); } /* Fetch the coin's denomination (hash) */ @@ -404,15 +227,17 @@ verify_and_execute_refund (struct MHD_Connection *connection, qs = TEH_plugin->get_coin_denomination (TEH_plugin->cls, &refund->coin.coin_pub, - &denom_hash); + &rctx.known_coin_id, + &refund->coin.denom_pub_hash); if (0 > qs) { MHD_RESULT res; char *dhs; GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - dhs = GNUNET_STRINGS_data_to_string_alloc (&denom_hash, - sizeof (denom_hash)); + dhs = GNUNET_STRINGS_data_to_string_alloc ( + &refund->coin.denom_pub_hash, + sizeof (refund->coin.denom_pub_hash)); res = TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_REFUND_COIN_NOT_FOUND, @@ -427,7 +252,7 @@ verify_and_execute_refund (struct MHD_Connection *connection, struct TEH_DenominationKey *dk; MHD_RESULT mret; - dk = TEH_keys_denomination_by_hash (&denom_hash, + dk = TEH_keys_denomination_by_hash (&refund->coin.denom_pub_hash, connection, &mret); if (NULL == dk) @@ -437,7 +262,8 @@ verify_and_execute_refund (struct MHD_Connection *connection, GNUNET_break (0); return mret; } - refund->details.refund_fee = dk->meta.fee_refund; + refund->details.refund_fee = dk->meta.fees.refund; + rctx.deposit_fee = dk->meta.fees.deposit; } /* Finally run the actual transaction logic */ @@ -447,9 +273,10 @@ verify_and_execute_refund (struct MHD_Connection *connection, if (GNUNET_OK != TEH_DB_run_transaction (connection, "run refund", + TEH_MT_REQUEST_OTHER, &mhd_ret, &refund_transaction, - (void *) refund)) + &rctx)) { return mhd_ret; } |