wallet-core,harness: implement pay templating
This commit is contained in:
parent
a9073a6797
commit
04ab9f3780
@ -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(
|
||||
|
@ -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"];
|
@ -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,
|
||||
|
@ -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/"
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user