From 0e88ef9bd2ea76e5b44cc0d4459b9a2e553b8d24 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 24 Aug 2020 19:39:09 +0530 Subject: implement fulfillment_message and make fulfillment_url optional --- packages/taler-wallet-core/src/i18n/index.ts | 9 ++++ packages/taler-wallet-core/src/operations/pay.ts | 33 +++++--------- packages/taler-wallet-core/src/operations/state.ts | 3 +- .../src/operations/transactions.ts | 4 +- packages/taler-wallet-core/src/types/dbTypes.ts | 5 ++- packages/taler-wallet-core/src/types/talerTypes.ts | 28 +++++++++--- .../taler-wallet-core/src/types/transactions.ts | 51 ++++++++++++++++++---- .../taler-wallet-core/src/types/walletTypes.ts | 29 ++++-------- 8 files changed, 99 insertions(+), 63 deletions(-) (limited to 'packages/taler-wallet-core') diff --git a/packages/taler-wallet-core/src/i18n/index.ts b/packages/taler-wallet-core/src/i18n/index.ts index c5b70b1fd..b8788115c 100644 --- a/packages/taler-wallet-core/src/i18n/index.ts +++ b/packages/taler-wallet-core/src/i18n/index.ts @@ -79,3 +79,12 @@ export function str(stringSeq: TemplateStringsArray, ...values: any[]): string { .fetch(...values); return tr; } + +/** + * Get an internationalized string (based on the globally set, current language) + * from a JSON object. Fall back to the default language of the JSON object + * if no match exists. + */ +export function getJsonI18n(obj: Record, key: K): string { + return obj[key]; +} \ No newline at end of file diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 0d1d4f993..6b45e3da2 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -513,17 +513,6 @@ async function recordConfirmPay( return t; } -function getNextUrl(contractData: WalletContractData): string { - const f = contractData.fulfillmentUrl; - if (f.startsWith("http://") || f.startsWith("https://")) { - const fu = new URL(contractData.fulfillmentUrl); - fu.searchParams.set("order_id", contractData.orderId); - return fu.href; - } else { - return f; - } -} - async function incrementProposalRetry( ws: InternalWalletState, proposalId: string, @@ -642,7 +631,10 @@ async function processDownloadProposalImpl( const httpResponse = await ws.http.postJson(orderClaimUrl, requestBody, { timeout: getProposalRequestTimeout(proposal), }); - const r = await readSuccessResponseJsonOrErrorCode(httpResponse, codecForProposal()); + const r = await readSuccessResponseJsonOrErrorCode( + httpResponse, + codecForProposal(), + ); if (r.isError) { switch (r.talerErrorResponse.code) { case TalerErrorCode.ORDERS_ALREADY_CLAIMED: @@ -652,7 +644,8 @@ async function processDownloadProposalImpl( { orderId: proposal.orderId, claimUrl: orderClaimUrl, - }); + }, + ); default: throwUnexpectedRequestError(httpResponse, r.talerErrorResponse); } @@ -723,8 +716,9 @@ async function processDownloadProposalImpl( contractTermsRaw: JSON.stringify(proposalResp.contract_terms), }; if ( - fulfillmentUrl.startsWith("http://") || - fulfillmentUrl.startsWith("https://") + fulfillmentUrl && + (fulfillmentUrl.startsWith("http://") || + fulfillmentUrl.startsWith("https://")) ) { const differentPurchase = await tx.getIndexed( Stores.purchases.fulfillmentUrlIndex, @@ -968,15 +962,9 @@ export async function submitPay( await storePayReplaySuccess(ws, proposalId, sessionId); } - const nextUrl = getNextUrl(purchase.contractData); - ws.cachedNextUrl[purchase.contractData.fulfillmentUrl] = { - nextUrl, - lastSessionId: sessionId, - }; - return { type: ConfirmPayResultType.Done, - nextUrl, + contractTerms: JSON.parse(purchase.contractTermsRaw), }; } @@ -1089,7 +1077,6 @@ export async function preparePayForUri( contractTerms: JSON.parse(purchase.contractTermsRaw), contractTermsHash: purchase.contractData.contractTermsHash, paid: true, - nextUrl: r.nextUrl, amountRaw: Amounts.stringify(purchase.contractData.amount), amountEffective: Amounts.stringify(purchase.payCostInfo.totalCost), }; diff --git a/packages/taler-wallet-core/src/operations/state.ts b/packages/taler-wallet-core/src/operations/state.ts index 582dd92d3..131e9083d 100644 --- a/packages/taler-wallet-core/src/operations/state.ts +++ b/packages/taler-wallet-core/src/operations/state.ts @@ -15,7 +15,7 @@ */ import { HttpRequestLibrary } from "../util/http"; -import { NextUrlResult, BalancesResponse } from "../types/walletTypes"; +import { BalancesResponse } from "../types/walletTypes"; import { CryptoApi, CryptoWorkerFactory } from "../crypto/workers/cryptoApi"; import { AsyncOpMemoMap, AsyncOpMemoSingle } from "../util/asyncMemo"; import { Logger } from "../util/logging"; @@ -32,7 +32,6 @@ export const EXCHANGE_COINS_LOCK = "exchange-coins-lock"; export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock"; export class InternalWalletState { - cachedNextUrl: { [fulfillmentUrl: string]: NextUrlResult } = {}; memoProcessReserve: AsyncOpMemoMap = new AsyncOpMemoMap(); memoMakePlanchet: AsyncOpMemoMap = new AsyncOpMemoMap(); memoGetPending: AsyncOpMemoSingle< diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 8300864b2..7b42b9a5f 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -35,7 +35,7 @@ import { PaymentStatus, WithdrawalType, WithdrawalDetails, - PaymentShortInfo, + OrderShortInfo, } from "../types/transactions"; import { getFundingPaytoUris } from "./reserves"; @@ -234,7 +234,7 @@ export async function getTransactions( if (!proposal) { return; } - const info: PaymentShortInfo = { + const info: OrderShortInfo = { fulfillmentUrl: pr.contractData.fulfillmentUrl, merchant: pr.contractData.merchant, orderId: pr.contractData.orderId, diff --git a/packages/taler-wallet-core/src/types/dbTypes.ts b/packages/taler-wallet-core/src/types/dbTypes.ts index e36e322d1..79100b69f 100644 --- a/packages/taler-wallet-core/src/types/dbTypes.ts +++ b/packages/taler-wallet-core/src/types/dbTypes.ts @@ -31,6 +31,7 @@ import { ExchangeSignKeyJson, MerchantInfo, Product, + InternationalizedString, } from "./talerTypes"; import { Index, Store } from "../util/query"; @@ -1270,8 +1271,10 @@ export interface AllowedExchangeInfo { export interface WalletContractData { products?: Product[]; summaryI18n: { [lang_tag: string]: string } | undefined; - fulfillmentUrl: string; + fulfillmentUrl?: string; contractTermsHash: string; + fulfillmentMessage?: string; + fulfillmentMessageI18n?: InternationalizedString; merchantSig: string; merchantPub: string; merchant: MerchantInfo; diff --git a/packages/taler-wallet-core/src/types/talerTypes.ts b/packages/taler-wallet-core/src/types/talerTypes.ts index f14e2a2ab..14e1b5751 100644 --- a/packages/taler-wallet-core/src/types/talerTypes.ts +++ b/packages/taler-wallet-core/src/types/talerTypes.ts @@ -314,6 +314,10 @@ export interface Product { delivery_location?: string; } +export interface InternationalizedString { + [lang_tag: string]: string; +} + /** * Contract terms from a merchant. */ @@ -338,7 +342,7 @@ export class ContractTerms { */ summary: string; - summary_i18n?: { [lang_tag: string]: string }; + summary_i18n?: InternationalizedString; /** * Nonce used to ensure freshness. @@ -420,7 +424,17 @@ export class ContractTerms { * Fulfillment URL to view the product or * delivery status. */ - fulfillment_url: string; + fulfillment_url?: string; + + /** + * Plain text fulfillment message in the merchant's default language. + */ + fulfillment_message?: string; + + /** + * Internationalized fulfillment messages. + */ + fulfillment_message_i18n?: InternationalizedString; /** * Share of the wire fee that must be settled with one payment. @@ -1032,14 +1046,14 @@ export const codecForTax = (): Codec => .property("tax", codecForString()) .build("Tax"); -export const codecForI18n = (): Codec<{ [lang_tag: string]: string }> => +export const codecForInternationalizedString = (): Codec => codecForMap(codecForString()); export const codecForProduct = (): Codec => buildCodecForObject() .property("product_id", codecOptional(codecForString())) .property("description", codecForString()) - .property("description_i18n", codecOptional(codecForI18n())) + .property("description_i18n", codecOptional(codecForInternationalizedString())) .property("quantity", codecOptional(codecForNumber())) .property("unit", codecOptional(codecForString())) .property("price", codecOptional(codecForString())) @@ -1050,13 +1064,15 @@ export const codecForProduct = (): Codec => export const codecForContractTerms = (): Codec => buildCodecForObject() .property("order_id", codecForString()) - .property("fulfillment_url", codecForString()) + .property("fulfillment_url", codecOptional(codecForString())) + .property("fulfillment_message", codecOptional(codecForString())) + .property("fulfillment_message_i18n", codecOptional(codecForInternationalizedString())) .property("merchant_base_url", codecForString()) .property("h_wire", codecForString()) .property("auto_refund", codecOptional(codecForDuration)) .property("wire_method", codecForString()) .property("summary", codecForString()) - .property("summary_i18n", codecOptional(codecForI18n())) + .property("summary_i18n", codecOptional(codecForInternationalizedString())) .property("nonce", codecForString()) .property("amount", codecForString()) .property("auditors", codecForList(codecForAuditorHandle())) diff --git a/packages/taler-wallet-core/src/types/transactions.ts b/packages/taler-wallet-core/src/types/transactions.ts index 5ee09384f..061ce28f4 100644 --- a/packages/taler-wallet-core/src/types/transactions.ts +++ b/packages/taler-wallet-core/src/types/transactions.ts @@ -25,7 +25,15 @@ * Imports. */ import { Timestamp } from "../util/time"; -import { AmountString, Product } from "./talerTypes"; +import { + AmountString, + Product, + InternationalizedString, + MerchantInfo, + codecForInternationalizedString, + codecForMerchantInfo, + codecForProduct, +} from "./talerTypes"; import { Codec, buildCodecForObject, @@ -202,7 +210,7 @@ export interface TransactionPayment extends TransactionCommon { /** * Additional information about the payment. */ - info: PaymentShortInfo; + info: OrderShortInfo; /** * How far did the wallet get with processing the payment? @@ -220,7 +228,7 @@ export interface TransactionPayment extends TransactionCommon { amountEffective: AmountString; } -export interface PaymentShortInfo { +export interface OrderShortInfo { /** * Order ID, uniquely identifies the order within a merchant instance */ @@ -234,7 +242,7 @@ export interface PaymentShortInfo { /** * More information about the merchant */ - merchant: any; + merchant: MerchantInfo; /** * Summary of the order, given by the merchant @@ -244,7 +252,7 @@ export interface PaymentShortInfo { /** * Map from IETF BCP 47 language tags to localized summaries */ - summary_i18n?: { [lang_tag: string]: string }; + summary_i18n?: InternationalizedString; /** * List of products that are part of the order @@ -254,7 +262,18 @@ export interface PaymentShortInfo { /** * URL of the fulfillment, given by the merchant */ - fulfillmentUrl: string; + fulfillmentUrl?: string; + + /** + * Plain text message that should be shown to the user + * when the payment is complete. + */ + fulfillmentMessage?: string; + + /** + * Translations of fulfillmentMessage. + */ + fulfillmentMessage_i18n?: InternationalizedString; } interface TransactionRefund extends TransactionCommon { @@ -264,7 +283,7 @@ interface TransactionRefund extends TransactionCommon { refundedTransactionId: string; // Additional information about the refunded payment - info: PaymentShortInfo; + info: OrderShortInfo; // Amount that has been refunded by the merchant amountRaw: AmountString; @@ -321,4 +340,20 @@ export const codecForTransactionsRequest = (): Codec => export const codecForTransactionsResponse = (): Codec => buildCodecForObject() .property("transactions", codecForList(codecForAny())) - .build("TransactionsResponse"); \ No newline at end of file + .build("TransactionsResponse"); + +export const codecForOrderShortInfo = (): Codec => + buildCodecForObject() + .property("contractTermsHash", codecForString()) + .property("fulfillmentMessage", codecOptional(codecForString())) + .property( + "fulfillmentMessage_i18n", + codecOptional(codecForInternationalizedString()), + ) + .property("fulfillmentUrl", codecOptional(codecForString())) + .property("merchant", codecForMerchantInfo()) + .property("orderId", codecForString()) + .property("products", codecOptional(codecForList(codecForProduct()))) + .property("summary", codecForString()) + .property("summary_i18n", codecOptional(codecForInternationalizedString())) + .build("OrderShortInfo"); diff --git a/packages/taler-wallet-core/src/types/walletTypes.ts b/packages/taler-wallet-core/src/types/walletTypes.ts index 921c63a1e..2cf3c7fbc 100644 --- a/packages/taler-wallet-core/src/types/walletTypes.ts +++ b/packages/taler-wallet-core/src/types/walletTypes.ts @@ -50,8 +50,8 @@ import { codecForAny, buildCodecForUnion, } from "../util/codec"; -import { AmountString, codecForContractTerms } from "./talerTypes"; -import { TransactionError } from "./transactions"; +import { AmountString, codecForContractTerms, ContractTerms } from "./talerTypes"; +import { TransactionError, OrderShortInfo, codecForOrderShortInfo } from "./transactions"; /** * Response for the create reserve request to the wallet. @@ -209,8 +209,7 @@ export const enum ConfirmPayResultType { */ export interface ConfirmPayResultDone { type: ConfirmPayResultType.Done; - - nextUrl: string; + contractTerms: ContractTerms; } export interface ConfirmPayResultPending { @@ -232,7 +231,7 @@ export const codecForConfirmPayResultPending = (): Codec< export const codecForConfirmPayResultDone = (): Codec => buildCodecForObject() .property("type", codecForConstString(ConfirmPayResultType.Done)) - .property("nextUrl", codecForString()) + .property("contractTerms", codecForContractTerms()) .build("ConfirmPayResultDone"); export const codecForConfirmPayResult = (): Codec => @@ -368,14 +367,6 @@ export interface BenchmarkResult { repetitions: number; } -/** - * Cached next URL for a particular session id. - */ -export interface NextUrlResult { - nextUrl: string; - lastSessionId: string | undefined; -} - export const enum PreparePayResultType { PaymentPossible = "payment-possible", InsufficientBalance = "insufficient-balance", @@ -388,7 +379,7 @@ export const codecForPreparePayResultPaymentPossible = (): Codec< buildCodecForObject() .property("amountEffective", codecForAmountString()) .property("amountRaw", codecForAmountString()) - .property("contractTerms", codecForAny()) + .property("contractTerms", codecForContractTerms()) .property("proposalId", codecForString()) .property( "status", @@ -419,7 +410,6 @@ export const codecForPreparePayResultAlreadyConfirmed = (): Codec< ) .property("amountEffective", codecForAmountString()) .property("amountRaw", codecForAmountString()) - .property("nextUrl", codecForString()) .property("paid", codecForBoolean) .property("contractTerms", codecForAny()) .property("contractTermsHash", codecForString()) @@ -450,7 +440,7 @@ export type PreparePayResult = export interface PreparePayResultPaymentPossible { status: PreparePayResultType.PaymentPossible; proposalId: string; - contractTerms: Record; + contractTerms: ContractTerms; amountRaw: string; amountEffective: string; } @@ -458,19 +448,16 @@ export interface PreparePayResultPaymentPossible { export interface PreparePayResultInsufficientBalance { status: PreparePayResultType.InsufficientBalance; proposalId: string; - contractTerms: Record; + contractTerms: ContractTerms; amountRaw: string; } export interface PreparePayResultAlreadyConfirmed { status: PreparePayResultType.AlreadyConfirmed; - contractTerms: Record; + contractTerms: ContractTerms; paid: boolean; amountRaw: string; amountEffective: string; - // Only specified if paid. - nextUrl?: string; - contractTermsHash: string; } -- cgit v1.2.3