diff options
4 files changed, 241 insertions, 24 deletions
diff --git a/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx index 30c2ef833..f8983b995 100644 --- a/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx +++ b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx @@ -17,6 +17,7 @@ import { AmountJson, Amounts, + PayMerchantInsufficientBalanceDetails, PreparePayResult, PreparePayResultType, TranslatedString, @@ -35,7 +36,6 @@ import { assertUnreachable } from "../utils/index.js"; interface Props { payStatus: PreparePayResult; payHandler: ButtonHandler | undefined; - balance: AmountJson | undefined; uri: string; amount: AmountJson; goToWalletManualWithdraw: (currency: string) => Promise<void>; @@ -45,7 +45,6 @@ export function PaymentButtons({ payStatus, uri, payHandler, - balance, amount, goToWalletManualWithdraw, }: Props): VNode { @@ -73,16 +72,58 @@ export function PaymentButtons({ } if (payStatus.status === PreparePayResultType.InsufficientBalance) { + const reason = getReason(payStatus.balanceDetails); + let BalanceMessage = ""; - if (!balance) { - BalanceMessage = i18n.str`You have no balance for this currency. Withdraw digital cash first.`; - } else { - const balanceShouldBeEnough = Amounts.cmp(balance, amount) !== -1; - if (balanceShouldBeEnough) { - BalanceMessage = i18n.str`Could not find enough coins to pay. Even if you have enough ${balance.currency} some restriction may apply.`; - } else { - BalanceMessage = i18n.str`Your current balance is not enough.`; + switch (reason) { + case "age-acceptable": { + BalanceMessage = i18n.str`Balance is not enough because you have ${Amounts.stringifyValue( + payStatus.balanceDetails.balanceAgeAcceptable, + )} ${amount.currency} to pay for contracts restricted for age above ${ + payStatus.contractTerms.minimum_age + } years old`; + break; + } + case "available": { + BalanceMessage = i18n.str`Balance is not enough because you have ${Amounts.stringifyValue( + payStatus.balanceDetails.balanceAvailable, + )} ${amount.currency} available.`; + break; + } + case "merchant-acceptable": { + BalanceMessage = i18n.str`Balance is not enough because merchant will just accept ${Amounts.stringifyValue( + payStatus.balanceDetails.balanceMerchantAcceptable, + )} ${ + amount.currency + } . To know more you can check which exchange and auditors the merchant trust.`; + break; + } + case "merchant-depositable": { + BalanceMessage = i18n.str`Balance is not enough because merchant will just accept ${Amounts.stringifyValue( + payStatus.balanceDetails.balanceMerchantDepositable, + )} ${ + amount.currency + } . To know more you can check which wire methods the merchant accepts.`; + break; } + case "material": { + BalanceMessage = i18n.str`Balance is not enough because you have ${Amounts.stringifyValue( + payStatus.balanceDetails.balanceMaterial, + )} ${ + amount.currency + } to spend right know. There are some coins that need to be refreshed.`; + break; + } + case "fee-gap": { + BalanceMessage = i18n.str`Balance looks like it should be enough, but doesn't cover all fees requested by the merchant and payment processor. Please ensure there is at least ${Amounts.stringifyValue( + payStatus.balanceDetails.feeGapEstimate, + )} ${ + amount.currency + } more balance in your wallet or ask your merchant to cover more of the fees.`; + break; + } + default: + assertUnreachable(reason); } const uriPrivate = `${uri}&n=${payStatus.noncePriv}`; @@ -150,3 +191,32 @@ function PayWithMobile({ uri }: { uri: string }): VNode { </section> ); } + +type NoEnoughBalanceReason = + | "available" + | "material" + | "age-acceptable" + | "merchant-acceptable" + | "merchant-depositable" + | "fee-gap"; + +function getReason( + info: PayMerchantInsufficientBalanceDetails, +): NoEnoughBalanceReason { + if (Amounts.cmp(info.amountRequested, info.balanceAvailable)) { + return "available"; + } + if (Amounts.cmp(info.amountRequested, info.balanceMaterial)) { + return "material"; + } + if (Amounts.cmp(info.amountRequested, info.balanceAgeAcceptable)) { + return "age-acceptable"; + } + if (Amounts.cmp(info.amountRequested, info.balanceMerchantAcceptable)) { + return "merchant-acceptable"; + } + if (Amounts.cmp(info.amountRequested, info.balanceMerchantDepositable)) { + return "merchant-depositable"; + } + return "fee-gap"; +} diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx b/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx index 9a748891c..f0e23b49b 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/views.tsx @@ -28,8 +28,7 @@ export function ReadyView( state: State.Ready | State.NoBalanceForCurrency | State.NoEnoughBalance, ): VNode { const { i18n } = useTranslationContext(); - const { summary, amount, expiration, uri, status, balance, payStatus } = - state; + const { summary, amount, expiration, uri, status, payStatus } = state; return ( <WalletAction> <LogoHeader /> @@ -47,7 +46,6 @@ export function ReadyView( </section> <PaymentButtons amount={amount} - balance={balance} payStatus={payStatus} uri={uri} payHandler={status === "ready" ? state.accept : undefined} diff --git a/packages/taler-wallet-webextension/src/cta/Payment/stories.tsx b/packages/taler-wallet-webextension/src/cta/Payment/stories.tsx index b63190236..2ac3ab57d 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/Payment/stories.tsx @@ -36,16 +36,106 @@ export default { argTypes: {}, }; -export const NoBalance = tests.createExample(BaseView, { - status: "no-balance-for-currency", +export const NoEnoughBalanceAvailable = tests.createExample(BaseView, { + status: "no-enough-balance", + error: undefined, + amount: Amounts.parseOrThrow("USD:10"), + balance: { + currency: "USD", + fraction: 40000000, + value: 12, + }, + + uri: "", + payStatus: { + status: PreparePayResultType.InsufficientBalance, + balanceDetails: { + amountRequested: "USD:10", + balanceAvailable: "USD:9", + balanceMaterial: "USD:9", + balanceAgeAcceptable: "USD:9", + balanceMerchantAcceptable: "USD:9", + balanceMerchantDepositable: "USD:9", + feeGapEstimate: "USD:1", + }, + talerUri: "taler://pay/..", + noncePriv: "", + proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0", + contractTerms: { + merchant: { + name: "the merchant", + logo: merchantIcon, + website: "https://www.themerchant.taler", + email: "contact@merchant.taler", + }, + summary: "some beers", + amount: "USD:10", + } as Partial<ContractTerms> as any, + amountRaw: "USD:10", + }, +}); + +export const NoEnoughBalanceMaterial = tests.createExample(BaseView, { + status: "no-enough-balance", + error: undefined, + amount: Amounts.parseOrThrow("USD:10"), + balance: { + currency: "USD", + fraction: 40000000, + value: 12, + }, + + uri: "", + payStatus: { + status: PreparePayResultType.InsufficientBalance, + balanceDetails: { + amountRequested: "USD:10", + balanceAvailable: "USD:10", + balanceMaterial: "USD:9", + balanceAgeAcceptable: "USD:9", + balanceMerchantAcceptable: "USD:9", + balanceMerchantDepositable: "USD:0", + feeGapEstimate: "USD:1", + }, + talerUri: "taler://pay/..", + noncePriv: "", + proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0", + contractTerms: { + merchant: { + name: "the merchant", + logo: merchantIcon, + website: "https://www.themerchant.taler", + email: "contact@merchant.taler", + }, + summary: "some beers", + amount: "USD:10", + } as Partial<ContractTerms> as any, + amountRaw: "USD:10", + }, +}); + +export const NoEnoughBalanceAgeAcceptable = tests.createExample(BaseView, { + status: "no-enough-balance", error: undefined, amount: Amounts.parseOrThrow("USD:10"), - balance: undefined, + balance: { + currency: "USD", + fraction: 40000000, + value: 12, + }, uri: "", payStatus: { status: PreparePayResultType.InsufficientBalance, - balanceDetails: {} as any, + balanceDetails: { + amountRequested: "USD:10", + balanceAvailable: "USD:10", + balanceMaterial: "USD:10", + balanceAgeAcceptable: "USD:9", + balanceMerchantAcceptable: "USD:9", + balanceMerchantDepositable: "USD:9", + feeGapEstimate: "USD:1", + }, talerUri: "taler://pay/..", noncePriv: "", proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0", @@ -56,6 +146,7 @@ export const NoBalance = tests.createExample(BaseView, { website: "https://www.themerchant.taler", email: "contact@merchant.taler", }, + minimum_age: 18, summary: "some beers", amount: "USD:10", } as Partial<ContractTerms> as any, @@ -63,20 +154,28 @@ export const NoBalance = tests.createExample(BaseView, { }, }); -export const NoEnoughBalance = tests.createExample(BaseView, { +export const NoEnoughBalanceMerchantAcceptable = tests.createExample(BaseView, { status: "no-enough-balance", error: undefined, amount: Amounts.parseOrThrow("USD:10"), balance: { currency: "USD", fraction: 40000000, - value: 9, + value: 12, }, uri: "", payStatus: { status: PreparePayResultType.InsufficientBalance, - balanceDetails: {} as any, + balanceDetails: { + amountRequested: "USD:10", + balanceAvailable: "USD:10", + balanceMaterial: "USD:10", + balanceAgeAcceptable: "USD:10", + balanceMerchantAcceptable: "USD:9", + balanceMerchantDepositable: "USD:9", + feeGapEstimate: "USD:1", + }, talerUri: "taler://pay/..", noncePriv: "", proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0", @@ -94,20 +193,70 @@ export const NoEnoughBalance = tests.createExample(BaseView, { }, }); -export const EnoughBalanceButRestricted = tests.createExample(BaseView, { +export const NoEnoughBalanceMerchantDepositable = tests.createExample( + BaseView, + { + status: "no-enough-balance", + error: undefined, + amount: Amounts.parseOrThrow("USD:10"), + balance: { + currency: "USD", + fraction: 40000000, + value: 12, + }, + + uri: "", + payStatus: { + status: PreparePayResultType.InsufficientBalance, + balanceDetails: { + amountRequested: "USD:10", + balanceAvailable: "USD:10", + balanceMaterial: "USD:10", + balanceAgeAcceptable: "USD:10", + balanceMerchantAcceptable: "USD:10", + balanceMerchantDepositable: "USD:9", + feeGapEstimate: "USD:1", + }, + talerUri: "taler://pay/..", + noncePriv: "", + proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0", + contractTerms: { + merchant: { + name: "the merchant", + logo: merchantIcon, + website: "https://www.themerchant.taler", + email: "contact@merchant.taler", + }, + summary: "some beers", + amount: "USD:10", + } as Partial<ContractTerms> as any, + amountRaw: "USD:10", + }, + }, +); + +export const NoEnoughBalanceFeeGap = tests.createExample(BaseView, { status: "no-enough-balance", error: undefined, amount: Amounts.parseOrThrow("USD:10"), balance: { currency: "USD", fraction: 40000000, - value: 19, + value: 12, }, uri: "", payStatus: { status: PreparePayResultType.InsufficientBalance, - balanceDetails: {} as any, + balanceDetails: { + amountRequested: "USD:10", + balanceAvailable: "USD:10", + balanceMaterial: "USD:10", + balanceAgeAcceptable: "USD:10", + balanceMerchantAcceptable: "USD:10", + balanceMerchantDepositable: "USD:10", + feeGapEstimate: "USD:1", + }, talerUri: "taler://pay/..", noncePriv: "", proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0", @@ -118,6 +267,7 @@ export const EnoughBalanceButRestricted = tests.createExample(BaseView, { website: "https://www.themerchant.taler", email: "contact@merchant.taler", }, + minimum_age: 18, summary: "some beers", amount: "USD:10", } as Partial<ContractTerms> as any, diff --git a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx index 53bc0c95f..f5a354786 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Payment/views.tsx @@ -105,7 +105,6 @@ export function BaseView(state: SupportedStates): VNode { </section> <PaymentButtons amount={state.amount} - balance={state.balance} payStatus={state.payStatus} uri={state.uri} payHandler={state.status === "ready" ? state.payHandler : undefined} |