partial implementation of tipping

This commit is contained in:
Florian Dold 2017-11-30 04:07:36 +01:00
parent bc2c4aff8e
commit b8ccc7c990
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
23 changed files with 1393 additions and 555 deletions

53
img/spinner-bars.svg Normal file
View File

@ -0,0 +1,53 @@
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg width="135" height="140" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg" fill="#fff">
<rect y="10" width="15" height="120" rx="6">
<animate attributeName="height"
begin="0.5s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.5s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="30" y="10" width="15" height="120" rx="6">
<animate attributeName="height"
begin="0.25s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.25s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="60" width="15" height="140" rx="6">
<animate attributeName="height"
begin="0s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="90" y="10" width="15" height="120" rx="6">
<animate attributeName="height"
begin="0.25s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.25s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="120" y="10" width="15" height="120" rx="6">
<animate attributeName="height"
begin="0.5s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.5s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -34,6 +34,7 @@ import {
PreCoinRecord,
RefreshSessionRecord,
ReserveRecord,
TipPlanchet,
WireFee,
} from "../types";
@ -253,6 +254,10 @@ export class CryptoApi {
return this.doRpc<PreCoinRecord>("createPreCoin", 1, denom, reserve);
}
createTipPlanchet(denom: DenominationRecord): Promise<TipPlanchet> {
return this.doRpc<TipPlanchet>("createTipPlanchet", 1, denom);
}
hashString(str: string): Promise<string> {
return this.doRpc<string>("hashString", 1, str);
}

View File

@ -40,6 +40,7 @@ import {
RefreshPreCoinRecord,
RefreshSessionRecord,
ReserveRecord,
TipPlanchet,
WireFee,
} from "../types";
@ -103,6 +104,7 @@ namespace RpcFunctions {
coinValue: denom.value,
denomPub: denomPub.encode().toCrock(),
exchangeBaseUrl: reserve.exchange_base_url,
isFromTip: false,
reservePub: reservePub.toCrock(),
withdrawSig: sig.toCrock(),
};
@ -110,6 +112,35 @@ namespace RpcFunctions {
}
export function createTipPlanchet(denom: DenominationRecord): TipPlanchet {
const denomPub = native.RsaPublicKey.fromCrock(denom.denomPub);
const coinPriv = native.EddsaPrivateKey.create();
const coinPub = coinPriv.getPublicKey();
const blindingFactor = native.RsaBlindingKeySecret.create();
const pubHash: native.HashCode = coinPub.hash();
const ev = native.rsaBlind(pubHash, blindingFactor, denomPub);
if (!ev) {
throw Error("couldn't blind (malicious exchange key?)");
}
if (!denom.feeWithdraw) {
throw Error("Field fee_withdraw missing");
}
const tipPlanchet: TipPlanchet = {
blindingKey: blindingFactor.toCrock(),
coinEv: ev.toCrock(),
coinPriv: coinPriv.toCrock(),
coinPub: coinPub.toCrock(),
coinValue: denom.value,
denomPubHash: denomPub.encode().hash().toCrock(),
denomPub: denomPub.encode().toCrock(),
};
return tipPlanchet;
}
/**
* Create and sign a message to request payback for a coin.
*/

View File

@ -66,67 +66,27 @@ msgstr ""
msgid "Confirm payment"
msgstr "Bezahlung bestätigen"
#: src/webex/pages/confirm-create-reserve.tsx:178
#, fuzzy, c-format
msgid "Withdrawal fees:"
msgstr "Abheben bei %1$s"
#: src/webex/pages/confirm-create-reserve.tsx:179
#, c-format
msgid "Rounding loss:"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:180
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:185
#, c-format
msgid "# Coins"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:186
#, c-format
msgid "Value"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:187
#, fuzzy, c-format
msgid "Withdraw Fee"
msgstr "Abheben bei %1$s"
#: src/webex/pages/confirm-create-reserve.tsx:188
#, c-format
msgid "Refresh Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:189
#, c-format
msgid "Deposit Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:243
#: src/webex/pages/confirm-create-reserve.tsx:121
#, c-format
msgid "Select"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:259
#: src/webex/pages/confirm-create-reserve.tsx:137
#, c-format
msgid "Error: URL may not be relative"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:327
#: src/webex/pages/confirm-create-reserve.tsx:205
#, c-format
msgid "The exchange is trusted by the wallet.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:333
#: src/webex/pages/confirm-create-reserve.tsx:211
#, c-format
msgid "The exchange is audited by a trusted auditor.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:339
#: src/webex/pages/confirm-create-reserve.tsx:217
#, c-format
msgid ""
"Warning: The exchange is neither directly trusted nor audited by a trusted "
@ -134,7 +94,7 @@ msgid ""
"If you withdraw from this exchange, it will be trusted in the future.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:348
#: src/webex/pages/confirm-create-reserve.tsx:226
#, c-format
msgid ""
"Using exchange provider%1$s.\n"
@ -142,58 +102,59 @@ msgid ""
" %2$s in fees.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:362
#: src/webex/pages/confirm-create-reserve.tsx:240
#, c-format
msgid ""
"Waiting for a response from\n"
" %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:379
#: src/webex/pages/confirm-create-reserve.tsx:257
#, c-format
msgid ""
"Information about fees will be available when an exchange provider is "
"selected."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:422
#: src/webex/pages/confirm-create-reserve.tsx:300
#, c-format
msgid "Accept fees and withdraw"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:427
#: src/webex/pages/confirm-create-reserve.tsx:305
#, c-format
msgid "Change Exchange Provider"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:484
#: src/webex/pages/confirm-create-reserve.tsx:357
#, c-format
msgid "You are about to withdraw %1$s from your bank account into your wallet."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:569
#: src/webex/pages/confirm-create-reserve.tsx:442
#, c-format
msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:578
#: src/webex/pages/confirm-create-reserve.tsx:451
#, c-format
msgid "Checking URL, please wait ..."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:592
#: src/webex/pages/confirm-create-reserve.tsx:465
#, c-format
msgid "Can't parse amount: %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:599
#: src/webex/pages/confirm-create-reserve.tsx:472
#, c-format
msgid "Can't parse wire_types: %1$s"
msgstr ""
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
#. TODO:generic error reporting function or component.
#: src/webex/pages/confirm-create-reserve.tsx:625
#: src/webex/pages/confirm-create-reserve.tsx:498 src/webex/pages/tip.tsx:148
#, c-format
msgid "Fatal error: \"%1$s\"."
msgstr ""
@ -324,6 +285,46 @@ msgstr "Bezahlung bestätigen"
msgid "Cancel"
msgstr "Saldo"
#: src/webex/renderHtml.tsx:209
#, fuzzy, c-format
msgid "Withdrawal fees:"
msgstr "Abheben bei %1$s"
#: src/webex/renderHtml.tsx:210
#, c-format
msgid "Rounding loss:"
msgstr ""
#: src/webex/renderHtml.tsx:211
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
#: src/webex/renderHtml.tsx:216
#, c-format
msgid "# Coins"
msgstr ""
#: src/webex/renderHtml.tsx:217
#, c-format
msgid "Value"
msgstr ""
#: src/webex/renderHtml.tsx:218
#, fuzzy, c-format
msgid "Withdraw Fee"
msgstr "Abheben bei %1$s"
#: src/webex/renderHtml.tsx:219
#, c-format
msgid "Refresh Fee"
msgstr ""
#: src/webex/renderHtml.tsx:220
#, c-format
msgid "Deposit Fee"
msgstr ""
#: src/wire.ts:38
#, c-format
msgid "Invalid Wire"

View File

@ -66,67 +66,27 @@ msgstr ""
msgid "Confirm payment"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:178
#, c-format
msgid "Withdrawal fees:"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:179
#, c-format
msgid "Rounding loss:"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:180
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:185
#, c-format
msgid "# Coins"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:186
#, c-format
msgid "Value"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:187
#, c-format
msgid "Withdraw Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:188
#, c-format
msgid "Refresh Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:189
#, c-format
msgid "Deposit Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:243
#: src/webex/pages/confirm-create-reserve.tsx:121
#, c-format
msgid "Select"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:259
#: src/webex/pages/confirm-create-reserve.tsx:137
#, c-format
msgid "Error: URL may not be relative"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:327
#: src/webex/pages/confirm-create-reserve.tsx:205
#, c-format
msgid "The exchange is trusted by the wallet.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:333
#: src/webex/pages/confirm-create-reserve.tsx:211
#, c-format
msgid "The exchange is audited by a trusted auditor.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:339
#: src/webex/pages/confirm-create-reserve.tsx:217
#, c-format
msgid ""
"Warning: The exchange is neither directly trusted nor audited by a trusted "
@ -134,7 +94,7 @@ msgid ""
"If you withdraw from this exchange, it will be trusted in the future.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:348
#: src/webex/pages/confirm-create-reserve.tsx:226
#, c-format
msgid ""
"Using exchange provider%1$s.\n"
@ -142,58 +102,59 @@ msgid ""
" %2$s in fees.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:362
#: src/webex/pages/confirm-create-reserve.tsx:240
#, c-format
msgid ""
"Waiting for a response from\n"
" %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:379
#: src/webex/pages/confirm-create-reserve.tsx:257
#, c-format
msgid ""
"Information about fees will be available when an exchange provider is "
"selected."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:422
#: src/webex/pages/confirm-create-reserve.tsx:300
#, c-format
msgid "Accept fees and withdraw"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:427
#: src/webex/pages/confirm-create-reserve.tsx:305
#, c-format
msgid "Change Exchange Provider"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:484
#: src/webex/pages/confirm-create-reserve.tsx:357
#, c-format
msgid "You are about to withdraw %1$s from your bank account into your wallet."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:569
#: src/webex/pages/confirm-create-reserve.tsx:442
#, c-format
msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:578
#: src/webex/pages/confirm-create-reserve.tsx:451
#, c-format
msgid "Checking URL, please wait ..."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:592
#: src/webex/pages/confirm-create-reserve.tsx:465
#, c-format
msgid "Can't parse amount: %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:599
#: src/webex/pages/confirm-create-reserve.tsx:472
#, c-format
msgid "Can't parse wire_types: %1$s"
msgstr ""
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
#. TODO:generic error reporting function or component.
#: src/webex/pages/confirm-create-reserve.tsx:625
#: src/webex/pages/confirm-create-reserve.tsx:498 src/webex/pages/tip.tsx:148
#, c-format
msgid "Fatal error: \"%1$s\"."
msgstr ""
@ -321,6 +282,46 @@ msgstr ""
msgid "Cancel"
msgstr ""
#: src/webex/renderHtml.tsx:209
#, c-format
msgid "Withdrawal fees:"
msgstr ""
#: src/webex/renderHtml.tsx:210
#, c-format
msgid "Rounding loss:"
msgstr ""
#: src/webex/renderHtml.tsx:211
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
#: src/webex/renderHtml.tsx:216
#, c-format
msgid "# Coins"
msgstr ""
#: src/webex/renderHtml.tsx:217
#, c-format
msgid "Value"
msgstr ""
#: src/webex/renderHtml.tsx:218
#, c-format
msgid "Withdraw Fee"
msgstr ""
#: src/webex/renderHtml.tsx:219
#, c-format
msgid "Refresh Fee"
msgstr ""
#: src/webex/renderHtml.tsx:220
#, c-format
msgid "Deposit Fee"
msgstr ""
#: src/wire.ts:38
#, c-format
msgid "Invalid Wire"

View File

@ -66,67 +66,27 @@ msgstr ""
msgid "Confirm payment"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:178
#, c-format
msgid "Withdrawal fees:"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:179
#, c-format
msgid "Rounding loss:"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:180
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:185
#, c-format
msgid "# Coins"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:186
#, c-format
msgid "Value"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:187
#, c-format
msgid "Withdraw Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:188
#, c-format
msgid "Refresh Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:189
#, c-format
msgid "Deposit Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:243
#: src/webex/pages/confirm-create-reserve.tsx:121
#, c-format
msgid "Select"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:259
#: src/webex/pages/confirm-create-reserve.tsx:137
#, c-format
msgid "Error: URL may not be relative"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:327
#: src/webex/pages/confirm-create-reserve.tsx:205
#, c-format
msgid "The exchange is trusted by the wallet.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:333
#: src/webex/pages/confirm-create-reserve.tsx:211
#, c-format
msgid "The exchange is audited by a trusted auditor.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:339
#: src/webex/pages/confirm-create-reserve.tsx:217
#, c-format
msgid ""
"Warning: The exchange is neither directly trusted nor audited by a trusted "
@ -134,7 +94,7 @@ msgid ""
"If you withdraw from this exchange, it will be trusted in the future.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:348
#: src/webex/pages/confirm-create-reserve.tsx:226
#, c-format
msgid ""
"Using exchange provider%1$s.\n"
@ -142,58 +102,59 @@ msgid ""
" %2$s in fees.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:362
#: src/webex/pages/confirm-create-reserve.tsx:240
#, c-format
msgid ""
"Waiting for a response from\n"
" %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:379
#: src/webex/pages/confirm-create-reserve.tsx:257
#, c-format
msgid ""
"Information about fees will be available when an exchange provider is "
"selected."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:422
#: src/webex/pages/confirm-create-reserve.tsx:300
#, c-format
msgid "Accept fees and withdraw"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:427
#: src/webex/pages/confirm-create-reserve.tsx:305
#, c-format
msgid "Change Exchange Provider"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:484
#: src/webex/pages/confirm-create-reserve.tsx:357
#, c-format
msgid "You are about to withdraw %1$s from your bank account into your wallet."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:569
#: src/webex/pages/confirm-create-reserve.tsx:442
#, c-format
msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:578
#: src/webex/pages/confirm-create-reserve.tsx:451
#, c-format
msgid "Checking URL, please wait ..."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:592
#: src/webex/pages/confirm-create-reserve.tsx:465
#, c-format
msgid "Can't parse amount: %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:599
#: src/webex/pages/confirm-create-reserve.tsx:472
#, c-format
msgid "Can't parse wire_types: %1$s"
msgstr ""
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
#. TODO:generic error reporting function or component.
#: src/webex/pages/confirm-create-reserve.tsx:625
#: src/webex/pages/confirm-create-reserve.tsx:498 src/webex/pages/tip.tsx:148
#, c-format
msgid "Fatal error: \"%1$s\"."
msgstr ""
@ -321,6 +282,46 @@ msgstr ""
msgid "Cancel"
msgstr ""
#: src/webex/renderHtml.tsx:209
#, c-format
msgid "Withdrawal fees:"
msgstr ""
#: src/webex/renderHtml.tsx:210
#, c-format
msgid "Rounding loss:"
msgstr ""
#: src/webex/renderHtml.tsx:211
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
#: src/webex/renderHtml.tsx:216
#, c-format
msgid "# Coins"
msgstr ""
#: src/webex/renderHtml.tsx:217
#, c-format
msgid "Value"
msgstr ""
#: src/webex/renderHtml.tsx:218
#, c-format
msgid "Withdraw Fee"
msgstr ""
#: src/webex/renderHtml.tsx:219
#, c-format
msgid "Refresh Fee"
msgstr ""
#: src/webex/renderHtml.tsx:220
#, c-format
msgid "Deposit Fee"
msgstr ""
#: src/wire.ts:38
#, c-format
msgid "Invalid Wire"

View File

@ -66,67 +66,27 @@ msgstr ""
msgid "Confirm payment"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:178
#, c-format
msgid "Withdrawal fees:"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:179
#, c-format
msgid "Rounding loss:"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:180
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:185
#, c-format
msgid "# Coins"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:186
#, c-format
msgid "Value"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:187
#, c-format
msgid "Withdraw Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:188
#, c-format
msgid "Refresh Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:189
#, c-format
msgid "Deposit Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:243
#: src/webex/pages/confirm-create-reserve.tsx:121
#, c-format
msgid "Select"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:259
#: src/webex/pages/confirm-create-reserve.tsx:137
#, c-format
msgid "Error: URL may not be relative"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:327
#: src/webex/pages/confirm-create-reserve.tsx:205
#, c-format
msgid "The exchange is trusted by the wallet.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:333
#: src/webex/pages/confirm-create-reserve.tsx:211
#, c-format
msgid "The exchange is audited by a trusted auditor.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:339
#: src/webex/pages/confirm-create-reserve.tsx:217
#, c-format
msgid ""
"Warning: The exchange is neither directly trusted nor audited by a trusted "
@ -134,7 +94,7 @@ msgid ""
"If you withdraw from this exchange, it will be trusted in the future.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:348
#: src/webex/pages/confirm-create-reserve.tsx:226
#, c-format
msgid ""
"Using exchange provider%1$s.\n"
@ -142,58 +102,59 @@ msgid ""
" %2$s in fees.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:362
#: src/webex/pages/confirm-create-reserve.tsx:240
#, c-format
msgid ""
"Waiting for a response from\n"
" %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:379
#: src/webex/pages/confirm-create-reserve.tsx:257
#, c-format
msgid ""
"Information about fees will be available when an exchange provider is "
"selected."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:422
#: src/webex/pages/confirm-create-reserve.tsx:300
#, c-format
msgid "Accept fees and withdraw"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:427
#: src/webex/pages/confirm-create-reserve.tsx:305
#, c-format
msgid "Change Exchange Provider"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:484
#: src/webex/pages/confirm-create-reserve.tsx:357
#, c-format
msgid "You are about to withdraw %1$s from your bank account into your wallet."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:569
#: src/webex/pages/confirm-create-reserve.tsx:442
#, c-format
msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:578
#: src/webex/pages/confirm-create-reserve.tsx:451
#, c-format
msgid "Checking URL, please wait ..."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:592
#: src/webex/pages/confirm-create-reserve.tsx:465
#, c-format
msgid "Can't parse amount: %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:599
#: src/webex/pages/confirm-create-reserve.tsx:472
#, c-format
msgid "Can't parse wire_types: %1$s"
msgstr ""
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
#. TODO:generic error reporting function or component.
#: src/webex/pages/confirm-create-reserve.tsx:625
#: src/webex/pages/confirm-create-reserve.tsx:498 src/webex/pages/tip.tsx:148
#, c-format
msgid "Fatal error: \"%1$s\"."
msgstr ""
@ -321,6 +282,46 @@ msgstr ""
msgid "Cancel"
msgstr ""
#: src/webex/renderHtml.tsx:209
#, c-format
msgid "Withdrawal fees:"
msgstr ""
#: src/webex/renderHtml.tsx:210
#, c-format
msgid "Rounding loss:"
msgstr ""
#: src/webex/renderHtml.tsx:211
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
#: src/webex/renderHtml.tsx:216
#, c-format
msgid "# Coins"
msgstr ""
#: src/webex/renderHtml.tsx:217
#, c-format
msgid "Value"
msgstr ""
#: src/webex/renderHtml.tsx:218
#, c-format
msgid "Withdraw Fee"
msgstr ""
#: src/webex/renderHtml.tsx:219
#, c-format
msgid "Refresh Fee"
msgstr ""
#: src/webex/renderHtml.tsx:220
#, c-format
msgid "Deposit Fee"
msgstr ""
#: src/wire.ts:38
#, c-format
msgid "Invalid Wire"

View File

@ -45,30 +45,6 @@ strings['de'] = {
"Confirm payment": [
"Bezahlung bestätigen"
],
"Withdrawal fees:": [
"Abheben bei %1$s"
],
"Rounding loss:": [
""
],
"Earliest expiration (for deposit): %1$s": [
""
],
"# Coins": [
""
],
"Value": [
""
],
"Withdraw Fee": [
"Abheben bei %1$s"
],
"Refresh Fee": [
""
],
"Deposit Fee": [
""
],
"Select": [
""
],
@ -186,6 +162,30 @@ strings['de'] = {
"Cancel": [
"Saldo"
],
"Withdrawal fees:": [
"Abheben bei %1$s"
],
"Rounding loss:": [
""
],
"Earliest expiration (for deposit): %1$s": [
""
],
"# Coins": [
""
],
"Value": [
""
],
"Withdraw Fee": [
"Abheben bei %1$s"
],
"Refresh Fee": [
""
],
"Deposit Fee": [
""
],
"Invalid Wire": [
""
],
@ -231,30 +231,6 @@ strings['en-US'] = {
"Confirm payment": [
""
],
"Withdrawal fees:": [
""
],
"Rounding loss:": [
""
],
"Earliest expiration (for deposit): %1$s": [
""
],
"# Coins": [
""
],
"Value": [
""
],
"Withdraw Fee": [
""
],
"Refresh Fee": [
""
],
"Deposit Fee": [
""
],
"Select": [
""
],
@ -372,6 +348,30 @@ strings['en-US'] = {
"Cancel": [
""
],
"Withdrawal fees:": [
""
],
"Rounding loss:": [
""
],
"Earliest expiration (for deposit): %1$s": [
""
],
"# Coins": [
""
],
"Value": [
""
],
"Withdraw Fee": [
""
],
"Refresh Fee": [
""
],
"Deposit Fee": [
""
],
"Invalid Wire": [
""
],
@ -417,30 +417,6 @@ strings['fr'] = {
"Confirm payment": [
""
],
"Withdrawal fees:": [
""
],
"Rounding loss:": [
""
],
"Earliest expiration (for deposit): %1$s": [
""
],
"# Coins": [
""
],
"Value": [
""
],
"Withdraw Fee": [
""
],
"Refresh Fee": [
""
],
"Deposit Fee": [
""
],
"Select": [
""
],
@ -558,6 +534,30 @@ strings['fr'] = {
"Cancel": [
""
],
"Withdrawal fees:": [
""
],
"Rounding loss:": [
""
],
"Earliest expiration (for deposit): %1$s": [
""
],
"# Coins": [
""
],
"Value": [
""
],
"Withdraw Fee": [
""
],
"Refresh Fee": [
""
],
"Deposit Fee": [
""
],
"Invalid Wire": [
""
],
@ -603,30 +603,6 @@ strings['it'] = {
"Confirm payment": [
""
],
"Withdrawal fees:": [
""
],
"Rounding loss:": [
""
],
"Earliest expiration (for deposit): %1$s": [
""
],
"# Coins": [
""
],
"Value": [
""
],
"Withdraw Fee": [
""
],
"Refresh Fee": [
""
],
"Deposit Fee": [
""
],
"Select": [
""
],
@ -744,6 +720,30 @@ strings['it'] = {
"Cancel": [
""
],
"Withdrawal fees:": [
""
],
"Rounding loss:": [
""
],
"Earliest expiration (for deposit): %1$s": [
""
],
"# Coins": [
""
],
"Value": [
""
],
"Withdraw Fee": [
""
],
"Refresh Fee": [
""
],
"Deposit Fee": [
""
],
"Invalid Wire": [
""
],

View File

@ -66,67 +66,27 @@ msgstr ""
msgid "Confirm payment"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:178
#, c-format
msgid "Withdrawal fees:"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:179
#, c-format
msgid "Rounding loss:"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:180
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:185
#, c-format
msgid "# Coins"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:186
#, c-format
msgid "Value"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:187
#, c-format
msgid "Withdraw Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:188
#, c-format
msgid "Refresh Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:189
#, c-format
msgid "Deposit Fee"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:243
#: src/webex/pages/confirm-create-reserve.tsx:121
#, c-format
msgid "Select"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:259
#: src/webex/pages/confirm-create-reserve.tsx:137
#, c-format
msgid "Error: URL may not be relative"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:327
#: src/webex/pages/confirm-create-reserve.tsx:205
#, c-format
msgid "The exchange is trusted by the wallet.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:333
#: src/webex/pages/confirm-create-reserve.tsx:211
#, c-format
msgid "The exchange is audited by a trusted auditor.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:339
#: src/webex/pages/confirm-create-reserve.tsx:217
#, c-format
msgid ""
"Warning: The exchange is neither directly trusted nor audited by a trusted "
@ -134,7 +94,7 @@ msgid ""
"If you withdraw from this exchange, it will be trusted in the future.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:348
#: src/webex/pages/confirm-create-reserve.tsx:226
#, c-format
msgid ""
"Using exchange provider%1$s.\n"
@ -142,58 +102,59 @@ msgid ""
" %2$s in fees.\n"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:362
#: src/webex/pages/confirm-create-reserve.tsx:240
#, c-format
msgid ""
"Waiting for a response from\n"
" %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:379
#: src/webex/pages/confirm-create-reserve.tsx:257
#, c-format
msgid ""
"Information about fees will be available when an exchange provider is "
"selected."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:422
#: src/webex/pages/confirm-create-reserve.tsx:300
#, c-format
msgid "Accept fees and withdraw"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:427
#: src/webex/pages/confirm-create-reserve.tsx:305
#, c-format
msgid "Change Exchange Provider"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:484
#: src/webex/pages/confirm-create-reserve.tsx:357
#, c-format
msgid "You are about to withdraw %1$s from your bank account into your wallet."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:569
#: src/webex/pages/confirm-create-reserve.tsx:442
#, c-format
msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:578
#: src/webex/pages/confirm-create-reserve.tsx:451
#, c-format
msgid "Checking URL, please wait ..."
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:592
#: src/webex/pages/confirm-create-reserve.tsx:465
#, c-format
msgid "Can't parse amount: %1$s"
msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:599
#: src/webex/pages/confirm-create-reserve.tsx:472
#, c-format
msgid "Can't parse wire_types: %1$s"
msgstr ""
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
#. TODO:generic error reporting function or component.
#: src/webex/pages/confirm-create-reserve.tsx:625
#: src/webex/pages/confirm-create-reserve.tsx:498 src/webex/pages/tip.tsx:148
#, c-format
msgid "Fatal error: \"%1$s\"."
msgstr ""
@ -321,6 +282,46 @@ msgstr ""
msgid "Cancel"
msgstr ""
#: src/webex/renderHtml.tsx:209
#, c-format
msgid "Withdrawal fees:"
msgstr ""
#: src/webex/renderHtml.tsx:210
#, c-format
msgid "Rounding loss:"
msgstr ""
#: src/webex/renderHtml.tsx:211
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
#: src/webex/renderHtml.tsx:216
#, c-format
msgid "# Coins"
msgstr ""
#: src/webex/renderHtml.tsx:217
#, c-format
msgid "Value"
msgstr ""
#: src/webex/renderHtml.tsx:218
#, c-format
msgid "Withdraw Fee"
msgstr ""
#: src/webex/renderHtml.tsx:219
#, c-format
msgid "Refresh Fee"
msgstr ""
#: src/webex/renderHtml.tsx:220
#, c-format
msgid "Deposit Fee"
msgstr ""
#: src/wire.ts:38
#, c-format
msgid "Invalid Wire"

View File

@ -50,6 +50,20 @@ export class Store<T> {
}
/**
* Options for an index.
*/
export interface IndexOptions {
/**
* If true and the path resolves to an array, create an index entry for
* each member of the array (instead of one index entry containing the full array).
*
* Defaults to false.
*/
multiEntry?: boolean;
}
/**
* Definition of an index.
*/
@ -59,7 +73,16 @@ export class Index<S extends IDBValidKey, T> {
*/
storeName: string;
constructor(s: Store<T>, public indexName: string, public keyPath: string | string[]) {
/**
* Options to use for the index.
*/
options: IndexOptions;
constructor(s: Store<T>, public indexName: string, public keyPath: string | string[], options?: IndexOptions) {
const defaultOptions = {
multiEntry: false,
};
this.options = { ...defaultOptions, ...(options || {}) };
this.storeName = s.name;
}
@ -671,26 +694,33 @@ export class QueryRoot {
/**
* Get, modify and store an element inside a transaction.
* Update objects inside a transaction.
*
* If the mutation function throws AbortTransaction, the whole transaction will be aborted.
* If the mutation function returns undefined or null, no modification will be made.
*/
mutate<T>(store: Store<T>, key: any, f: (v: T|undefined) => T|undefined): QueryRoot {
this.checkFinished();
const doPut = (tx: IDBTransaction) => {
const reqGet = tx.objectStore(store.name).get(key);
reqGet.onsuccess = () => {
const r = reqGet.result;
let m: T|undefined;
try {
m = f(r);
} catch (e) {
if (e === AbortTransaction) {
tx.abort();
return;
const req = tx.objectStore(store.name).openCursor(IDBKeyRange.only(key));
req.onsuccess = () => {
const cursor = req.result;
if (cursor) {
const value = cursor.value;
let modifiedValue: T|undefined;
try {
modifiedValue = f(value);
} catch (e) {
if (e === AbortTransaction) {
tx.abort();
return;
}
throw e;
}
throw e;
}
if (m !== undefined && m !== null) {
tx.objectStore(store.name).put(m);
if (modifiedValue !== undefined && modifiedValue !== null) {
cursor.update(modifiedValue);
}
cursor.continue();
}
};
};
@ -702,8 +732,6 @@ export class QueryRoot {
/**
* Add all object from an iterable to the given object store.
* Fails if the object's key is already present
* in the object store.
*/
putAll<T>(store: Store<T>, iterable: T[]): QueryRoot {
this.checkFinished();
@ -822,13 +850,13 @@ export class QueryRoot {
/**
* Delete an object by from the given object store.
*/
delete(storeName: string, key: any): QueryRoot {
delete<T>(store: Store<T>, key: any): QueryRoot {
this.checkFinished();
const doDelete = (tx: IDBTransaction) => {
tx.objectStore(storeName).delete(key);
tx.objectStore(store.name).delete(key);
};
this.scheduleFinish();
this.addWork(doDelete, storeName, true);
this.addWork(doDelete, store.name, true);
return this;
}

View File

@ -598,6 +598,12 @@ export interface PreCoinRecord {
coinEv: string;
exchangeBaseUrl: string;
coinValue: AmountJson;
/**
* Set to true if this pre-coin came from a tip.
* Until the tip is marked as "accepted", the resulting
* coin will not be used for payments.
*/
isFromTip: boolean;
}
/**
@ -836,6 +842,10 @@ export enum CoinStatus {
* Coin was dirty but can't be refreshed.
*/
Useless,
/**
* The coin was withdrawn for a tip that the user hasn't accepted yet.
*/
TainedByTip,
}
@ -1782,3 +1792,217 @@ export interface CoinWithDenom {
*/
denom: DenominationRecord;
}
/**
* Planchet detail sent to the merchant.
*/
export interface TipPlanchetDetail {
/**
* Hashed denomination public key.
*/
denom_pub_hash: string;
/**
* Coin's blinded public key.
*/
coin_ev: string;
}
export interface TipPickupRequest {
/**
* Identifier of the tip.
*/
tip_id: string;
/**
* List of planchets the wallet wants to use for the tip.
*/
planchets: TipPlanchetDetail[];
}
@Checkable.Class()
export class ReserveSigSingleton {
@Checkable.String
reserve_sig: string;
static checked: (obj: any) => ReserveSigSingleton;
}
/**
* Response of the merchant
* to the TipPickupRequest.
*/
@Checkable.Class()
export class TipResponse {
/**
* Public key of the reserve
*/
@Checkable.String
reserve_pub: string;
/**
* The order of the signatures matches the planchets list.
*/
@Checkable.List(Checkable.Value(ReserveSigSingleton))
reserve_sigs: ReserveSigSingleton[];
static checked: (obj: any) => TipResponse;
}
/**
* Tipping planchet stored in the database.
*/
export interface TipPlanchet {
blindingKey: string;
coinEv: string;
coinPriv: string;
coinPub: string;
coinValue: AmountJson;
denomPubHash: string;
denomPub: string;
}
/**
* Status of a tip we got from a merchant.
*/
export interface TipRecord {
/**
* Has the user accepted the tip? Only after the tip has been accepted coins
* withdrawn from the tip may be used.
*/
accepted: boolean;
/**
* The tipped amount.
*/
amount: AmountJson;
/**
* Coin public keys from the planchets.
* This field is redundant and used for indexing the record via
* a multi-entry index to look up tip records by coin public key.
*/
coinPubs: string[];
/**
* Timestamp, the tip can't be picked up anymore after this deadline.
*/
deadline: number;
/**
* The exchange that will sign our coins, chosen by the merchant.
*/
exchangeUrl: string;
/**
* Domain of the merchant, necessary to uniquely identify the tip since
* merchants can freely choose the ID and a malicious merchant might cause a
* collision.
*/
merchantDomain: string;
/**
* Planchets, the members included in TipPlanchetDetail will be sent to the
* merchant.
*/
planchets: TipPlanchet[];
/**
* Response if the merchant responded,
* undefined otherwise.
*/
response?: TipResponse[];
/**
* Identifier for the tip, chosen by the merchant.
*/
tipId: string;
}
export interface TipStatus {
tip: TipRecord;
rci?: ReserveCreationInfo;
}
@Checkable.Class()
export class TipStatusRequest {
@Checkable.String
tipId: string;
@Checkable.String
merchantDomain: string;
static checked: (obj: any) => TipStatusRequest;
}
@Checkable.Class()
export class AcceptTipRequest {
@Checkable.String
tipId: string;
@Checkable.String
merchantDomain: string;
static checked: (obj: any) => AcceptTipRequest;
}
@Checkable.Class()
export class ProcessTipResponseRequest {
@Checkable.String
tipId: string;
@Checkable.String
merchantDomain: string;
@Checkable.Value(TipResponse)
tipResponse: TipResponse;
static checked: (obj: any) => ProcessTipResponseRequest;
}
@Checkable.Class()
export class GetTipPlanchetsRequest {
@Checkable.String
tipId: string;
@Checkable.String
merchantDomain: string;
@Checkable.Optional(Checkable.Value(AmountJson))
amount: AmountJson;
@Checkable.Number
deadline: number;
@Checkable.String
exchangeUrl: string;
static checked: (obj: any) => GetTipPlanchetsRequest;
}
@Checkable.Class()
export class TipToken {
@Checkable.String
expiration: string;
@Checkable.String
exchange_url: string;
@Checkable.String
pickup_url: string;
@Checkable.String
tip_id: string;
@Checkable.Value(AmountJson)
amount: AmountJson;
static checked: (obj: any) => TipToken;
}

View File

@ -81,6 +81,10 @@ import {
ReserveRecord,
ReturnCoinsRequest,
SenderWireInfos,
TipPlanchetDetail,
TipRecord,
TipResponse,
TipStatus,
WalletBalance,
WalletBalanceEntry,
WireFee,
@ -324,7 +328,7 @@ export const WALLET_PROTOCOL_VERSION = "0:0:0";
* In the future we might consider adding migration functions for
* each version increment.
*/
export const WALLET_DB_VERSION = 20;
export const WALLET_DB_VERSION = 21;
const builtinCurrencies: CurrencyRecord[] = [
{
@ -506,7 +510,7 @@ export namespace Stores {
super("exchanges", {keyPath: "baseUrl"});
}
pubKeyIndex = new Index<string, ExchangeRecord>(this, "pubKey", "masterPublicKey");
pubKeyIndex = new Index<string, ExchangeRecord>(this, "pubKeyIndex", "masterPublicKey");
}
class NonceStore extends Store<NonceRecord> {
@ -521,7 +525,7 @@ export namespace Stores {
}
exchangeBaseUrlIndex = new Index<string, CoinRecord>(this, "exchangeBaseUrl", "exchangeBaseUrl");
denomPubIndex = new Index<string, CoinRecord>(this, "denomPub", "denomPub");
denomPubIndex = new Index<string, CoinRecord>(this, "denomPubIndex", "denomPub");
}
class ProposalsStore extends Store<ProposalRecord> {
@ -531,7 +535,7 @@ export namespace Stores {
keyPath: "id",
});
}
timestampIndex = new Index<string, ProposalRecord>(this, "timestamp", "timestamp");
timestampIndex = new Index<string, ProposalRecord>(this, "timestampIndex", "timestamp");
}
class PurchasesStore extends Store<PurchaseRecord> {
@ -539,9 +543,9 @@ export namespace Stores {
super("purchases", {keyPath: "contractTermsHash"});
}
fulfillmentUrlIndex = new Index<string, PurchaseRecord>(this, "fulfillment_url", "contractTerms.fulfillment_url");
orderIdIndex = new Index<string, PurchaseRecord>(this, "order_id", "contractTerms.order_id");
timestampIndex = new Index<string, PurchaseRecord>(this, "timestamp", "timestamp");
fulfillmentUrlIndex = new Index<string, PurchaseRecord>(this, "fulfillmentUrlIndex", "contractTerms.fulfillment_url");
orderIdIndex = new Index<string, PurchaseRecord>(this, "orderIdIndex", "contractTerms.order_id");
timestampIndex = new Index<string, PurchaseRecord>(this, "timestampIndex", "timestamp");
}
class DenominationsStore extends Store<DenominationRecord> {
@ -551,9 +555,9 @@ export namespace Stores {
{keyPath: ["exchangeBaseUrl", "denomPub"] as any as IDBKeyPath});
}
denomPubHashIndex = new Index<string, DenominationRecord>(this, "denomPubHash", "denomPubHash");
exchangeBaseUrlIndex = new Index<string, DenominationRecord>(this, "exchangeBaseUrl", "exchangeBaseUrl");
denomPubIndex = new Index<string, DenominationRecord>(this, "denomPub", "denomPub");
denomPubHashIndex = new Index<string, DenominationRecord>(this, "denomPubHashIndex", "denomPubHash");
exchangeBaseUrlIndex = new Index<string, DenominationRecord>(this, "exchangeBaseUrlIndex", "exchangeBaseUrl");
denomPubIndex = new Index<string, DenominationRecord>(this, "denomPubIndex", "denomPub");
}
class CurrenciesStore extends Store<CurrencyRecord> {
@ -578,9 +582,16 @@ export namespace Stores {
constructor() {
super("reserves", {keyPath: "reserve_pub"});
}
timestampCreatedIndex = new Index<string, ReserveRecord>(this, "timestampCreated", "created");
timestampConfirmedIndex = new Index<string, ReserveRecord>(this, "timestampConfirmed", "timestamp_confirmed");
timestampDepletedIndex = new Index<string, ReserveRecord>(this, "timestampDepleted", "timestamp_depleted");
timestampCreatedIndex = new Index<string, ReserveRecord>(this, "timestampCreatedIndex", "created");
timestampConfirmedIndex = new Index<string, ReserveRecord>(this, "timestampConfirmedIndex", "timestamp_confirmed");
timestampDepletedIndex = new Index<string, ReserveRecord>(this, "timestampDepletedIndex", "timestamp_depleted");
}
class TipsStore extends Store<TipRecord> {
constructor() {
super("tips", {keyPath: ["tipId", "merchantDomain"] as any as IDBKeyPath});
}
coinPubIndex = new Index<string, TipRecord>(this, "coinPubIndex", "coinPubs", { multiEntry: true });
}
export const coins = new CoinsStore();
@ -596,6 +607,7 @@ export namespace Stores {
export const refresh = new Store<RefreshSessionRecord>("refresh", {keyPath: "id", autoIncrement: true});
export const reserves = new ReservesStore();
export const purchases = new PurchasesStore();
export const tips = new TipsStore();
}
/* tslint:enable:completed-docs */
@ -1126,7 +1138,7 @@ export class Wallet {
() => this.processPreCoin(preCoin, Math.min(retryDelayMs * 2, 5 * 60 * 1000)));
return;
}
console.log("executing processPreCoin");
console.log("executing processPreCoin", preCoin);
this.processPreCoinConcurrent++;
try {
const exchange = await this.q().get(Stores.exchanges,
@ -1143,6 +1155,7 @@ export class Wallet {
}
const coin = await this.withdrawExecute(preCoin);
console.log("processPreCoin: got coin", coin);
const mutateReserve = (r: ReserveRecord) => {
@ -1160,10 +1173,28 @@ export class Wallet {
await this.q()
.mutate(Stores.reserves, preCoin.reservePub, mutateReserve)
.delete("precoins", coin.coinPub)
.delete(Stores.precoins, coin.coinPub)
.add(Stores.coins, coin)
.finish();
if (coin.status === CoinStatus.TainedByTip) {
let tip = await this.q().getIndexed(Stores.tips.coinPubIndex, coin.coinPub);
if (!tip) {
throw Error(`inconsistent DB: tip for coin pub ${coin.coinPub} not found.`);
}
if (tip.accepted) {
// Transactionall set coin to fresh.
const mutateCoin = (c: CoinRecord) => {
if (c.status === CoinStatus.TainedByTip) {
c.status = CoinStatus.Fresh;
}
return c;
}
await this.q().mutate(Stores.coins, coin.coinPub, mutateCoin)
}
}
this.notifier.notify();
} catch (e) {
console.error("Failed to withdraw coin from precoin, retrying in",
@ -1266,19 +1297,12 @@ export class Wallet {
private async withdrawExecute(pc: PreCoinRecord): Promise<CoinRecord> {
const reserve = await this.q().get<ReserveRecord>(Stores.reserves,
pc.reservePub);
if (!reserve) {
throw Error("db inconsistent");
}
const wd: any = {};
wd.denom_pub = pc.denomPub;
wd.reserve_pub = pc.reservePub;
wd.reserve_sig = pc.withdrawSig;
wd.coin_ev = pc.coinEv;
const reqUrl = (new URI("reserve/withdraw")).absoluteTo(reserve.exchange_base_url);
const reqUrl = (new URI("reserve/withdraw")).absoluteTo(pc.exchangeBaseUrl);
const resp = await this.http.postJson(reqUrl.href(), wd);
if (resp.status !== 200) {
@ -1289,8 +1313,8 @@ export class Wallet {
}
const r = JSON.parse(resp.responseText);
const denomSig = await this.cryptoApi.rsaUnblind(r.ev_sig,
pc.blindingKey,
pc.denomPub);
pc.blindingKey,
pc.denomPub);
const coin: CoinRecord = {
blindingKey: pc.blindingKey,
coinPriv: pc.coinPriv,
@ -2809,4 +2833,113 @@ export class Wallet {
}
return feeAcc;
}
/**
* Get planchets for a tip. Creates new planchets if they don't exist already
* for this tip. The tip is uniquely identified by the merchant's domain and the tip id.
*/
async getTipPlanchets(merchantDomain: string, tipId: string, amount: AmountJson, deadline: number, exchangeUrl: string): Promise<TipPlanchetDetail[]> {
let tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]);
if (!tipRecord) {
await this.updateExchangeFromUrl(exchangeUrl);
const denomsForWithdraw = await this.getVerifiedWithdrawDenomList(exchangeUrl, amount);
const planchets = await Promise.all(denomsForWithdraw.map(d => this.cryptoApi.createTipPlanchet(d)));
const coinPubs: string[] = planchets.map(x => x.coinPub);
tipRecord = {
accepted: false,
amount,
coinPubs,
deadline,
exchangeUrl,
merchantDomain,
planchets,
tipId,
};
await this.q().put(Stores.tips, tipRecord).finish();
}
// Planchets in the form that the merchant expects
const planchetDetail: TipPlanchetDetail[]= tipRecord.planchets.map((p) => ({
denom_pub_hash: p.denomPubHash,
coin_ev: p.coinEv,
}));
return planchetDetail;
}
/**
* Accept a merchant's response to a tip pickup and start withdrawing the coins.
* These coins will not appear in the wallet yet.
*/
async processTipResponse(merchantDomain: string, tipId: string, response: TipResponse): Promise<void> {
let tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]);
if (!tipRecord) {
throw Error("tip not found");
}
console.log("processing tip response", response);
if (response.reserve_sigs.length !== tipRecord.planchets.length) {
throw Error("number of tip responses does not match requested planchets");
}
for (let i = 0; i < tipRecord.planchets.length; i++) {
let planchet = tipRecord.planchets[i];
let preCoin = {
coinPub: planchet.coinPub,
coinPriv: planchet.coinPriv,
coinEv: planchet.coinEv,
coinValue: planchet.coinValue,
reservePub: response.reserve_pub,
denomPub: planchet.denomPub,
blindingKey: planchet.blindingKey,
withdrawSig: response.reserve_sigs[i].reserve_sig,
exchangeBaseUrl: tipRecord.exchangeUrl,
isFromTip: true,
};
await this.q().put(Stores.precoins, preCoin);
this.processPreCoin(preCoin);
}
}
/**
* Start using the coins from a tip.
*/
async acceptTip(merchantDomain: string, tipId: string): Promise<void> {
const tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]);
if (!tipRecord) {
throw Error("tip not found");
}
tipRecord.accepted = true;
// Create one transactional query, within this transaction
// both the tip will be marked as accepted and coins
// already withdrawn will be untainted.
const q = this.q();
q.put(Stores.tips, tipRecord);
const updateCoin = (c: CoinRecord) => {
if (c.status === CoinStatus.TainedByTip) {
c.status = CoinStatus.Fresh;
}
return c;
};
for (const coinPub of tipRecord.coinPubs) {
q.mutate(Stores.coins, coinPub, updateCoin);
}
await q.finish();
this.notifier.notify();
}
async getTipStatus(merchantDomain: string, tipId: string): Promise<TipStatus> {
const tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]);
if (!tipRecord) {
throw Error("tip not found");
}
const rci = await this.getReserveCreationInfo(tipRecord.exchangeUrl, tipRecord.amount);
const tipStatus: TipStatus = {
rci,
tip: tipRecord,
};
return tipStatus;
}
}

View File

@ -192,6 +192,22 @@ export interface MessageMap {
request: { refundPermissions: types.RefundPermission[] };
response: void;
};
"get-tip-planchets": {
request: types.GetTipPlanchetsRequest;
response: void;
};
"process-tip-response": {
request: types.ProcessTipResponseRequest;
response: void;
};
"accept-tip": {
request: types.AcceptTipRequest;
response: void;
};
"get-tip-status": {
request: types.TipStatusRequest;
response: void;
};
}
/**

View File

@ -28,7 +28,9 @@ import URI = require("urijs");
import wxApi = require("./wxApi");
import { QueryPaymentResult } from "../types";
import { getTalerStampSec } from "../helpers";
import { TipToken, QueryPaymentResult } from "../types";
import axios from "axios";
@ -260,6 +262,87 @@ function talerPay(msg: any): Promise<any> {
// Use a promise directly instead of of an async
// function since some paths never resolve the promise.
return new Promise(async(resolve, reject) => {
if (msg.tip) {
const tipToken = TipToken.checked(JSON.parse(msg.tip));
console.log("got tip token", tipToken);
const deadlineSec = getTalerStampSec(tipToken.expiration);
if (!deadlineSec) {
wxApi.logAndDisplayError({
message: "invalid expiration",
name: "tipping-failed",
sameTab: true,
});
return;
}
const merchantDomain = new URI(document.location.href).origin();
let walletResp;
try {
walletResp = await wxApi.getTipPlanchets(merchantDomain, tipToken.tip_id, tipToken.amount, deadlineSec, tipToken.exchange_url);
} catch (e) {
wxApi.logAndDisplayError({
message: e.message,
name: "tipping-failed",
response: e.response,
sameTab: true,
});
throw e;
}
let planchets = walletResp;
if (!planchets) {
wxApi.logAndDisplayError({
message: "processing tip failed",
detail: walletResp,
name: "tipping-failed",
sameTab: true,
});
return;
}
let merchantResp;
try {
const config = {
validateStatus: (s: number) => s === 200,
};
const req = { planchets, tip_id: tipToken.tip_id };
merchantResp = await axios.post(tipToken.pickup_url, req, config);
} catch (e) {
wxApi.logAndDisplayError({
message: e.message,
name: "tipping-failed",
response: e.response,
sameTab: true,
});
throw e;
}
try {
wxApi.processTipResponse(merchantDomain, tipToken.tip_id, merchantResp.data);
} catch (e) {
wxApi.logAndDisplayError({
message: e.message,
name: "tipping-failed",
response: e.response,
sameTab: true,
});
throw e;
}
// Go to tip dialog page, where the user can confirm the tip or
// decline if they are not happy with the exchange.
const uri = new URI(chrome.extension.getURL("/src/webex/pages/tip.html"));
const params = { tip_id: tipToken.tip_id, merchant_domain: merchantDomain };
const redirectUrl = uri.query(params).href();
window.location.href = redirectUrl;
return;
}
if (msg.refund_url) {
console.log("processing refund");
let resp;

View File

@ -22,18 +22,17 @@
* @author Florian Dold
*/
import {canonicalizeBaseUrl} from "../../helpers";
import { canonicalizeBaseUrl } from "../../helpers";
import * as i18n from "../../i18n";
import {
AmountJson,
Amounts,
CreateReserveResponse,
CurrencyRecord,
DenominationRecord,
ReserveCreationInfo,
} from "../../types";
import {ImplicitStateComponent, StateHolder} from "../components";
import { ImplicitStateComponent, StateHolder } from "../components";
import {
createReserve,
getCurrency,
@ -41,9 +40,8 @@ import {
getReserveCreationInfo,
} from "../wxApi";
import {Collapsible, renderAmount} from "../renderHtml";
import { renderAmount, WithdrawDetailView } from "../renderHtml";
import * as moment from "moment";
import * as React from "react";
import * as ReactDOM from "react-dom";
import URI = require("urijs");
@ -80,126 +78,6 @@ class EventTrigger {
}
function renderAuditorDetails(rci: ReserveCreationInfo|null) {
console.log("rci", rci);
if (!rci) {
return (
<p>
Details will be displayed when a valid exchange provider URL is entered.
</p>
);
}
if (rci.exchangeInfo.auditors.length === 0) {
return (
<p>
The exchange is not audited by any auditors.
</p>
);
}
return (
<div>
{rci.exchangeInfo.auditors.map((a) => (
<div>
<h3>Auditor {a.auditor_url}</h3>
<p>Public key: {a.auditor_pub}</p>
<p>Trusted: {rci.trustedAuditorPubs.indexOf(a.auditor_pub) >= 0 ? "yes" : "no"}</p>
<p>Audits {a.denomination_keys.length} of {rci.numOfferedDenoms} denominations</p>
</div>
))}
</div>
);
}
function renderReserveCreationDetails(rci: ReserveCreationInfo|null) {
if (!rci) {
return (
<p>
Details will be displayed when a valid exchange provider URL is entered.
</p>
);
}
const denoms = rci.selectedDenoms;
const countByPub: {[s: string]: number} = {};
const uniq: DenominationRecord[] = [];
denoms.forEach((x: DenominationRecord) => {
let c = countByPub[x.denomPub] || 0;
if (c === 0) {
uniq.push(x);
}
c += 1;
countByPub[x.denomPub] = c;
});
function row(denom: DenominationRecord) {
return (
<tr>
<td>{countByPub[denom.denomPub] + "x"}</td>
<td>{renderAmount(denom.value)}</td>
<td>{renderAmount(denom.feeWithdraw)}</td>
<td>{renderAmount(denom.feeRefresh)}</td>
<td>{renderAmount(denom.feeDeposit)}</td>
</tr>
);
}
function wireFee(s: string) {
return [
<thead>
<tr>
<th colSpan={3}>Wire Method {s}</th>
</tr>
<tr>
<th>Applies Until</th>
<th>Wire Fee</th>
<th>Closing Fee</th>
</tr>
</thead>,
<tbody>
{rci!.wireFees.feesForType[s].map((f) => (
<tr>
<td>{moment.unix(f.endStamp).format("llll")}</td>
<td>{renderAmount(f.wireFee)}</td>
<td>{renderAmount(f.closingFee)}</td>
</tr>
))}
</tbody>,
];
}
const withdrawFee = renderAmount(rci.withdrawFee);
const overhead = renderAmount(rci.overhead);
return (
<div>
<h3>Overview</h3>
<p>{i18n.str`Withdrawal fees:`} {withdrawFee}</p>
<p>{i18n.str`Rounding loss:`} {overhead}</p>
<p>{i18n.str`Earliest expiration (for deposit): ${moment.unix(rci.earliestDepositExpiration).fromNow()}`}</p>
<h3>Coin Fees</h3>
<table className="pure-table">
<thead>
<tr>
<th>{i18n.str`# Coins`}</th>
<th>{i18n.str`Value`}</th>
<th>{i18n.str`Withdraw Fee`}</th>
<th>{i18n.str`Refresh Fee`}</th>
<th>{i18n.str`Deposit Fee`}</th>
</tr>
</thead>
<tbody>
{uniq.map(row)}
</tbody>
</table>
<h3>Wire Fees</h3>
<table className="pure-table">
{Object.keys(rci.wireFees.feesForType).map(wireFee)}
</table>
</div>
);
}
interface ExchangeSelectionProps {
@ -428,12 +306,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
</button>
</p>
{this.renderUpdateStatus()}
<Collapsible initiallyCollapsed={true} title="Fee and Spending Details">
{renderReserveCreationDetails(this.reserveCreationInfo())}
</Collapsible>
<Collapsible initiallyCollapsed={true} title="Auditor Details">
{renderAuditorDetails(this.reserveCreationInfo())}
</Collapsible>
<WithdrawDetailView rci={this.reserveCreationInfo()} />
</div>
);
}

24
src/webex/pages/tip.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Taler Wallet: Received Tip</title>
<link rel="icon" href="/img/icon.png">
<link rel="stylesheet" type="text/css" href="../style/pure.css">
<link rel="stylesheet" type="text/css" href="../style/wallet.css">
<script src="/dist/page-common-bundle.js"></script>
<script src="/dist/tip-bundle.js"></script>
</head>
<body>
<section id="main">
<h1>GNU Taler Wallet</h1>
<div id="container"></div>
</section>
</body>
</html>

155
src/webex/pages/tip.tsx Normal file
View File

@ -0,0 +1,155 @@
/*
This file is part of TALER
(C) 2017 GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Page shown to the user to confirm creation
* of a reserve, usually requested by the bank.
*
* @author Florian Dold
*/
import * as React from "react";
import * as ReactDOM from "react-dom";
import URI = require("urijs");
import * as i18n from "../../i18n";
import {
acceptTip,
getTipStatus,
} from "../wxApi";
import { renderAmount, WithdrawDetailView } from "../renderHtml";
import { Amounts, TipStatus } from "../../types";
interface TipDisplayProps {
merchantDomain: string;
tipId: string;
}
interface TipDisplayState {
tipStatus?: TipStatus;
working: boolean;
}
class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> {
constructor(props: TipDisplayProps) {
super(props);
this.state = { working: false };
}
async update() {
let tipStatus = await getTipStatus(this.props.merchantDomain, this.props.tipId);
this.setState({ tipStatus });
}
componentDidMount() {
this.update();
const port = chrome.runtime.connect();
port.onMessage.addListener((msg: any) => {
if (msg.notify) {
console.log("got notified");
this.update();
}
});
this.update();
}
renderExchangeInfo(ts: TipStatus) {
const rci = ts.rci;
if (!rci) {
return <p>Waiting for info about exchange ...</p>
}
const totalCost = Amounts.add(rci.overhead, rci.withdrawFee).amount;
return (
<div>
<p>
The tip is handled by the exchange <strong>{rci.exchangeInfo.baseUrl}</strong>.{" "}
The exchange provider will charge
{" "}
<strong>{renderAmount(totalCost)}</strong>
{" "}.
</p>
<WithdrawDetailView rci={rci} />
</div>
);
}
accept() {
this.setState({ working: true});
acceptTip(this.props.merchantDomain, this.props.tipId);
}
renderButtons() {
return (
<form className="pure-form">
<button
className="pure-button pure-button-primary"
type="button"
onClick={() => this.accept()}>
{ this.state.working ? <span><object className="svg-icon svg-baseline" data="/img/spinner-bars.svg" /> </span> : null }
Accept tip
</button>
{" "}
<button className="pure-button" type="button" onClick={() => { window.close(); }}>Discard tip</button>
</form>
);
}
render(): JSX.Element {
const ts = this.state.tipStatus;
if (!ts) {
return <p>Processing ...</p>;
}
return (
<div>
<h2>Tip Received!</h2>
<p>You received a tip of <strong>{renderAmount(ts.tip.amount)}</strong> from <strong>{this.props.merchantDomain}</strong>.</p>
{ts.tip.accepted
? <p>You've accepted this tip!</p>
: this.renderButtons()
}
{this.renderExchangeInfo(ts)}
</div>
);
}
}
async function main() {
try {
const url = new URI(document.location.href);
const query: any = URI.parseQuery(url.query());
let merchantDomain = query.merchant_domain;
let tipId = query.tip_id;
let props: TipDisplayProps = { tipId, merchantDomain };
ReactDOM.render(<TipDisplay {...props} />,
document.getElementById("container")!);
} catch (e) {
// TODO: provide more context information, maybe factor it out into a
// TODO:generic error reporting function or component.
document.body.innerText = i18n.str`Fatal error: "${e.message}".`;
console.error(`got error "${e.message}"`, e);
}
}
document.addEventListener("DOMContentLoaded", () => {
main();
});

View File

@ -27,8 +27,14 @@
import {
AmountJson,
Amounts,
DenominationRecord,
ReserveCreationInfo,
} from "../types";
import * as moment from "moment";
import * as i18n from "../i18n";
import * as React from "react";
@ -101,3 +107,142 @@ export class Collapsible extends React.Component<CollapsibleProps, CollapsibleSt
);
}
}
function AuditorDetailsView(props: {rci: ReserveCreationInfo|null}): JSX.Element {
const rci = props.rci;
console.log("rci", rci);
if (!rci) {
return (
<p>
Details will be displayed when a valid exchange provider URL is entered.
</p>
);
}
if (rci.exchangeInfo.auditors.length === 0) {
return (
<p>
The exchange is not audited by any auditors.
</p>
);
}
return (
<div>
{rci.exchangeInfo.auditors.map((a) => (
<div>
<h3>Auditor {a.auditor_url}</h3>
<p>Public key: {a.auditor_pub}</p>
<p>Trusted: {rci.trustedAuditorPubs.indexOf(a.auditor_pub) >= 0 ? "yes" : "no"}</p>
<p>Audits {a.denomination_keys.length} of {rci.numOfferedDenoms} denominations</p>
</div>
))}
</div>
);
}
function FeeDetailsView(props: {rci: ReserveCreationInfo|null}): JSX.Element {
const rci = props.rci;
if (!rci) {
return (
<p>
Details will be displayed when a valid exchange provider URL is entered.
</p>
);
}
const denoms = rci.selectedDenoms;
const countByPub: {[s: string]: number} = {};
const uniq: DenominationRecord[] = [];
denoms.forEach((x: DenominationRecord) => {
let c = countByPub[x.denomPub] || 0;
if (c === 0) {
uniq.push(x);
}
c += 1;
countByPub[x.denomPub] = c;
});
function row(denom: DenominationRecord) {
return (
<tr>
<td>{countByPub[denom.denomPub] + "x"}</td>
<td>{renderAmount(denom.value)}</td>
<td>{renderAmount(denom.feeWithdraw)}</td>
<td>{renderAmount(denom.feeRefresh)}</td>
<td>{renderAmount(denom.feeDeposit)}</td>
</tr>
);
}
function wireFee(s: string) {
return [
<thead>
<tr>
<th colSpan={3}>Wire Method {s}</th>
</tr>
<tr>
<th>Applies Until</th>
<th>Wire Fee</th>
<th>Closing Fee</th>
</tr>
</thead>,
<tbody>
{rci!.wireFees.feesForType[s].map((f) => (
<tr>
<td>{moment.unix(f.endStamp).format("llll")}</td>
<td>{renderAmount(f.wireFee)}</td>
<td>{renderAmount(f.closingFee)}</td>
</tr>
))}
</tbody>,
];
}
const withdrawFee = renderAmount(rci.withdrawFee);
const overhead = renderAmount(rci.overhead);
return (
<div>
<h3>Overview</h3>
<p>{i18n.str`Withdrawal fees:`} {withdrawFee}</p>
<p>{i18n.str`Rounding loss:`} {overhead}</p>
<p>{i18n.str`Earliest expiration (for deposit): ${moment.unix(rci.earliestDepositExpiration).fromNow()}`}</p>
<h3>Coin Fees</h3>
<table className="pure-table">
<thead>
<tr>
<th>{i18n.str`# Coins`}</th>
<th>{i18n.str`Value`}</th>
<th>{i18n.str`Withdraw Fee`}</th>
<th>{i18n.str`Refresh Fee`}</th>
<th>{i18n.str`Deposit Fee`}</th>
</tr>
</thead>
<tbody>
{uniq.map(row)}
</tbody>
</table>
<h3>Wire Fees</h3>
<table className="pure-table">
{Object.keys(rci.wireFees.feesForType).map(wireFee)}
</table>
</div>
);
}
export function WithdrawDetailView(props: {rci: ReserveCreationInfo | null}): JSX.Element {
const rci = props.rci;
return (
<div>
<Collapsible initiallyCollapsed={true} title="Fee and Spending Details">
<FeeDetailsView rci={rci} />
</Collapsible>
<Collapsible initiallyCollapsed={true} title="Auditor Details">
<AuditorDetailsView rci={rci} />
</Collapsible>
</div>
);
}

View File

@ -251,3 +251,18 @@ a.opener {
.opener-collapsed::before {
content: "\25b6 "
}
.svg-icon {
display: inline-flex;
align-self: center;
position: relative;
height: 1em;
width: 1em;
}
.svg-icon svg {
height:1em;
width:1em;
}
object.svg-icon.svg-baseline {
transform: translate(0, 0.125em);
}

View File

@ -37,6 +37,9 @@ import {
ReserveCreationInfo,
ReserveRecord,
SenderWireInfos,
TipResponse,
TipPlanchetDetail,
TipStatus,
WalletBalance,
} from "../types";
@ -358,3 +361,23 @@ export function getPurchase(contractTermsHash: string): Promise<PurchaseRecord>
export function getFullRefundFees(args: { refundPermissions: RefundPermission[] }): Promise<AmountJson> {
return callBackend("get-full-refund-fees", { refundPermissions: args.refundPermissions });
}
/**
* Get or generate planchets to give the merchant that wants to tip us.
*/
export function getTipPlanchets(merchantDomain: string, tipId: string, amount: AmountJson, deadline: number, exchangeUrl: string): Promise<TipPlanchetDetail[]> {
return callBackend("get-tip-planchets", { merchantDomain, tipId, amount, deadline, exchangeUrl });
}
export function getTipStatus(merchantDomain: string, tipId: string): Promise<TipStatus> {
return callBackend("get-tip-status", { merchantDomain, tipId });
}
export function acceptTip(merchantDomain: string, tipId: string): Promise<TipStatus> {
return callBackend("accept-tip", { merchantDomain, tipId });
}
export function processTipResponse(merchantDomain: string, tipId: string, tipResponse: TipResponse): Promise<void> {
return callBackend("process-tip-response", { merchantDomain, tipId, tipResponse });
}

View File

@ -31,12 +31,16 @@ import {
Store,
} from "../query";
import {
AcceptTipRequest,
AmountJson,
ConfirmReserveRequest,
CreateReserveRequest,
GetTipPlanchetsRequest,
Notifier,
ProcessTipResponseRequest,
ProposalRecord,
ReturnCoinsRequest,
TipStatusRequest,
} from "../types";
import {
Stores,
@ -44,6 +48,7 @@ import {
Wallet,
} from "../wallet";
import { ChromeBadge } from "./chromeBadge";
import { MessageType } from "./messages";
import * as wxApi from "./wxApi";
@ -316,6 +321,22 @@ function handleMessage(sender: MessageSender,
}
case "get-full-refund-fees":
return needsWallet().getFullRefundFees(detail.refundPermissions);
case "get-tip-status": {
const req = TipStatusRequest.checked(detail);
return needsWallet().getTipStatus(req.merchantDomain, req.tipId);
}
case "accept-tip": {
const req = AcceptTipRequest.checked(detail);
return needsWallet().acceptTip(req.merchantDomain, req.tipId);
}
case "process-tip-response": {
const req = ProcessTipResponseRequest.checked(detail);
return needsWallet().processTipResponse(req.merchantDomain, req.tipId, req.tipResponse);
}
case "get-tip-planchets": {
const req = GetTipPlanchetsRequest.checked(detail);
return needsWallet().getTipPlanchets(req.merchantDomain, req.tipId, req.amount, req.deadline, req.exchangeUrl);
}
default:
// Exhaustiveness check.
// See https://www.typescriptlang.org/docs/handbook/advanced-types.html
@ -409,6 +430,7 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri
contract_url: headers["x-taler-contract-url"],
offer_url: headers["x-taler-offer-url"],
refund_url: headers["x-taler-refund-url"],
tip: headers["x-taler-tip"],
};
const talerHeaderFound = Object.keys(fields).filter((x: any) => (fields as any)[x]).length !== 0;
@ -424,6 +446,7 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri
contract_url: fields.contract_url,
offer_url: fields.offer_url,
refund_url: fields.refund_url,
tip: fields.tip,
};
console.log("got pay detail", payDetail);
@ -728,7 +751,7 @@ function openTalerDb(): Promise<IDBDatabase> {
for (const indexName in (si as any)) {
if ((si as any)[indexName] instanceof Index) {
const ii: Index<any, any> = (si as any)[indexName];
s.createIndex(ii.indexName, ii.keyPath);
s.createIndex(ii.indexName, ii.keyPath, ii.options);
}
}
}

View File

@ -65,6 +65,7 @@
"src/webex/pages/reset-required.tsx",
"src/webex/pages/return-coins.tsx",
"src/webex/pages/show-db.ts",
"src/webex/pages/tip.tsx",
"src/webex/pages/tree.tsx",
"src/webex/renderHtml.tsx",
"src/webex/wxApi.ts",

View File

@ -78,6 +78,7 @@ module.exports = function (env) {
"return-coins": "./src/webex/pages/return-coins.tsx",
"refund": "./src/webex/pages/refund.tsx",
"show-db": "./src/webex/pages/show-db.ts",
"tip": "./src/webex/pages/tip.tsx",
"tree": "./src/webex/pages/tree.tsx",
},
plugins: [