summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-02-11 14:24:29 +0100
committerFlorian Dold <florian@dold.me>2023-02-11 14:24:29 +0100
commit04ab9f37801f6a42b85581cc79667239d3fc79e5 (patch)
tree7f5841f5a872a6374251137b75a17d00a258740e
parenta9073a67971e56dc58e8633d10c5e0c7c3920c8a (diff)
downloadwallet-core-04ab9f37801f6a42b85581cc79667239d3fc79e5.tar.gz
wallet-core-04ab9f37801f6a42b85581cc79667239d3fc79e5.tar.bz2
wallet-core-04ab9f37801f6a42b85581cc79667239d3fc79e5.zip
wallet-core,harness: implement pay templating
-rw-r--r--packages/taler-harness/src/harness/harness.ts31
-rw-r--r--packages/taler-harness/src/integrationtests/test-payment-template.ts95
-rw-r--r--packages/taler-harness/src/integrationtests/testrunner.ts2
-rw-r--r--packages/taler-harness/tsconfig.json2
-rw-r--r--packages/taler-util/src/index.ts1
-rw-r--r--packages/taler-util/src/merchant-api-types.ts (renamed from packages/taler-harness/src/harness/merchantApiTypes.ts)56
-rw-r--r--packages/taler-util/src/taler-types.ts15
-rw-r--r--packages/taler-util/src/taleruri.test.ts36
-rw-r--r--packages/taler-util/src/taleruri.ts65
-rw-r--r--packages/taler-util/src/wallet-types.ts11
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts14
-rw-r--r--packages/taler-wallet-core/src/wallet.ts56
12 files changed, 346 insertions, 38 deletions
diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts
index 4e5d8238c..3659ea538 100644
--- a/packages/taler-harness/src/harness/harness.ts
+++ b/packages/taler-harness/src/harness/harness.ts
@@ -38,6 +38,7 @@ import {
hash,
j2s,
Logger,
+ MerchantTemplateAddDetails,
parsePaytoUri,
stringToBytes,
TalerProtocolDuration,
@@ -66,15 +67,15 @@ import { CoinConfig } from "./denomStructures.js";
import { LibeufinNexusApi, LibeufinSandboxApi } from "./libeufin-apis.js";
import {
codecForMerchantOrderPrivateStatusResponse,
- codecForPostOrderResponse,
+ codecForMerchantPostOrderResponse,
MerchantInstancesResponse,
MerchantOrderPrivateStatusResponse,
- PostOrderRequest,
- PostOrderResponse,
+ MerchantPostOrderRequest,
+ MerchantPostOrderResponse,
TipCreateConfirmation,
TipCreateRequest,
TippingReserveStatus,
-} from "./merchantApiTypes.js";
+} from "@gnu-taler/taler-util";
import {
createRemoteWallet,
getClientFromRemoteWallet,
@@ -1473,15 +1474,31 @@ export namespace MerchantPrivateApi {
export async function createOrder(
merchantService: MerchantServiceInterface,
instanceName: string,
- req: PostOrderRequest,
+ req: MerchantPostOrderRequest,
withAuthorization: WithAuthorization = {},
- ): Promise<PostOrderResponse> {
+ ): Promise<MerchantPostOrderResponse> {
const baseUrl = merchantService.makeInstanceBaseUrl(instanceName);
let url = new URL("private/orders", baseUrl);
const resp = await axios.post(url.href, req, {
headers: withAuthorization as Record<string, string>,
});
- return codecForPostOrderResponse().decode(resp.data);
+ return codecForMerchantPostOrderResponse().decode(resp.data);
+ }
+
+ export async function createTemplate(
+ merchantService: MerchantServiceInterface,
+ instanceName: string,
+ req: MerchantTemplateAddDetails,
+ withAuthorization: WithAuthorization = {},
+ ) {
+ const baseUrl = merchantService.makeInstanceBaseUrl(instanceName);
+ let url = new URL("private/templates", baseUrl);
+ const resp = await axios.post(url.href, req, {
+ headers: withAuthorization as Record<string, string>,
+ });
+ if (resp.status !== 204) {
+ throw Error("failed to create template");
+ }
}
export async function queryPrivateOrderStatus(
diff --git a/packages/taler-harness/src/integrationtests/test-payment-template.ts b/packages/taler-harness/src/integrationtests/test-payment-template.ts
new file mode 100644
index 000000000..41e43e28a
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-payment-template.ts
@@ -0,0 +1,95 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 Taler Systems S.A.
+
+ GNU 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.
+
+ GNU 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
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import { ConfirmPayResultType, Duration, PreparePayResultType, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";
+import {
+ createSimpleTestkudosEnvironment,
+ withdrawViaBank,
+ makeTestPayment,
+} from "../harness/helpers.js";
+
+/**
+ * Test for taler://payment-template/ URIs
+ */
+export async function runPaymentTemplateTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { wallet, bank, exchange, merchant } =
+ await createSimpleTestkudosEnvironment(t);
+
+ await MerchantPrivateApi.createTemplate(merchant, "default", {
+ template_id: "template1",
+ template_description: "my test template",
+ template_contract: {
+ minimum_age: 0,
+ pay_duration: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({
+ minutes: 2,
+ }),
+ ),
+ summary: "hello, I'm a summary",
+ },
+ });
+
+ // Withdraw digital cash into the wallet.
+
+ await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" });
+
+ // Request a template payment
+
+ const preparePayResult = await wallet.client.call(
+ WalletApiOperation.PreparePayForTemplate,
+ {
+ talerPayTemplateUri: `taler+http://pay-template/localhost:${merchant.port}/template1?amount=TESTKUDOS:1`,
+ templateParams: {},
+ },
+ );
+
+ console.log(preparePayResult);
+
+ t.assertTrue(
+ preparePayResult.status === PreparePayResultType.PaymentPossible,
+ );
+
+ // Pay for it
+
+ const r2 = await wallet.client.call(WalletApiOperation.ConfirmPay, {
+ proposalId: preparePayResult.proposalId,
+ });
+
+ t.assertTrue(r2.type === ConfirmPayResultType.Done);
+
+ // Check if payment was successful.
+
+ const orderStatus = await MerchantPrivateApi.queryPrivateOrderStatus(
+ merchant,
+ {
+ orderId: preparePayResult.contractTerms.order_id,
+ instance: "default",
+ },
+ );
+
+ t.assertTrue(orderStatus.order_status === "paid");
+
+ await wallet.runUntilDone();
+}
+
+runPaymentTemplateTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
index 025f2e514..a20300e02 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -100,6 +100,7 @@ import { runKycTest } from "./test-kyc.js";
import { runPaymentAbortTest } from "./test-payment-abort.js";
import { runWithdrawalFeesTest } from "./test-withdrawal-fees.js";
import { runWalletBalanceTest } from "./test-wallet-balance.js";
+import { runPaymentTemplateTest } from "./test-payment-template.js";
/**
* Test runner.
@@ -163,6 +164,7 @@ const allTests: TestMainFunction[] = [
runPaymentIdempotencyTest,
runPaymentMultipleTest,
runPaymentTest,
+ runPaymentTemplateTest,
runPaymentAbortTest,
runPaymentTransientTest,
runPaymentZeroTest,
diff --git a/packages/taler-harness/tsconfig.json b/packages/taler-harness/tsconfig.json
index 447d3f946..d022b16e8 100644
--- a/packages/taler-harness/tsconfig.json
+++ b/packages/taler-harness/tsconfig.json
@@ -21,7 +21,7 @@
"baseUrl": "./src",
"typeRoots": ["./node_modules/@types"]
},
- "include": ["src/**/*"],
+ "include": ["src/**/*", "../taler-util/src/merchant-api-types.ts"],
"references": [
{
"path": "../taler-wallet-core/"
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index 2f674d097..661b0332f 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -35,3 +35,4 @@ export { RequestThrottler } from "./RequestThrottler.js";
export * from "./CancellationToken.js";
export * from "./contract-terms.js";
export * from "./base64.js";
+export * from "./merchant-api-types.js";
diff --git a/packages/taler-harness/src/harness/merchantApiTypes.ts b/packages/taler-util/src/merchant-api-types.ts
index 1985e9150..61002191a 100644
--- a/packages/taler-harness/src/harness/merchantApiTypes.ts
+++ b/packages/taler-util/src/merchant-api-types.ts
@@ -26,7 +26,6 @@
*/
import {
MerchantContractTerms,
- Duration,
Codec,
buildCodecForObject,
codecForString,
@@ -47,7 +46,7 @@ import {
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util";
-export interface PostOrderRequest {
+export interface MerchantPostOrderRequest {
// The order must at least contain the minimal
// order detail, but can override all
order: Partial<MerchantContractTerms>;
@@ -71,18 +70,18 @@ export interface PostOrderRequest {
export type ClaimToken = string;
-export interface PostOrderResponse {
+export interface MerchantPostOrderResponse {
order_id: string;
token?: ClaimToken;
}
-export const codecForPostOrderResponse = (): Codec<PostOrderResponse> =>
- buildCodecForObject<PostOrderResponse>()
+export const codecForMerchantPostOrderResponse = (): Codec<MerchantPostOrderResponse> =>
+ buildCodecForObject<MerchantPostOrderResponse>()
.property("order_id", codecForString())
.property("token", codecOptional(codecForString()))
.build("PostOrderResponse");
-export const codecForRefundDetails = (): Codec<RefundDetails> =>
+export const codecForMerchantRefundDetails = (): Codec<RefundDetails> =>
buildCodecForObject<RefundDetails>()
.property("reason", codecForString())
.property("pending", codecForBoolean())
@@ -90,9 +89,9 @@ export const codecForRefundDetails = (): Codec<RefundDetails> =>
.property("timestamp", codecForTimestamp)
.build("PostOrderResponse");
-export const codecForCheckPaymentPaidResponse =
- (): Codec<CheckPaymentPaidResponse> =>
- buildCodecForObject<CheckPaymentPaidResponse>()
+export const codecForMerchantCheckPaymentPaidResponse =
+ (): Codec<MerchantCheckPaymentPaidResponse> =>
+ buildCodecForObject<MerchantCheckPaymentPaidResponse>()
.property("order_status_url", codecForString())
.property("order_status", codecForConstString("paid"))
.property("refunded", codecForBoolean())
@@ -128,13 +127,13 @@ export const codecForMerchantOrderPrivateStatusResponse =
(): Codec<MerchantOrderPrivateStatusResponse> =>
buildCodecForUnion<MerchantOrderPrivateStatusResponse>()
.discriminateOn("order_status")
- .alternative("paid", codecForCheckPaymentPaidResponse())
+ .alternative("paid", codecForMerchantCheckPaymentPaidResponse())
.alternative("unpaid", codecForCheckPaymentUnpaidResponse())
.alternative("claimed", codecForCheckPaymentClaimedResponse())
.build("MerchantOrderPrivateStatusResponse");
export type MerchantOrderPrivateStatusResponse =
- | CheckPaymentPaidResponse
+ | MerchantCheckPaymentPaidResponse
| CheckPaymentUnpaidResponse
| CheckPaymentClaimedResponse;
@@ -145,7 +144,7 @@ export interface CheckPaymentClaimedResponse {
contract_terms: MerchantContractTerms;
}
-export interface CheckPaymentPaidResponse {
+export interface MerchantCheckPaymentPaidResponse {
// did the customer pay for this contract
order_status: "paid";
@@ -334,3 +333,36 @@ export interface MerchantInstanceDetail {
// front-ends do not have to support wallets selecting payment targets.
payment_targets: string[];
}
+
+export interface MerchantTemplateContractDetails {
+ // Human-readable summary for the template.
+ summary?: string;
+
+ // The price is imposed by the merchant and cannot be changed by the customer.
+ // This parameter is optional.
+ amount?: AmountString;
+
+ // Minimum age buyer must have (in years). Default is 0.
+ minimum_age: number;
+
+ // The time the customer need to pay before his order will be deleted.
+ // It is deleted if the customer did not pay and if the duration is over.
+ pay_duration: TalerProtocolDuration;
+}
+
+export interface MerchantTemplateAddDetails {
+
+ // Template ID to use.
+ template_id: string;
+
+ // Human-readable description for the template.
+ template_description: string;
+
+ // A base64-encoded image selected by the merchant.
+ // This parameter is optional.
+ // We are not sure about it.
+ image?: string;
+
+ // Additional information in a separate template.
+ template_contract: MerchantTemplateContractDetails;
+} \ No newline at end of file
diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts
index bb15f0494..6e7df2c04 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -1481,10 +1481,11 @@ export const codecForWithdrawResponse = (): Codec<ExchangeWithdrawResponse> =>
.property("ev_sig", codecForBlindedDenominationSignature())
.build("WithdrawResponse");
-export const codecForWithdrawBatchResponse = (): Codec<ExchangeWithdrawBatchResponse> =>
- buildCodecForObject<ExchangeWithdrawBatchResponse>()
- .property("ev_sigs", codecForList(codecForWithdrawResponse()))
- .build("WithdrawBatchResponse");
+export const codecForWithdrawBatchResponse =
+ (): Codec<ExchangeWithdrawBatchResponse> =>
+ buildCodecForObject<ExchangeWithdrawBatchResponse>()
+ .property("ev_sigs", codecForList(codecForWithdrawResponse()))
+ .build("WithdrawBatchResponse");
export const codecForMerchantPayResponse = (): Codec<MerchantPayResponse> =>
buildCodecForObject<MerchantPayResponse>()
@@ -1757,7 +1758,6 @@ export interface ExchangeBatchWithdrawRequest {
planchets: ExchangeWithdrawRequest[];
}
-
export interface ExchangeRefreshRevealRequest {
new_denoms_h: HashCodeString[];
coin_evs: CoinEnvelope[];
@@ -2113,3 +2113,8 @@ export const codecForWalletKycUuid = (): Codec<WalletKycUuid> =>
.property("requirement_row", codecForNumber())
.property("h_payto", codecForString())
.build("WalletKycUuid");
+
+export interface MerchantUsingTemplateDetails {
+ summary?: string;
+ amount?: AmountString;
+}
diff --git a/packages/taler-util/src/taleruri.test.ts b/packages/taler-util/src/taleruri.test.ts
index 3ee243fb3..a6c4d89fc 100644
--- a/packages/taler-util/src/taleruri.test.ts
+++ b/packages/taler-util/src/taleruri.test.ts
@@ -22,6 +22,8 @@ import {
parseTipUri,
parsePayPushUri,
constructPayPushUri,
+ parsePayTemplateUri,
+ constructPayUri,
} from "./taleruri.js";
test("taler pay url parsing: wrong scheme", (t) => {
@@ -225,3 +227,37 @@ test("taler peer to peer push URI (construction)", (t) => {
});
t.deepEqual(url, "taler://pay-push/foo.example.com/bla/123");
});
+
+test("taler pay URI (construction)", (t) => {
+ const url1 = constructPayUri("http://localhost:123/", "foo", "");
+ t.deepEqual(url1, "taler+http://pay/localhost:123/foo/");
+
+ const url2 = constructPayUri("http://localhost:123/", "foo", "bla");
+ t.deepEqual(url2, "taler+http://pay/localhost:123/foo/bla");
+});
+
+test("taler pay template URI (parsing)", (t) => {
+ const url1 =
+ "taler://pay-template/merchant.example.com/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY?amount=KUDOS:5";
+ const r1 = parsePayTemplateUri(url1);
+ if (!r1) {
+ t.fail();
+ return;
+ }
+ t.deepEqual(r1.merchantBaseUrl, "https://merchant.example.com/");
+ t.deepEqual(r1.templateId, "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY");
+ t.deepEqual(r1.templateParams.amount, "KUDOS:5");
+});
+
+test("taler pay template URI (parsing, http with port)", (t) => {
+ const url1 =
+ "taler+http://pay-template/merchant.example.com:1234/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY?amount=KUDOS:5";
+ const r1 = parsePayTemplateUri(url1);
+ if (!r1) {
+ t.fail();
+ return;
+ }
+ t.deepEqual(r1.merchantBaseUrl, "http://merchant.example.com:1234/");
+ t.deepEqual(r1.templateId, "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY");
+ t.deepEqual(r1.templateParams.amount, "KUDOS:5");
+});
diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts
index 4e47acbce..2aa9cb030 100644
--- a/packages/taler-util/src/taleruri.ts
+++ b/packages/taler-util/src/taleruri.ts
@@ -16,7 +16,6 @@
import { BackupRecovery } from "./backup-types.js";
import { canonicalizeBaseUrl } from "./helpers.js";
-import { initNodePrng } from "./prng-node.js";
import { URLSearchParams, URL } from "./url.js";
export interface PayUriResult {
@@ -27,6 +26,12 @@ export interface PayUriResult {
noncePriv: string | undefined;
}
+export interface PayTemplateUriResult {
+ merchantBaseUrl: string;
+ templateId: string;
+ templateParams: Record<string, string>;
+}
+
export interface WithdrawUriResult {
bankIntegrationApiBaseUrl: string;
withdrawalOperationId: string;
@@ -91,6 +96,7 @@ export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
export enum TalerUriType {
TalerPay = "taler-pay",
+ TalerTemplate = "taler-template",
TalerWithdraw = "taler-withdraw",
TalerTip = "taler-tip",
TalerRefund = "taler-refund",
@@ -103,6 +109,7 @@ export enum TalerUriType {
const talerActionPayPull = "pay-pull";
const talerActionPayPush = "pay-push";
+const talerActionPayTemplate = "pay-template";
/**
* Classify a taler:// URI.
@@ -121,6 +128,12 @@ export function classifyTalerUri(s: string): TalerUriType {
if (sl.startsWith("taler+http://pay/")) {
return TalerUriType.TalerPay;
}
+ if (sl.startsWith("taler://pay-template/")) {
+ return TalerUriType.TalerPay;
+ }
+ if (sl.startsWith("taler+http://pay-template/")) {
+ return TalerUriType.TalerPay;
+ }
if (sl.startsWith("taler://tip/")) {
return TalerUriType.TalerTip;
}
@@ -216,6 +229,38 @@ export function parsePayUri(s: string): PayUriResult | undefined {
};
}
+export function parsePayTemplateUri(
+ s: string,
+): PayTemplateUriResult | undefined {
+ const pi = parseProtoInfo(s, talerActionPayTemplate);
+ if (!pi) {
+ return undefined;
+ }
+ const c = pi?.rest.split("?");
+ const q = new URLSearchParams(c[1] ?? "");
+ const parts = c[0].split("/");
+ if (parts.length < 2) {
+ return undefined;
+ }
+ const host = parts[0].toLowerCase();
+ const templateId = parts[parts.length - 1];
+ const pathSegments = parts.slice(1, parts.length - 1);
+ const p = [host, ...pathSegments].join("/");
+ const merchantBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
+
+ const params: Record<string, string> = {};
+
+ q.forEach((v, k) => {
+ params[k] = v;
+ });
+
+ return {
+ merchantBaseUrl,
+ templateId,
+ templateParams: params,
+ };
+}
+
export function constructPayUri(
merchantBaseUrl: string,
orderId: string,
@@ -227,9 +272,21 @@ export function constructPayUri(
const url = new URL(base);
const isHttp = base.startsWith("http://");
let result = isHttp ? `taler+http://pay/` : `taler://pay/`;
- result += `${url.hostname}${url.pathname}${orderId}/${sessionId}?`;
- if (claimToken) result += `c=${claimToken}`;
- if (noncePriv) result += `n=${noncePriv}`;
+ result += url.hostname;
+ if (url.port != "") {
+ result += `:${url.port}`;
+ }
+ result += `${url.pathname}${orderId}/${sessionId}`;
+ let queryPart = "";
+ if (claimToken) {
+ queryPart += `c=${claimToken}`;
+ }
+ if (noncePriv) {
+ queryPart += `n=${noncePriv}`;
+ }
+ if (queryPart) {
+ result += "?" + queryPart;
+ }
return result;
}
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index d57a221f3..0f29b964b 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -1418,6 +1418,17 @@ export const codecForPreparePayRequest = (): Codec<PreparePayRequest> =>
.property("talerPayUri", codecForString())
.build("PreparePay");
+export interface PreparePayTemplateRequest {
+ talerPayTemplateUri: string;
+ templateParams: Record<string, string>;
+}
+
+export const codecForPreparePayTemplateRequest = (): Codec<PreparePayTemplateRequest> =>
+ buildCodecForObject<PreparePayTemplateRequest>()
+ .property("talerPayTemplateUri", codecForString())
+ .property("templateParams", codecForAny())
+ .build("PreparePayTemplate");
+
export interface ConfirmPayRequest {
proposalId: string;
sessionId?: string;
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
index 61d1417f9..da57253a0 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -78,6 +78,7 @@ import {
PrepareDepositResponse,
PreparePayRequest,
PreparePayResult,
+ PreparePayTemplateRequest,
PreparePeerPullPaymentRequest,
PreparePeerPullPaymentResponse,
PreparePeerPushPaymentRequest,
@@ -126,6 +127,7 @@ export enum WalletApiOperation {
WithdrawTestkudos = "withdrawTestkudos",
WithdrawTestBalance = "withdrawTestBalance",
PreparePayForUri = "preparePayForUri",
+ PreparePayForTemplate = "preparePayForTemplate",
GetContractTermsDetails = "getContractTermsDetails",
RunIntegrationTest = "runIntegrationTest",
TestCrypto = "testCrypto",
@@ -313,7 +315,7 @@ export type AcceptManualWithdrawalOp = {
// group: Merchant Payments
/**
- * Prepare to make a payment
+ * Prepare to make a payment based on a taler://pay/ URI.
*/
export type PreparePayForUriOp = {
op: WalletApiOperation.PreparePayForUri;
@@ -321,6 +323,15 @@ export type PreparePayForUriOp = {
response: PreparePayResult;
};
+/**
+ * Prepare to make a payment based on a taler://pay-template/ URI.
+ */
+export type PreparePayForTemplateOp = {
+ op: WalletApiOperation.PreparePayForTemplate;
+ request: PreparePayTemplateRequest;
+ response: PreparePayResult;
+};
+
export type GetContractTermsDetailsOp = {
op: WalletApiOperation.GetContractTermsDetails;
request: GetContractTermsDetailsRequest;
@@ -835,6 +846,7 @@ export type WalletOperations = {
[WalletApiOperation.GetVersion]: GetVersionOp;
[WalletApiOperation.WithdrawFakebank]: WithdrawFakebankOp;
[WalletApiOperation.PreparePayForUri]: PreparePayForUriOp;
+ [WalletApiOperation.PreparePayForTemplate]: PreparePayForTemplateOp;
[WalletApiOperation.GetContractTermsDetails]: GetContractTermsDetailsOp;
[WalletApiOperation.WithdrawTestkudos]: WithdrawTestkudosOp;
[WalletApiOperation.ConfirmPay]: ConfirmPayOp;
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index f1ed592bd..57ae85c1c 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -56,8 +56,10 @@ import {
codecForInitiatePeerPushPaymentRequest,
codecForIntegrationTestArgs,
codecForListKnownBankAccounts,
+ codecForMerchantPostOrderResponse,
codecForPrepareDepositRequest,
codecForPreparePayRequest,
+ codecForPreparePayTemplateRequest,
codecForPreparePeerPullPaymentRequest,
codecForPreparePeerPushPaymentRequest,
codecForPrepareRefundRequest,
@@ -77,6 +79,7 @@ import {
CoinDumpJson,
CoinRefreshRequest,
CoinStatus,
+ constructPayUri,
CoreApiResponse,
DenominationInfo,
DenomOperationMap,
@@ -88,7 +91,6 @@ import {
ExchangesListResponse,
ExchangeTosStatusDetails,
FeeDescription,
- GetBalanceDetailRequest,
GetExchangeTosResult,
InitResponse,
j2s,
@@ -96,7 +98,9 @@ import {
KnownBankAccountsInfo,
Logger,
ManualWithdrawalDetails,
+ MerchantUsingTemplateDetails,
NotificationType,
+ parsePayTemplateUri,
parsePaytoUri,
RefreshReason,
TalerErrorCode,
@@ -156,11 +160,7 @@ import {
runBackupCycle,
} from "./operations/backup/index.js";
import { setWalletDeviceId } from "./operations/backup/state.js";
-import {
- getBalanceDetail,
- getBalances,
- getMerchantPaymentBalanceDetails,
-} from "./operations/balance.js";
+import { getBalanceDetail, getBalances } from "./operations/balance.js";
import {
getExchangeTosStatus,
makeExchangeListItem,
@@ -186,7 +186,6 @@ import {
} from "./operations/exchanges.js";
import { getMerchantInfo } from "./operations/merchants.js";
import {
- abortPay as abortPay,
applyRefund,
applyRefundFromPurchaseId,
confirmPay,
@@ -1171,11 +1170,50 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
await runPending(ws, true);
return {};
}
- // FIXME: Deprecate one of the aliases!
case WalletApiOperation.PreparePayForUri: {
const req = codecForPreparePayRequest().decode(payload);
return await preparePayForUri(ws, req.talerPayUri);
}
+ case WalletApiOperation.PreparePayForTemplate: {
+ const req = codecForPreparePayTemplateRequest().decode(payload);
+ const url = parsePayTemplateUri(req.talerPayTemplateUri);
+ const templateDetails: MerchantUsingTemplateDetails = {};
+ if (!url) {
+ throw Error("invalid taler-template URI");
+ }
+ if (
+ url.templateParams.amount &&
+ typeof url.templateParams.amount === "string"
+ ) {
+ templateDetails.amount =
+ req.templateParams.amount ?? url.templateParams.amount;
+ }
+ if (
+ url.templateParams.summary &&
+ typeof url.templateParams.summary === "string"
+ ) {
+ templateDetails.summary =
+ req.templateParams.summary ?? url.templateParams.summary;
+ }
+ const reqUrl = new URL(
+ `templates/${url.templateId}`,
+ url.merchantBaseUrl,
+ );
+ const httpReq = await ws.http.postJson(reqUrl.href, templateDetails);
+ const resp = await readSuccessResponseJsonOrThrow(
+ httpReq,
+ codecForMerchantPostOrderResponse(),
+ );
+
+ const payUri = constructPayUri(
+ url.merchantBaseUrl,
+ resp.order_id,
+ "",
+ resp.token,
+ );
+
+ return await preparePayForUri(ws, payUri);
+ }
case WalletApiOperation.ConfirmPay: {
const req = codecForConfirmPayRequest().decode(payload);
return await confirmPay(ws, req.proposalId, req.sessionId);
@@ -1434,6 +1472,8 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
case WalletApiOperation.GetVersion: {
return getVersion(ws);
}
+ //default:
+ // assertUnreachable(operation);
}
throw TalerError.fromDetail(
TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN,