implement aborting and getting refunds from failed payments
This commit is contained in:
parent
c8c03e381e
commit
1a66e232a5
@ -31,8 +31,8 @@ import {
|
||||
CoinPaySig,
|
||||
ContractTerms,
|
||||
Denomination,
|
||||
MerchantRefundPermission,
|
||||
PayReq,
|
||||
RefundPermission,
|
||||
TipResponse,
|
||||
WireDetail,
|
||||
} from "./talerTypes";
|
||||
@ -762,9 +762,25 @@ export interface WireFee {
|
||||
* the customer accepts a proposal. Includes refund status if applicable.
|
||||
*/
|
||||
export interface PurchaseRecord {
|
||||
/**
|
||||
* Hash of the contract terms.
|
||||
*/
|
||||
contractTermsHash: string;
|
||||
|
||||
/**
|
||||
* Contract terms we got from the merchant.
|
||||
*/
|
||||
contractTerms: ContractTerms;
|
||||
|
||||
/**
|
||||
* The payment request, ready to be send to the merchant's
|
||||
* /pay URL.
|
||||
*/
|
||||
payReq: PayReq;
|
||||
|
||||
/**
|
||||
* Signature from the merchant over the contract terms.
|
||||
*/
|
||||
merchantSig: string;
|
||||
|
||||
/**
|
||||
@ -773,8 +789,15 @@ export interface PurchaseRecord {
|
||||
*/
|
||||
finished: boolean;
|
||||
|
||||
refundsPending: { [refundSig: string]: RefundPermission };
|
||||
refundsDone: { [refundSig: string]: RefundPermission };
|
||||
/**
|
||||
* Pending refunds for the purchase.
|
||||
*/
|
||||
refundsPending: { [refundSig: string]: MerchantRefundPermission };
|
||||
|
||||
/**
|
||||
* Submitted refunds for the purchase.
|
||||
*/
|
||||
refundsDone: { [refundSig: string]: MerchantRefundPermission };
|
||||
|
||||
/**
|
||||
* When was the purchase made?
|
||||
@ -788,8 +811,25 @@ export interface PurchaseRecord {
|
||||
*/
|
||||
timestamp_refund: number;
|
||||
|
||||
/**
|
||||
* Last session id that we submitted to /pay (if any).
|
||||
*/
|
||||
lastSessionSig: string | undefined;
|
||||
|
||||
/**
|
||||
* Last session signature that we submitted to /pay (if any).
|
||||
*/
|
||||
lastSessionId: string | undefined;
|
||||
|
||||
/**
|
||||
* An abort (with refund) was requested for this (incomplete!) purchase.
|
||||
*/
|
||||
abortRequested: boolean;
|
||||
|
||||
/**
|
||||
* The abort (with refund) was completed for this (incomplete!) purchase.
|
||||
*/
|
||||
abortDone: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
@ -27,28 +27,28 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:73
|
||||
#: src/webex/pages/confirm-contract.tsx:74
|
||||
#, c-format
|
||||
msgid "show more details\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:87
|
||||
#: src/webex/pages/confirm-contract.tsx:88
|
||||
#, c-format
|
||||
msgid "Accepted exchanges:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:92
|
||||
#: src/webex/pages/confirm-contract.tsx:93
|
||||
#, c-format
|
||||
msgid "Exchanges in the wallet:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:200
|
||||
#: src/webex/pages/confirm-contract.tsx:211
|
||||
#, c-format
|
||||
msgid "You have insufficient funds of the requested currency in your wallet."
|
||||
msgstr ""
|
||||
|
||||
#. tslint:disable-next-line:max-line-length
|
||||
#: src/webex/pages/confirm-contract.tsx:202
|
||||
#: src/webex/pages/confirm-contract.tsx:213
|
||||
#, c-format
|
||||
msgid ""
|
||||
"You do not have any funds from an exchange that is accepted by this "
|
||||
@ -56,16 +56,21 @@ msgid ""
|
||||
"wallet."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:280
|
||||
#, c-format
|
||||
msgid "The merchant%1$s offers you to purchase:\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:301
|
||||
#: src/webex/pages/confirm-contract.tsx:305
|
||||
#, fuzzy, c-format
|
||||
msgid "Confirm payment"
|
||||
msgstr "Bezahlung bestätigen"
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:314
|
||||
#, c-format
|
||||
msgid "Submitting payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:349
|
||||
#, c-format
|
||||
msgid "The merchant%1$s offers you to purchase:\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:126
|
||||
#, c-format
|
||||
msgid "Select"
|
||||
@ -154,7 +159,7 @@ msgstr ""
|
||||
|
||||
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||
#. TODO:generic error reporting function or component.
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:505 src/webex/pages/tip.tsx:155
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:505 src/webex/pages/tip.tsx:153
|
||||
#, c-format
|
||||
msgid "Fatal error: \"%1$s\"."
|
||||
msgstr ""
|
||||
|
@ -27,28 +27,28 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:73
|
||||
#: src/webex/pages/confirm-contract.tsx:74
|
||||
#, c-format
|
||||
msgid "show more details\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:87
|
||||
#: src/webex/pages/confirm-contract.tsx:88
|
||||
#, c-format
|
||||
msgid "Accepted exchanges:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:92
|
||||
#: src/webex/pages/confirm-contract.tsx:93
|
||||
#, c-format
|
||||
msgid "Exchanges in the wallet:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:200
|
||||
#: src/webex/pages/confirm-contract.tsx:211
|
||||
#, c-format
|
||||
msgid "You have insufficient funds of the requested currency in your wallet."
|
||||
msgstr ""
|
||||
|
||||
#. tslint:disable-next-line:max-line-length
|
||||
#: src/webex/pages/confirm-contract.tsx:202
|
||||
#: src/webex/pages/confirm-contract.tsx:213
|
||||
#, c-format
|
||||
msgid ""
|
||||
"You do not have any funds from an exchange that is accepted by this "
|
||||
@ -56,16 +56,21 @@ msgid ""
|
||||
"wallet."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:280
|
||||
#, c-format
|
||||
msgid "The merchant%1$s offers you to purchase:\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:301
|
||||
#: src/webex/pages/confirm-contract.tsx:305
|
||||
#, c-format
|
||||
msgid "Confirm payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:314
|
||||
#, c-format
|
||||
msgid "Submitting payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:349
|
||||
#, c-format
|
||||
msgid "The merchant%1$s offers you to purchase:\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:126
|
||||
#, c-format
|
||||
msgid "Select"
|
||||
@ -154,7 +159,7 @@ msgstr ""
|
||||
|
||||
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||
#. TODO:generic error reporting function or component.
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:505 src/webex/pages/tip.tsx:155
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:505 src/webex/pages/tip.tsx:153
|
||||
#, c-format
|
||||
msgid "Fatal error: \"%1$s\"."
|
||||
msgstr ""
|
||||
|
@ -27,28 +27,28 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:73
|
||||
#: src/webex/pages/confirm-contract.tsx:74
|
||||
#, c-format
|
||||
msgid "show more details\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:87
|
||||
#: src/webex/pages/confirm-contract.tsx:88
|
||||
#, c-format
|
||||
msgid "Accepted exchanges:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:92
|
||||
#: src/webex/pages/confirm-contract.tsx:93
|
||||
#, c-format
|
||||
msgid "Exchanges in the wallet:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:200
|
||||
#: src/webex/pages/confirm-contract.tsx:211
|
||||
#, c-format
|
||||
msgid "You have insufficient funds of the requested currency in your wallet."
|
||||
msgstr ""
|
||||
|
||||
#. tslint:disable-next-line:max-line-length
|
||||
#: src/webex/pages/confirm-contract.tsx:202
|
||||
#: src/webex/pages/confirm-contract.tsx:213
|
||||
#, c-format
|
||||
msgid ""
|
||||
"You do not have any funds from an exchange that is accepted by this "
|
||||
@ -56,16 +56,21 @@ msgid ""
|
||||
"wallet."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:280
|
||||
#, c-format
|
||||
msgid "The merchant%1$s offers you to purchase:\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:301
|
||||
#: src/webex/pages/confirm-contract.tsx:305
|
||||
#, c-format
|
||||
msgid "Confirm payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:314
|
||||
#, c-format
|
||||
msgid "Submitting payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:349
|
||||
#, c-format
|
||||
msgid "The merchant%1$s offers you to purchase:\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:126
|
||||
#, c-format
|
||||
msgid "Select"
|
||||
@ -154,7 +159,7 @@ msgstr ""
|
||||
|
||||
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||
#. TODO:generic error reporting function or component.
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:505 src/webex/pages/tip.tsx:155
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:505 src/webex/pages/tip.tsx:153
|
||||
#, c-format
|
||||
msgid "Fatal error: \"%1$s\"."
|
||||
msgstr ""
|
||||
|
@ -27,28 +27,28 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:73
|
||||
#: src/webex/pages/confirm-contract.tsx:74
|
||||
#, c-format
|
||||
msgid "show more details\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:87
|
||||
#: src/webex/pages/confirm-contract.tsx:88
|
||||
#, c-format
|
||||
msgid "Accepted exchanges:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:92
|
||||
#: src/webex/pages/confirm-contract.tsx:93
|
||||
#, c-format
|
||||
msgid "Exchanges in the wallet:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:200
|
||||
#: src/webex/pages/confirm-contract.tsx:211
|
||||
#, c-format
|
||||
msgid "You have insufficient funds of the requested currency in your wallet."
|
||||
msgstr ""
|
||||
|
||||
#. tslint:disable-next-line:max-line-length
|
||||
#: src/webex/pages/confirm-contract.tsx:202
|
||||
#: src/webex/pages/confirm-contract.tsx:213
|
||||
#, c-format
|
||||
msgid ""
|
||||
"You do not have any funds from an exchange that is accepted by this "
|
||||
@ -56,16 +56,21 @@ msgid ""
|
||||
"wallet."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:280
|
||||
#, c-format
|
||||
msgid "The merchant%1$s offers you to purchase:\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:301
|
||||
#: src/webex/pages/confirm-contract.tsx:305
|
||||
#, c-format
|
||||
msgid "Confirm payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:314
|
||||
#, c-format
|
||||
msgid "Submitting payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:349
|
||||
#, c-format
|
||||
msgid "The merchant%1$s offers you to purchase:\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:126
|
||||
#, c-format
|
||||
msgid "Select"
|
||||
@ -154,7 +159,7 @@ msgstr ""
|
||||
|
||||
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||
#. TODO:generic error reporting function or component.
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:505 src/webex/pages/tip.tsx:155
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:505 src/webex/pages/tip.tsx:153
|
||||
#, c-format
|
||||
msgid "Fatal error: \"%1$s\"."
|
||||
msgstr ""
|
||||
|
@ -39,12 +39,15 @@ strings['de'] = {
|
||||
"You do not have any funds from an exchange that is accepted by this merchant. None of the exchanges accepted by the merchant is known to your wallet.": [
|
||||
""
|
||||
],
|
||||
"The merchant%1$s offers you to purchase:\n": [
|
||||
""
|
||||
],
|
||||
"Confirm payment": [
|
||||
"Bezahlung bestätigen"
|
||||
],
|
||||
"Submitting payment": [
|
||||
""
|
||||
],
|
||||
"The merchant%1$s offers you to purchase:\n": [
|
||||
""
|
||||
],
|
||||
"Select": [
|
||||
""
|
||||
],
|
||||
@ -228,10 +231,13 @@ strings['en-US'] = {
|
||||
"You do not have any funds from an exchange that is accepted by this merchant. None of the exchanges accepted by the merchant is known to your wallet.": [
|
||||
""
|
||||
],
|
||||
"The merchant%1$s offers you to purchase:\n": [
|
||||
"Confirm payment": [
|
||||
""
|
||||
],
|
||||
"Confirm payment": [
|
||||
"Submitting payment": [
|
||||
""
|
||||
],
|
||||
"The merchant%1$s offers you to purchase:\n": [
|
||||
""
|
||||
],
|
||||
"Select": [
|
||||
@ -417,10 +423,13 @@ strings['fr'] = {
|
||||
"You do not have any funds from an exchange that is accepted by this merchant. None of the exchanges accepted by the merchant is known to your wallet.": [
|
||||
""
|
||||
],
|
||||
"The merchant%1$s offers you to purchase:\n": [
|
||||
"Confirm payment": [
|
||||
""
|
||||
],
|
||||
"Confirm payment": [
|
||||
"Submitting payment": [
|
||||
""
|
||||
],
|
||||
"The merchant%1$s offers you to purchase:\n": [
|
||||
""
|
||||
],
|
||||
"Select": [
|
||||
@ -606,10 +615,13 @@ strings['it'] = {
|
||||
"You do not have any funds from an exchange that is accepted by this merchant. None of the exchanges accepted by the merchant is known to your wallet.": [
|
||||
""
|
||||
],
|
||||
"The merchant%1$s offers you to purchase:\n": [
|
||||
"Confirm payment": [
|
||||
""
|
||||
],
|
||||
"Confirm payment": [
|
||||
"Submitting payment": [
|
||||
""
|
||||
],
|
||||
"The merchant%1$s offers you to purchase:\n": [
|
||||
""
|
||||
],
|
||||
"Select": [
|
||||
|
@ -27,28 +27,28 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:73
|
||||
#: src/webex/pages/confirm-contract.tsx:74
|
||||
#, c-format
|
||||
msgid "show more details\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:87
|
||||
#: src/webex/pages/confirm-contract.tsx:88
|
||||
#, c-format
|
||||
msgid "Accepted exchanges:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:92
|
||||
#: src/webex/pages/confirm-contract.tsx:93
|
||||
#, c-format
|
||||
msgid "Exchanges in the wallet:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:200
|
||||
#: src/webex/pages/confirm-contract.tsx:211
|
||||
#, c-format
|
||||
msgid "You have insufficient funds of the requested currency in your wallet."
|
||||
msgstr ""
|
||||
|
||||
#. tslint:disable-next-line:max-line-length
|
||||
#: src/webex/pages/confirm-contract.tsx:202
|
||||
#: src/webex/pages/confirm-contract.tsx:213
|
||||
#, c-format
|
||||
msgid ""
|
||||
"You do not have any funds from an exchange that is accepted by this "
|
||||
@ -56,16 +56,21 @@ msgid ""
|
||||
"wallet."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:280
|
||||
#, c-format
|
||||
msgid "The merchant%1$s offers you to purchase:\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:301
|
||||
#: src/webex/pages/confirm-contract.tsx:305
|
||||
#, c-format
|
||||
msgid "Confirm payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:314
|
||||
#, c-format
|
||||
msgid "Submitting payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-contract.tsx:349
|
||||
#, c-format
|
||||
msgid "The merchant%1$s offers you to purchase:\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:126
|
||||
#, c-format
|
||||
msgid "Select"
|
||||
@ -154,7 +159,7 @@ msgstr ""
|
||||
|
||||
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||
#. TODO:generic error reporting function or component.
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:505 src/webex/pages/tip.tsx:155
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:505 src/webex/pages/tip.tsx:153
|
||||
#, c-format
|
||||
msgid "Fatal error: \"%1$s\"."
|
||||
msgstr ""
|
||||
|
@ -475,42 +475,117 @@ export interface PayReq {
|
||||
/**
|
||||
* Refund permission in the format that the merchant gives it to us.
|
||||
*/
|
||||
export interface RefundPermission {
|
||||
@Checkable.Class()
|
||||
export class MerchantRefundPermission {
|
||||
/**
|
||||
* Amount to be refunded.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
refund_amount: AmountJson;
|
||||
|
||||
/**
|
||||
* Fee for the refund.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
refund_fee: AmountJson;
|
||||
|
||||
/**
|
||||
* Contract terms hash to identify the contract that this
|
||||
* refund is for.
|
||||
*/
|
||||
h_contract_terms: string;
|
||||
|
||||
/**
|
||||
* Public key of the coin being refunded.
|
||||
*/
|
||||
@Checkable.String
|
||||
coin_pub: string;
|
||||
|
||||
/**
|
||||
* Refund transaction ID between merchant and exchange.
|
||||
*/
|
||||
@Checkable.Number
|
||||
rtransaction_id: number;
|
||||
|
||||
/**
|
||||
* Public key of the merchant.
|
||||
*/
|
||||
merchant_pub: string;
|
||||
|
||||
/**
|
||||
* Signature made by the merchant over the refund permission.
|
||||
*/
|
||||
@Checkable.String
|
||||
merchant_sig: string;
|
||||
|
||||
/**
|
||||
* Create a MerchantRefundPermission from untyped JSON.
|
||||
*/
|
||||
static checked: (obj: any) => MerchantRefundPermission;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refund request sent to the exchange.
|
||||
*/
|
||||
export interface RefundRequest {
|
||||
/**
|
||||
* Amount to be refunded, can be a fraction of the
|
||||
* coin's total deposit value (including deposit fee);
|
||||
* must be larger than the refund fee.
|
||||
*/
|
||||
refund_amount: AmountJson;
|
||||
|
||||
/**
|
||||
* Refund fee associated with the given coin.
|
||||
* must be smaller than the refund amount.
|
||||
*/
|
||||
refund_fee: AmountJson;
|
||||
|
||||
/**
|
||||
* SHA-512 hash of the contact of the merchant with the customer.
|
||||
*/
|
||||
h_contract_terms: string;
|
||||
|
||||
/**
|
||||
* coin's public key, both ECDHE and EdDSA.
|
||||
*/
|
||||
coin_pub: string;
|
||||
|
||||
/**
|
||||
* 64-bit transaction id of the refund transaction between merchant and customer
|
||||
*/
|
||||
rtransaction_id: number;
|
||||
|
||||
/**
|
||||
* EdDSA public key of the merchant.
|
||||
*/
|
||||
merchant_pub: string;
|
||||
|
||||
/**
|
||||
* EdDSA signature of the merchant affirming the refund.
|
||||
*/
|
||||
merchant_sig: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Response for a refund pickup or a /pay in abort mode.
|
||||
*/
|
||||
@Checkable.Class()
|
||||
export class MerchantRefundResponse {
|
||||
/**
|
||||
* Public key of the merchant
|
||||
*/
|
||||
@Checkable.String
|
||||
merchant_pub: string;
|
||||
|
||||
/**
|
||||
* Contract terms hash of the contract that
|
||||
* is being refunded.
|
||||
*/
|
||||
@Checkable.String
|
||||
h_contract_terms: string;
|
||||
|
||||
/**
|
||||
* The signed refund permissions, to be sent to the exchange.
|
||||
*/
|
||||
@Checkable.List(Checkable.Value(() => MerchantRefundPermission))
|
||||
refund_permissions: MerchantRefundPermission[];
|
||||
|
||||
/**
|
||||
* Create a MerchantRefundReponse from untyped JSON.
|
||||
*/
|
||||
static checked: (obj: any) => MerchantRefundResponse;
|
||||
}
|
||||
|
||||
|
||||
|
176
src/wallet.ts
176
src/wallet.ts
@ -76,10 +76,12 @@ import {
|
||||
Denomination,
|
||||
ExchangeHandle,
|
||||
KeysJson,
|
||||
MerchantRefundPermission,
|
||||
MerchantRefundResponse,
|
||||
PayReq,
|
||||
PaybackConfirmation,
|
||||
Proposal,
|
||||
RefundPermission,
|
||||
RefundRequest,
|
||||
TipPlanchetDetail,
|
||||
TipResponse,
|
||||
TipToken,
|
||||
@ -648,6 +650,8 @@ export class Wallet {
|
||||
order_id: proposal.contractTerms.order_id,
|
||||
};
|
||||
const t: PurchaseRecord = {
|
||||
abortDone: false,
|
||||
abortRequested: false,
|
||||
contractTerms: proposal.contractTerms,
|
||||
contractTermsHash: proposal.contractTermsHash,
|
||||
finished: false,
|
||||
@ -676,7 +680,6 @@ export class Wallet {
|
||||
* Returns an id for it to retrieve it later.
|
||||
*/
|
||||
async downloadProposal(url: string): Promise<number> {
|
||||
|
||||
const oldProposal = await this.q().getIndexed(Stores.proposals.urlIndex, url);
|
||||
if (oldProposal) {
|
||||
return oldProposal.id!;
|
||||
@ -716,13 +719,37 @@ export class Wallet {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
async refundFailedPay(proposalId: number) {
|
||||
console.log(`refunding failed payment with proposal id ${proposalId}`);
|
||||
const proposal: ProposalDownloadRecord|undefined = await this.q().get(Stores.proposals, proposalId);
|
||||
|
||||
if (!proposal) {
|
||||
throw Error(`proposal with id ${proposalId} not found`);
|
||||
}
|
||||
|
||||
const purchase = await this.q().get(Stores.purchases, proposal.contractTermsHash);
|
||||
if (!purchase) {
|
||||
throw Error("purchase not found for proposal");
|
||||
}
|
||||
|
||||
if (purchase.finished) {
|
||||
throw Error("can't auto-refund finished purchase");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async submitPay(contractTermsHash: string, sessionId: string | undefined): Promise<ConfirmPayResult> {
|
||||
const purchase = await this.q().get(Stores.purchases, contractTermsHash);
|
||||
if (!purchase) {
|
||||
throw Error("Purchase not found: " + contractTermsHash);
|
||||
}
|
||||
if (purchase.abortRequested) {
|
||||
throw Error("not submitting payment for aborted purchase");
|
||||
}
|
||||
let resp;
|
||||
const payReq = { ...purchase.payReq, session_id: sessionId };
|
||||
|
||||
try {
|
||||
const config = {
|
||||
headers: { "Content-Type": "application/json;charset=UTF-8" },
|
||||
@ -737,14 +764,6 @@ export class Wallet {
|
||||
}
|
||||
const merchantResp = resp.data;
|
||||
console.log("got success from pay_url");
|
||||
const fu = new URI(purchase.contractTerms.fulfillment_url);
|
||||
fu.addSearch("order_id", purchase.contractTerms.order_id);
|
||||
if (merchantResp.session_sig) {
|
||||
purchase.lastSessionSig = merchantResp.session_sig;
|
||||
purchase.lastSessionId = sessionId;
|
||||
fu.addSearch("session_sig", merchantResp.session_sig);
|
||||
await this.q().put(Stores.purchases, purchase).finish();
|
||||
}
|
||||
|
||||
const merchantPub = purchase.contractTerms.merchant_pub;
|
||||
const valid: boolean = await (
|
||||
@ -767,6 +786,14 @@ export class Wallet {
|
||||
modifiedCoins.push(c);
|
||||
}
|
||||
|
||||
const fu = new URI(purchase.contractTerms.fulfillment_url);
|
||||
fu.addSearch("order_id", purchase.contractTerms.order_id);
|
||||
if (merchantResp.session_sig) {
|
||||
purchase.lastSessionSig = merchantResp.session_sig;
|
||||
purchase.lastSessionId = sessionId;
|
||||
fu.addSearch("session_sig", merchantResp.session_sig);
|
||||
}
|
||||
|
||||
await this.q()
|
||||
.putAll(Stores.coins, modifiedCoins)
|
||||
.put(Stores.purchases, purchase)
|
||||
@ -782,8 +809,7 @@ export class Wallet {
|
||||
|
||||
|
||||
/**
|
||||
* Add a contract to the wallet and sign coins,
|
||||
* but do not send them yet.
|
||||
* Add a contract to the wallet and sign coins, and send them.
|
||||
*/
|
||||
async confirmPay(proposalId: number, sessionId: string | undefined): Promise<ConfirmPayResult> {
|
||||
console.log(`executing confirmPay with proposalId ${proposalId} and sessionId ${sessionId}`);
|
||||
@ -860,6 +886,7 @@ export class Wallet {
|
||||
return sp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if payment for an offer is possible, or if the offer has already
|
||||
* been payed for.
|
||||
@ -1295,6 +1322,7 @@ export class Wallet {
|
||||
return wiJson;
|
||||
}
|
||||
|
||||
|
||||
async getPossibleDenoms(exchangeBaseUrl: string) {
|
||||
return (
|
||||
this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex,
|
||||
@ -2522,46 +2550,13 @@ export class Wallet {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a refund, return the contract hash for the contract
|
||||
* that was involved in the refund.
|
||||
*/
|
||||
async acceptRefund(refundUrl: string): Promise<string> {
|
||||
console.log("processing refund");
|
||||
let resp;
|
||||
try {
|
||||
const config = {
|
||||
validateStatus: (s: number) => s === 200,
|
||||
};
|
||||
resp = await axios.get(refundUrl, config);
|
||||
} catch (e) {
|
||||
console.log("error downloading refund permission", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// FIXME: validate schema
|
||||
const refundPermissions = resp.data.refund_permissions;
|
||||
async acceptRefundResponse(refundResponse: MerchantRefundResponse): Promise<string> {
|
||||
const refundPermissions = refundResponse.refund_permissions;
|
||||
|
||||
if (!refundPermissions.length) {
|
||||
console.warn("got empty refund list");
|
||||
throw Error("empty refund");
|
||||
}
|
||||
const hc = refundPermissions[0].h_contract_terms;
|
||||
if (!hc) {
|
||||
throw Error("h_contract_terms missing in refund permission");
|
||||
}
|
||||
const m = refundPermissions[0].merchant_pub;
|
||||
if (!hc) {
|
||||
throw Error("merchant_pub missing in refund permission");
|
||||
}
|
||||
for (const perm of refundPermissions) {
|
||||
if (perm.h_contract_terms !== hc) {
|
||||
throw Error("h_contract_terms different in refund permission");
|
||||
}
|
||||
if (perm.merchant_pub !== m) {
|
||||
throw Error("merchant_pub different in refund permission");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add refund to purchase if not already added.
|
||||
@ -2582,6 +2577,8 @@ export class Wallet {
|
||||
return t;
|
||||
}
|
||||
|
||||
const hc = refundResponse.h_contract_terms;
|
||||
|
||||
// Add the refund permissions to the purchase within a DB transaction
|
||||
await this.q().mutate(Stores.purchases, hc, f).finish();
|
||||
this.notifier.notify();
|
||||
@ -2589,7 +2586,29 @@ export class Wallet {
|
||||
// Start submitting it but don't wait for it here.
|
||||
this.submitRefunds(hc);
|
||||
|
||||
return refundPermissions[0].h_contract_terms;
|
||||
return hc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Accept a refund, return the contract hash for the contract
|
||||
* that was involved in the refund.
|
||||
*/
|
||||
async acceptRefund(refundUrl: string): Promise<string> {
|
||||
console.log("processing refund");
|
||||
let resp;
|
||||
try {
|
||||
const config = {
|
||||
validateStatus: (s: number) => s === 200,
|
||||
};
|
||||
resp = await axios.get(refundUrl, config);
|
||||
} catch (e) {
|
||||
console.log("error downloading refund permission", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
const refundResponse = MerchantRefundResponse.checked(resp.data);
|
||||
return this.acceptRefundResponse(refundResponse);
|
||||
}
|
||||
|
||||
|
||||
@ -2605,11 +2624,20 @@ export class Wallet {
|
||||
}
|
||||
for (const pk of pendingKeys) {
|
||||
const perm = purchase.refundsPending[pk];
|
||||
const req: RefundRequest = {
|
||||
coin_pub: perm.coin_pub,
|
||||
h_contract_terms: purchase.contractTermsHash,
|
||||
merchant_pub: purchase.contractTerms.merchant_pub,
|
||||
merchant_sig: perm.merchant_sig,
|
||||
refund_amount: perm.refund_amount,
|
||||
refund_fee: perm.refund_fee,
|
||||
rtransaction_id: perm.rtransaction_id,
|
||||
};
|
||||
console.log("sending refund permission", perm);
|
||||
// FIXME: not correct once we support multiple exchanges per payment
|
||||
const exchangeUrl = purchase.payReq.coins[0].exchange_url;
|
||||
const reqUrl = (new URI("refund")).absoluteTo(exchangeUrl);
|
||||
const resp = await this.http.postJson(reqUrl.href(), perm);
|
||||
const resp = await this.http.postJson(reqUrl.href(), req);
|
||||
if (resp.status !== 200) {
|
||||
console.error("refund failed", resp);
|
||||
continue;
|
||||
@ -2654,7 +2682,7 @@ export class Wallet {
|
||||
return this.q().get(Stores.purchases, contractTermsHash);
|
||||
}
|
||||
|
||||
async getFullRefundFees(refundPermissions: RefundPermission[]): Promise<AmountJson> {
|
||||
async getFullRefundFees(refundPermissions: MerchantRefundPermission[]): Promise<AmountJson> {
|
||||
if (refundPermissions.length === 0) {
|
||||
throw Error("no refunds given");
|
||||
}
|
||||
@ -2829,6 +2857,54 @@ export class Wallet {
|
||||
}
|
||||
|
||||
|
||||
async abortFailedPayment(contractTermsHash: string): Promise<void> {
|
||||
const purchase = await this.q().get(Stores.purchases, contractTermsHash);
|
||||
if (!purchase) {
|
||||
throw Error("Purchase not found, unable to abort with refund");
|
||||
}
|
||||
if (purchase.finished) {
|
||||
throw Error("Purchase already finished, not aborting");
|
||||
}
|
||||
if (purchase.abortDone) {
|
||||
console.warn("abort requested on already aborted purchase");
|
||||
return;
|
||||
}
|
||||
|
||||
purchase.abortRequested = true;
|
||||
|
||||
// From now on, we can't retry payment anymore,
|
||||
// so mark this in the DB in case the /pay abort
|
||||
// does not complete on the first try.
|
||||
await this.q().put(Stores.purchases, purchase);
|
||||
|
||||
let resp;
|
||||
|
||||
const abortReq = { ...purchase.payReq, mode: "abort-refund" };
|
||||
|
||||
try {
|
||||
const config = {
|
||||
headers: { "Content-Type": "application/json;charset=UTF-8" },
|
||||
timeout: 5000, /* 5 seconds */
|
||||
validateStatus: (s: number) => s === 200,
|
||||
};
|
||||
resp = await axios.post(purchase.contractTerms.pay_url, abortReq, config);
|
||||
} catch (e) {
|
||||
// Gives the user the option to retry / abort and refresh
|
||||
console.log("aborting payment failed", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
const refundResponse = MerchantRefundResponse.checked(resp.data);
|
||||
await this.acceptRefundResponse(refundResponse);
|
||||
|
||||
const markAbortDone = (p: PurchaseRecord) => {
|
||||
p.abortDone = true;
|
||||
return p;
|
||||
};
|
||||
await this.q().mutate(Stores.purchases, purchase.contractTermsHash, markAbortDone);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Synchronously get the paid URL for a resource from the plain fulfillment
|
||||
* URL. Returns undefined if the fulfillment URL is not a resource that was
|
||||
|
@ -170,7 +170,7 @@ export interface MessageMap {
|
||||
response: dbTypes.PurchaseRecord;
|
||||
};
|
||||
"get-full-refund-fees": {
|
||||
request: { refundPermissions: talerTypes.RefundPermission[] };
|
||||
request: { refundPermissions: talerTypes.MerchantRefundPermission[] };
|
||||
response: AmountJson;
|
||||
};
|
||||
"accept-tip": {
|
||||
@ -201,6 +201,10 @@ export interface MessageMap {
|
||||
request: { refundUrl: string }
|
||||
response: string;
|
||||
};
|
||||
"abort-failed-payment": {
|
||||
request: { contractTermsHash: string }
|
||||
response: void;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,6 +40,7 @@ import * as wxApi from "../wxApi";
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import URI = require("urijs");
|
||||
import { WalletApiError } from "../wxApi";
|
||||
|
||||
|
||||
interface DetailState {
|
||||
@ -111,7 +112,8 @@ interface ContractPromptProps {
|
||||
interface ContractPromptState {
|
||||
proposalId: number | undefined;
|
||||
proposal: ProposalDownloadRecord | undefined;
|
||||
error: string | null;
|
||||
checkPayError: string | undefined;
|
||||
confirmPayError: object | undefined;
|
||||
payDisabled: boolean;
|
||||
alreadyPaid: boolean;
|
||||
exchanges: ExchangeRecord[] | undefined;
|
||||
@ -124,21 +126,30 @@ interface ContractPromptState {
|
||||
payStatus?: CheckPayResult;
|
||||
replaying: boolean;
|
||||
payInProgress: boolean;
|
||||
payAttempt: number;
|
||||
working: boolean;
|
||||
abortDone: boolean;
|
||||
abortStarted: boolean;
|
||||
}
|
||||
|
||||
class ContractPrompt extends React.Component<ContractPromptProps, ContractPromptState> {
|
||||
constructor(props: ContractPromptProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
abortDone: false,
|
||||
abortStarted: false,
|
||||
alreadyPaid: false,
|
||||
error: null,
|
||||
checkPayError: undefined,
|
||||
confirmPayError: undefined,
|
||||
exchanges: undefined,
|
||||
holdCheck: false,
|
||||
payAttempt: 0,
|
||||
payDisabled: true,
|
||||
payInProgress: false,
|
||||
proposal: undefined,
|
||||
proposalId: props.proposalId,
|
||||
replaying: false,
|
||||
working: false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -154,7 +165,7 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
|
||||
if (this.props.resourceUrl) {
|
||||
const p = await wxApi.queryPaymentByFulfillmentUrl(this.props.resourceUrl);
|
||||
console.log("query for resource url", this.props.resourceUrl, "result", p);
|
||||
if (p) {
|
||||
if (p && p.finished) {
|
||||
if (p.lastSessionSig === undefined || p.lastSessionSig === this.props.sessionId) {
|
||||
const nextUrl = new URI(p.contractTerms.fulfillment_url);
|
||||
nextUrl.addSearch("order_id", p.contractTerms.order_id);
|
||||
@ -166,6 +177,8 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
|
||||
} else {
|
||||
// We're in a new session
|
||||
this.setState({ replaying: true });
|
||||
// FIXME: This could also go wrong. However the payment
|
||||
// was already successful once, so we can just retry and not refund it.
|
||||
const payResult = await wxApi.submitPay(p.contractTermsHash, this.props.sessionId);
|
||||
console.log("payResult", payResult);
|
||||
location.replace(payResult.nextUrl);
|
||||
@ -206,24 +219,24 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
|
||||
const acceptedExchangePubs = this.state.proposal.contractTerms.exchanges.map((e) => e.master_pub);
|
||||
const ex = this.state.exchanges.find((e) => acceptedExchangePubs.indexOf(e.masterPublicKey) >= 0);
|
||||
if (ex) {
|
||||
this.setState({ error: msgInsufficient });
|
||||
this.setState({ checkPayError: msgInsufficient });
|
||||
} else {
|
||||
this.setState({ error: msgNoMatch });
|
||||
this.setState({ checkPayError: msgNoMatch });
|
||||
}
|
||||
} else {
|
||||
this.setState({ error: msgInsufficient });
|
||||
this.setState({ checkPayError: msgInsufficient });
|
||||
}
|
||||
this.setState({ payDisabled: true });
|
||||
} else if (payStatus.status === "paid") {
|
||||
this.setState({ alreadyPaid: true, payDisabled: false, error: null, payStatus });
|
||||
this.setState({ alreadyPaid: true, payDisabled: false, checkPayError: undefined, payStatus });
|
||||
} else {
|
||||
this.setState({ payDisabled: false, error: null, payStatus });
|
||||
this.setState({ payDisabled: false, checkPayError: undefined, payStatus });
|
||||
}
|
||||
}
|
||||
|
||||
async doPayment() {
|
||||
const proposal = this.state.proposal;
|
||||
this.setState({holdCheck: true});
|
||||
this.setState({ holdCheck: true, payAttempt: this.state.payAttempt + 1});
|
||||
if (!proposal) {
|
||||
return;
|
||||
}
|
||||
@ -234,11 +247,17 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
|
||||
}
|
||||
console.log("confirmPay with", proposalId, "and", this.props.sessionId);
|
||||
let payResult;
|
||||
this.setState({ working: true });
|
||||
try {
|
||||
payResult = await wxApi.confirmPay(proposalId, this.props.sessionId);
|
||||
} catch (e) {
|
||||
|
||||
if (!(e instanceof WalletApiError)) {
|
||||
throw e;
|
||||
}
|
||||
this.setState({ confirmPayError: e.detail });
|
||||
return;
|
||||
} finally {
|
||||
this.setState({ working: false });
|
||||
}
|
||||
console.log("payResult", payResult);
|
||||
document.location.href = payResult.nextUrl;
|
||||
@ -246,6 +265,17 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
|
||||
}
|
||||
|
||||
|
||||
async abortPayment() {
|
||||
const proposal = this.state.proposal;
|
||||
this.setState({ holdCheck: true, abortStarted: true });
|
||||
if (!proposal) {
|
||||
return;
|
||||
}
|
||||
wxApi.abortFailedPayment(proposal.contractTermsHash);
|
||||
this.setState({ abortDone: true });
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (this.props.contractUrl === undefined && this.props.proposalId === undefined) {
|
||||
return <span>Error: either contractUrl or proposalId must be given</span>;
|
||||
@ -272,18 +302,72 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
|
||||
let products = null;
|
||||
if (c.products.length) {
|
||||
products = (
|
||||
<>
|
||||
<div>
|
||||
<span>The following items are included:</span>
|
||||
<ul>
|
||||
{c.products.map(
|
||||
(p: any, i: number) => (<li key={i}>{p.description}: {renderAmount(p.price)}</li>))
|
||||
}
|
||||
</ul>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const ConfirmButton = () => (
|
||||
<button className="pure-button button-success"
|
||||
disabled={this.state.payDisabled}
|
||||
onClick={() => this.doPayment()}>
|
||||
{i18n.str`Confirm payment`}
|
||||
</button>
|
||||
);
|
||||
|
||||
const WorkingButton = () => (
|
||||
<div>
|
||||
<button className="pure-button button-success"
|
||||
disabled={this.state.payDisabled}
|
||||
onClick={() => this.doPayment()}>
|
||||
<span><object className="svg-icon svg-baseline" data="/img/spinner-bars.svg" /> </span>
|
||||
{i18n.str`Submitting payment`}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ConfirmPayDialog = () => (
|
||||
<div>
|
||||
{this.state.working ? WorkingButton() : ConfirmButton()}
|
||||
<div>
|
||||
{(this.state.alreadyPaid
|
||||
? <p className="okaybox">
|
||||
You already paid for this, clicking "Confirm payment" will not cost money again.
|
||||
</p>
|
||||
: <p />)}
|
||||
{(this.state.checkPayError ? <p className="errorbox">{this.state.checkPayError}</p> : <p />)}
|
||||
</div>
|
||||
<Details exchanges={this.state.exchanges} contractTerms={c} collapsed={!this.state.checkPayError}/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const PayErrorDialog = () => (
|
||||
<div>
|
||||
<p>There was an error paying (attempt #{this.state.payAttempt}):</p>
|
||||
<pre>{JSON.stringify(this.state.confirmPayError)}</pre>
|
||||
{ this.state.abortStarted
|
||||
? <span>Aborting payment ...</span>
|
||||
: this.state.abortDone
|
||||
? <span>Payment aborted!</span>
|
||||
: <>
|
||||
<button className="pure-button" onClick={() => this.doPayment()}>
|
||||
Retry Payment
|
||||
</button>
|
||||
<button className="pure-button" onClick={() => this.abortPayment()}>
|
||||
Abort Payment
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<i18n.Translate wrap="p">
|
||||
The merchant <span>{merchantName}</span> {" "}
|
||||
@ -302,22 +386,11 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
|
||||
:
|
||||
<p>The total price is <span>{amount}</span>.</p>
|
||||
}
|
||||
{ this.state.confirmPayError
|
||||
? PayErrorDialog()
|
||||
: ConfirmPayDialog()
|
||||
}
|
||||
</div>
|
||||
<button className="pure-button button-success"
|
||||
disabled={this.state.payDisabled}
|
||||
onClick={() => this.doPayment()}>
|
||||
{i18n.str`Confirm payment`}
|
||||
</button>
|
||||
<div>
|
||||
{(this.state.alreadyPaid
|
||||
? <p className="okaybox">
|
||||
You already paid for this, clicking "Confirm payment" will not cost money again.
|
||||
</p>
|
||||
: <p />)}
|
||||
{(this.state.error ? <p className="errorbox">{this.state.error}</p> : <p />)}
|
||||
</div>
|
||||
<Details exchanges={this.state.exchanges} contractTerms={c} collapsed={!this.state.error}/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ import {
|
||||
} from "../walletTypes";
|
||||
|
||||
import {
|
||||
RefundPermission,
|
||||
MerchantRefundPermission,
|
||||
TipToken,
|
||||
} from "../talerTypes";
|
||||
|
||||
@ -72,14 +72,22 @@ export interface UpgradeResponse {
|
||||
}
|
||||
|
||||
|
||||
export class WalletApiError extends Error {
|
||||
constructor(message: string, public detail: any) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function callBackend<T extends MessageType>(
|
||||
type: T,
|
||||
detail: MessageMap[T]["request"],
|
||||
): Promise<MessageMap[T]["response"]> {
|
||||
return new Promise<MessageMap[T]["response"]>((resolve, reject) => {
|
||||
chrome.runtime.sendMessage({ type, detail }, (resp) => {
|
||||
if (resp && resp.error) {
|
||||
reject(resp);
|
||||
if (typeof resp === "object" && resp && resp.error) {
|
||||
const e = new WalletApiError(resp.error.message, resp);
|
||||
reject(e);
|
||||
} else {
|
||||
resolve(resp);
|
||||
}
|
||||
@ -327,7 +335,7 @@ export function getPurchase(contractTermsHash: string): Promise<PurchaseRecord>
|
||||
* Get the refund fees for a refund permission, including
|
||||
* subsequent refresh and unrefreshable coins.
|
||||
*/
|
||||
export function getFullRefundFees(args: { refundPermissions: RefundPermission[] }): Promise<AmountJson> {
|
||||
export function getFullRefundFees(args: { refundPermissions: MerchantRefundPermission[] }): Promise<AmountJson> {
|
||||
return callBackend("get-full-refund-fees", { refundPermissions: args.refundPermissions });
|
||||
}
|
||||
|
||||
@ -374,3 +382,10 @@ export function downloadProposal(url: string): Promise<number> {
|
||||
export function acceptRefund(refundUrl: string): Promise<string> {
|
||||
return callBackend("accept-refund", { refundUrl });
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort a failed payment and try to get a refund.
|
||||
*/
|
||||
export function abortFailedPayment(contractTermsHash: string) {
|
||||
return callBackend("abort-failed-payment", { contractTermsHash });
|
||||
}
|
||||
|
@ -308,6 +308,12 @@ function handleMessage(sender: MessageSender,
|
||||
case "download-proposal": {
|
||||
return needsWallet().downloadProposal(detail.url);
|
||||
}
|
||||
case "abort-failed-payment": {
|
||||
if (!detail.contractTermsHash) {
|
||||
throw Error("contracTermsHash not given");
|
||||
}
|
||||
return needsWallet().abortFailedPayment(detail.contractTermsHash);
|
||||
}
|
||||
case "taler-pay": {
|
||||
const senderUrl = sender.url;
|
||||
if (!senderUrl) {
|
||||
@ -514,7 +520,7 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri
|
||||
console.log("processing refund");
|
||||
const uri = new URI(chrome.extension.getURL("/src/webex/pages/refund.html"));
|
||||
uri.query({ refundUrl: fields.refund_url });
|
||||
return { redirectUrl: uri.href };
|
||||
return { redirectUrl: uri.href() };
|
||||
}
|
||||
|
||||
// We need to do some asynchronous operation, we can't directly redirect
|
||||
|
Loading…
Reference in New Issue
Block a user