From 029340469ac661bab1450b1ff4dcc6e717780e5b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 29 May 2022 01:23:15 -0300 Subject: show refund info in purchase notify refund pending with accept button on the purchase details better payto box --- packages/taler-util/src/transactionsTypes.ts | 12 ++++ packages/taler-util/src/walletTypes.ts | 9 +++ .../taler-wallet-core/src/operations/refund.ts | 14 +++- .../src/operations/transactions.ts | 13 +++- packages/taler-wallet-core/src/wallet.ts | 6 ++ .../src/components/Part.tsx | 29 +++++--- .../src/components/TransactionItem.tsx | 2 +- .../src/wallet/History.stories.tsx | 1 + .../src/wallet/History.tsx | 2 +- .../src/wallet/ReserveCreated.stories.tsx | 2 +- .../src/wallet/Transaction.stories.tsx | 30 ++++++++- .../src/wallet/Transaction.tsx | 78 ++++++++++++++++++++-- packages/taler-wallet-webextension/src/wxApi.ts | 9 +++ 13 files changed, 183 insertions(+), 24 deletions(-) (limited to 'packages') diff --git a/packages/taler-util/src/transactionsTypes.ts b/packages/taler-util/src/transactionsTypes.ts index dcaa56675..f7383f902 100644 --- a/packages/taler-util/src/transactionsTypes.ts +++ b/packages/taler-util/src/transactionsTypes.ts @@ -244,6 +244,11 @@ export interface TransactionPayment extends TransactionCommon { * Amount pending to be picked up */ refundPending: AmountString | undefined; + + /** + * Reference to applied refunds + */ + refunds: RefundInfoShort[]; } export interface OrderShortInfo { @@ -305,6 +310,13 @@ export interface OrderShortInfo { fulfillmentMessage_i18n?: InternationalizedString; } +export interface RefundInfoShort { + transactionId: string, + timestamp: TalerProtocolTimestamp, + amountEffective: AmountString, + amountRaw: AmountString, +} + export interface TransactionRefund extends TransactionCommon { type: TransactionType.Refund; diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts index fa884c414..00a489861 100644 --- a/packages/taler-util/src/walletTypes.ts +++ b/packages/taler-util/src/walletTypes.ts @@ -799,6 +799,15 @@ export const codecForApplyRefundRequest = (): Codec => .property("talerRefundUri", codecForString()) .build("ApplyRefundRequest"); +export interface ApplyRefundFromPurchaseIdRequest { + purchaseId: string; +} + +export const codecForApplyRefundFromPurchaseIdRequest = (): Codec => + buildCodecForObject() + .property("purchaseId", codecForString()) + .build("ApplyRefundFromPurchaseIdRequest"); + export interface GetWithdrawalDetailsForUriRequest { talerWithdrawUri: string; restrictAge?: number; diff --git a/packages/taler-wallet-core/src/operations/refund.ts b/packages/taler-wallet-core/src/operations/refund.ts index 186fbf7d3..28a92286b 100644 --- a/packages/taler-wallet-core/src/operations/refund.ts +++ b/packages/taler-wallet-core/src/operations/refund.ts @@ -573,7 +573,7 @@ export async function applyRefund( throw Error("invalid refund URI"); } - let purchase = await ws.db + const purchase = await ws.db .mktx((x) => ({ purchases: x.purchases, })) @@ -590,7 +590,15 @@ export async function applyRefund( ); } - const proposalId = purchase.proposalId; + return applyRefundFromPurchaseId(ws, purchase.proposalId) +} + +export async function applyRefundFromPurchaseId( + ws: InternalWalletState, + proposalId: string, +): Promise { + + logger.trace("applying refund for purchase", proposalId); logger.info("processing purchase for refund"); const success = await ws.db @@ -620,7 +628,7 @@ export async function applyRefund( }); } - purchase = await ws.db + const purchase = await ws.db .mktx((x) => ({ purchases: x.purchases, })) diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 87b109d98..db282bb68 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -24,6 +24,7 @@ import { Logger, OrderShortInfo, PaymentStatus, + RefundInfoShort, Transaction, TransactionsRequest, TransactionsResponse, @@ -306,6 +307,7 @@ export async function getTransactions( let totalRefundRaw = Amounts.getZero(contractData.amount.currency); let totalRefundEffective = Amounts.getZero(contractData.amount.currency); + const refunds: RefundInfoShort[] = [] for (const groupKey of refundGroupKeys.values()) { const refundTombstoneId = makeEventId( @@ -345,6 +347,13 @@ export async function getTransactions( refund.totalRefreshCostBound, ).amount, ).amount; + + refunds.push({ + transactionId: refundTransactionId, + timestamp: r0.obtainedTime, + amountEffective: Amounts.stringify(amountEffective), + amountRaw: Amounts.stringify(amountRaw), + }) } } if (!r0) { @@ -353,7 +362,6 @@ export async function getTransactions( totalRefundRaw = Amounts.add(totalRefundRaw, amountRaw).amount; totalRefundEffective = Amounts.add(totalRefundEffective, amountEffective).amount; - transactions.push({ type: TransactionType.Refund, info, @@ -382,10 +390,11 @@ export async function getTransactions( pending: !pr.timestampFirstSuccessfulPay && pr.abortStatus === AbortStatus.None, + refunds, timestamp: pr.timestampAccept, transactionId: paymentTransactionId, proposalId: pr.proposalId, - info: info, + info, frozen: pr.payFrozen ?? false, ...(err ? { error: err } : {}), }); diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index ffceec38f..689e45f3c 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -33,6 +33,7 @@ import { codecForAcceptTipRequest, codecForAddExchangeRequest, codecForAny, + codecForApplyRefundFromPurchaseIdRequest, codecForApplyRefundRequest, codecForConfirmPayRequest, codecForCreateDepositGroupRequest, @@ -145,6 +146,7 @@ import { import { abortFailedPayWithRefund, applyRefund, + applyRefundFromPurchaseId, prepareRefund, processPurchaseQueryRefund } from "./operations/refund.js"; @@ -839,6 +841,10 @@ async function dispatchRequestInternal( const req = codecForApplyRefundRequest().decode(payload); return await applyRefund(ws, req.talerRefundUri); } + case "applyRefundFromPurchaseId": { + const req = codecForApplyRefundFromPurchaseIdRequest().decode(payload); + return await applyRefundFromPurchaseId(ws, req.purchaseId); + } case "acceptBankIntegratedWithdrawal": { const req = codecForAcceptBankIntegratedWithdrawalRequest().decode(payload); diff --git a/packages/taler-wallet-webextension/src/components/Part.tsx b/packages/taler-wallet-webextension/src/components/Part.tsx index 58165a349..06e2985cf 100644 --- a/packages/taler-wallet-webextension/src/components/Part.tsx +++ b/packages/taler-wallet-webextension/src/components/Part.tsx @@ -92,6 +92,7 @@ const CollasibleBox = styled.div` } `; import arrowDown from "../svg/chevron-down.svg"; +import { useTranslationContext } from "../context/translation.js"; export function PartCollapsible({ text, title, big, showSign }: Props): VNode { const Text = big ? ExtraLargeText : LargeText; @@ -137,27 +138,37 @@ interface PropsPayto { } export function PartPayto({ payto, kind, big }: PropsPayto): VNode { const Text = big ? ExtraLargeText : LargeText; - let text: string | undefined = undefined; + let text: VNode | undefined = undefined; let title = ""; + const { i18n } = useTranslationContext(); if (payto.isKnown) { if (payto.targetType === "x-taler-bank") { - text = payto.account; - title = "Bank account"; + text = {payto.account}; + title = i18n.str`Bank account`; } else if (payto.targetType === "bitcoin") { - text = payto.targetPath; - title = "Bitcoin addr"; + text = + payto.segwitAddrs && payto.segwitAddrs.length > 0 ? ( +
    +
  • {payto.targetPath}
  • +
  • {payto.segwitAddrs[0]}
  • +
  • {payto.segwitAddrs[1]}
  • +
+ ) : ( + {payto.targetPath} + ); + title = i18n.str`Bitcoin address`; } else if (payto.targetType === "iban") { - text = payto.targetPath; - title = "IBAN"; + text = {payto.targetPath}; + title = i18n.str`IBAN`; } } if (!text) { - text = stringifyPaytoUri(payto); + text = {stringifyPaytoUri(payto)}; title = "Payto URI"; } return (
- {title} + {title} {sign} - {Amounts.stringifyValue(props.amount)} + {Amounts.stringifyValue(props.amount, 2)} {props.pending && (
diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx index 3080a866e..004327c94 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx @@ -78,6 +78,7 @@ const exampleData = { summary: "the summary", fulfillmentMessage: "", }, + refunds: [], refundPending: undefined, totalRefundEffective: "USD:0", totalRefundRaw: "USD:0", diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx index 4a435d0cf..59f245522 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.tsx @@ -193,7 +193,7 @@ export function HistoryView({ margin: 8, }} > - {Amounts.stringifyValue(currencyAmount)} + {Amounts.stringifyValue(currencyAmount, 2)} )}
diff --git a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx index 587e24e98..895c301c2 100644 --- a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx @@ -45,7 +45,7 @@ export const TalerBank = createExample(TestedComponent, { export const IBAN = createExample(TestedComponent, { reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", paytoURI: parsePaytoUri( - "payto://iban/ASDQWEASDZXCASDQWE?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", + "payto://iban/ES8877998399652238?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", ), amount: { currency: "USD", diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx index 493cdd1d7..83848d005 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx @@ -93,6 +93,7 @@ const exampleData = { // address_lines: [""], // }, }, + refunds: [], refundPending: undefined, totalRefundEffective: "KUDOS:0", totalRefundRaw: "KUDOS:0", @@ -199,7 +200,7 @@ export const WithdrawPendingManual = createExample(TestedComponent, () => ({ ...exampleData.withdraw, withdrawalDetails: { type: WithdrawalType.ManualTransfer, - exchangePaytoUris: ["payto://iban/asdasdasd"], + exchangePaytoUris: ["payto://iban/ES8877998399652238"], reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", } as WithdrawalDetails, pending: true, @@ -254,6 +255,14 @@ export const PaymentWithRefund = createExample(TestedComponent, { amountRaw: "KUDOS:12", totalRefundEffective: "KUDOS:1", totalRefundRaw: "KUDOS:1", + refunds: [ + { + transactionId: "1123123", + amountRaw: "KUDOS:1", + amountEffective: "KUDOS:1", + timestamp: TalerProtocolTimestamp.fromSeconds(1546546544), + }, + ], }, }); @@ -410,6 +419,25 @@ export const PaymentWithLongSummary = createExample(TestedComponent, { export const Deposit = createExample(TestedComponent, { transaction: exampleData.deposit, }); +export const DepositTalerBank = createExample(TestedComponent, { + transaction: { + ...exampleData.deposit, + targetPaytoUri: "payto://x-taler-bank/bank.demo.taler.net/Exchange", + }, +}); +export const DepositBitcoin = createExample(TestedComponent, { + transaction: { + ...exampleData.deposit, + targetPaytoUri: + "payto://bitcoin/bcrt1q6ps8qs6v8tkqrnru4xqqqa6rfwcx5ufpdfqht4?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00", + }, +}); +export const DepositIBAN = createExample(TestedComponent, { + transaction: { + ...exampleData.deposit, + targetPaytoUri: "payto://iban/ES8877998399652238", + }, +}); export const DepositError = createExample(TestedComponent, { transaction: { diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index 9ccb353a9..8165953ab 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -22,6 +22,8 @@ import { NotificationType, parsePaytoUri, parsePayUri, + PaytoUri, + stringifyPaytoUri, TalerProtocolTimestamp, Transaction, TransactionDeposit, @@ -50,6 +52,7 @@ import { ButtonDestructive, ButtonPrimary, CenteredDialog, + HistoryRow, InfoBox, ListOfProducts, Overlay, @@ -83,7 +86,7 @@ async function getTransaction(tid: string): Promise { export function TransactionPage({ tid, goToWalletHistory }: Props): VNode { const { i18n } = useTranslationContext(); - const state = useAsyncAsHook(() => getTransaction(tid)); + const state = useAsyncAsHook(() => getTransaction(tid), [tid]); useEffect(() => { wxApi.onUpdateNotification([NotificationType.WithdrawGroupFinished], () => { @@ -119,6 +122,7 @@ export function TransactionPage({ tid, goToWalletHistory }: Props): VNode { onRetry={() => wxApi.retryTransaction(tid).then(() => goToWalletHistory(currency)) } + onRefund={(id) => wxApi.applyRefundFromPurchaseId(id)} onBack={() => goToWalletHistory(currency)} /> ); @@ -128,6 +132,7 @@ export interface WalletTransactionProps { transaction: Transaction; onDelete: () => void; onRetry: () => void; + onRefund: (id: string) => void; onBack: () => void; } @@ -143,7 +148,7 @@ export function TransactionView({ transaction, onDelete, onRetry, - onBack, + onRefund, }: WalletTransactionProps): VNode { const [confirmBeforeForget, setConfirmBeforeForget] = useState(false); @@ -334,6 +339,40 @@ export function TransactionView({ )}
+ {transaction.refunds.length > 0 ? ( + Refunds} + text={ + + {transaction.refunds.map((r, i) => { + return ( + + + + ); + })} +
+ {}{" "} + + was refunded + {" "} + on{" "} + { +
+ } + kind="neutral" + /> + ) : undefined} {pendingRefund !== undefined && Amounts.isNonZero(pendingRefund) && ( @@ -348,7 +387,7 @@ export function TransactionView({
- + onRefund(transaction.proposalId)}> Accept
@@ -385,9 +424,9 @@ export function TransactionView({ total={total} kind="negative" > - {transaction.targetPaytoUri} + {!payto ? transaction.targetPaytoUri : } - {payto && } + {payto && } Details} text={} @@ -669,7 +708,7 @@ function PurchaseDetails({ Refunded - + )} @@ -988,3 +1027,30 @@ function Header({
); } + +function NicePayto({ payto }: { payto: PaytoUri }): VNode { + if (payto.isKnown) { + switch (payto.targetType) { + case "bitcoin": { + return
{payto.targetPath.substring(0, 20)}...
; + } + case "x-taler-bank": { + const url = new URL("/", `https://${payto.host}`); + return ( + +
{payto.account}
+ + + {url.toString()} + + +
+ ); + } + case "iban": { + return
{payto.targetPath.substring(0, 20)}
; + } + } + } + return {stringifyPaytoUri(payto)}; +} diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index dd4eb2cf4..63840017b 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -311,6 +311,15 @@ export function applyRefund( return callBackend("applyRefund", { talerRefundUri }); } +/** + * Do refund for purchase. + */ +export function applyRefundFromPurchaseId( + purchaseId: string, +): Promise { + return callBackend("applyRefundFromPurchaseId", { purchaseId }); +} + /** * Get details about a pay operation. */ -- cgit v1.2.3