diff options
author | Sebastian <sebasjm@gmail.com> | 2023-11-05 00:53:53 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-11-05 00:53:53 -0300 |
commit | 31cf3187e447e2c4ec8a473362c5bacc07a874f1 (patch) | |
tree | 74e528b91eb313cf4d991410afda6ce905a218d6 /packages | |
parent | 78240f6c0f600f905f29e6dceb75286461297f24 (diff) |
exchange and merchant api
Diffstat (limited to 'packages')
-rw-r--r-- | packages/taler-harness/src/http-client/bank-core.ts | 244 | ||||
-rw-r--r-- | packages/taler-harness/src/index.ts | 79 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-integration.ts | 8 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-revenue.ts | 9 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-wire.ts | 7 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/exchange.ts | 37 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/merchant.ts | 37 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/types.ts | 1983 | ||||
-rw-r--r-- | packages/taler-util/src/operation.ts | 2 |
9 files changed, 2304 insertions, 102 deletions
diff --git a/packages/taler-harness/src/http-client/bank-core.ts b/packages/taler-harness/src/http-client/bank-core.ts index ccefd2bfe..9919fc0a7 100644 --- a/packages/taler-harness/src/http-client/bank-core.ts +++ b/packages/taler-harness/src/http-client/bank-core.ts @@ -1,8 +1,8 @@ -import { AccessToken, Amounts, TalerCoreBankHttpClient, TalerCorebankApi, TestForApi, buildPayto, encodeCrock, failOrThrow, getRandomBytes, parsePaytoUri, stringifyPaytoUri, succeedOrThrow } from "@gnu-taler/taler-util" +import { AccessToken, AmountJson, Amounts, TalerCoreBankHttpClient, TalerCorebankApi, TalerRevenueHttpClient, TalerWireGatewayApi, TalerWireGatewayHttpClient, TestForApi, buildPayto, encodeCrock, failOrThrow, getRandomBytes, parsePaytoUri, stringifyPaytoUri, succeedOrThrow } from "@gnu-taler/taler-util" -export function createTestForBankCore(adminToken: AccessToken): TestForApi<TalerCoreBankHttpClient> { +export function createTestForBankCore(api: TalerCoreBankHttpClient, adminToken: AccessToken): TestForApi<TalerCoreBankHttpClient> { return { test_abortCashoutById: { success: undefined, @@ -46,17 +46,17 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler success: undefined, }, test_abortWithdrawalById: { - "invalid-id": async (api) => { + "invalid-id": async () => { await failOrThrow("invalid-id", () => api.abortWithdrawalById("invalid") ) }, - "not-found": async (api) => { + "not-found": async () => { await failOrThrow("not-found", () => api.abortWithdrawalById("11111111-1111-1111-1111-111111111111") ) }, - "previously-confirmed": async (api) => { + "previously-confirmed": async () => { const { username: exchangeUser, token: exchangeToken } = await createRandomTestUser(api, adminToken, { is_taler_exchange: true }) const { username, token } = await createRandomTestUser(api, adminToken) @@ -86,7 +86,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler api.abortWithdrawalById(withdrawal_id) ) }, - success: async (api) => { + success: async () => { const { username, token } = await createRandomTestUser(api, adminToken) const userInfo = await succeedOrThrow(() => @@ -105,15 +105,15 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, }, test_confirmWithdrawalById: { - "insufficient-funds": async (api) => { + "insufficient-funds": async () => { }, - "invalid-id": async (api) => { + "invalid-id": async () => { await failOrThrow("invalid-id", () => api.confirmWithdrawalById("invalid") ) }, - "no-exchange-or-reserve-selected": async (api) => { + "no-exchange-or-reserve-selected": async () => { const { username, token } = await createRandomTestUser(api, adminToken) const userInfo = await succeedOrThrow(() => @@ -130,12 +130,12 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler api.confirmWithdrawalById(withdrawal_id) ) }, - "not-found": async (api) => { + "not-found": async () => { await failOrThrow("not-found", () => api.confirmWithdrawalById("11111111-1111-1111-1111-111111111111") ) }, - "previously-aborted": async (api) => { + "previously-aborted": async () => { const { username, token } = await createRandomTestUser(api, adminToken) const userInfo = await succeedOrThrow(() => api.getAccount({ username, token }) @@ -153,7 +153,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler api.confirmWithdrawalById(withdrawal_id) ) }, - success: async (api) => { + success: async () => { const { username: exchangeUser, token: exchangeToken } = await createRandomTestUser(api, adminToken, { is_taler_exchange: true }) const { username, token } = await createRandomTestUser(api, adminToken) @@ -185,7 +185,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, test_createAccount: { "insufficient-funds": undefined, - "payto-already-exists": async (api) => { + "payto-already-exists": async () => { const { username, token } = await createRandomTestUser(api, adminToken) const userInfo = await succeedOrThrow(() => @@ -203,7 +203,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler ); }, - "username-reserved": async (api) => { + "username-reserved": async () => { await failOrThrow("username-reserved", () => api.createAccount(adminToken, { name: "admin", @@ -211,7 +211,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }) ) }, - "username-already-exists": async (api) => { + "username-already-exists": async () => { const username = "harness-" + encodeCrock(getRandomBytes(10)).toLowerCase(); await succeedOrThrow(() => api.createAccount(adminToken, { @@ -227,7 +227,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }) ); }, - "invalid-phone-or-email": async (api) => { + "invalid-phone-or-email": async () => { const username = "harness-" + encodeCrock(getRandomBytes(10)).toLowerCase(); await failOrThrow("invalid-input", () => api.createAccount(adminToken, { @@ -240,7 +240,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }) ) }, - success: async (api) => { + success: async () => { const username = "harness-" + encodeCrock(getRandomBytes(10)).toLowerCase(); await succeedOrThrow(() => @@ -250,7 +250,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }) ) }, - unauthorized: async (api) => { + unauthorized: async () => { const username = "harness-" + encodeCrock(getRandomBytes(10)).toLowerCase(); await succeedOrThrow(() => @@ -278,7 +278,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, test_createTransaction: { - "creditor-not-found": async (api) => { + "creditor-not-found": async () => { const { username, token } = await createRandomTestUser(api, adminToken) const userInfo = await succeedOrThrow(() => @@ -294,7 +294,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }) ) }, - "creditor-same": async (api) => { + "creditor-same": async () => { const { username, token } = await createRandomTestUser(api, adminToken) const userInfo = await succeedOrThrow(() => @@ -311,7 +311,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler ) }, - "insufficient-funds": async (api) => { + "insufficient-funds": async () => { const { username, token } = await createRandomTestUser(api, adminToken) const { username: otherUser, token: otherToken } = await createRandomTestUser(api, adminToken) @@ -331,7 +331,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }) ) }, - "not-found": async (api) => { + "not-found": async () => { const { username, token } = await createRandomTestUser(api, adminToken) const { username: otherUser, token: otherToken } = await createRandomTestUser(api, adminToken) @@ -351,7 +351,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }) ) }, - "invalid-input": async (api) => { + "invalid-input": async () => { const { username, token } = await createRandomTestUser(api, adminToken) const { username: otherUser, token: otherToken } = await createRandomTestUser(api, adminToken) @@ -380,7 +380,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler ) }, - success: async (api) => { + success: async () => { const { username, token } = await createRandomTestUser(api, adminToken) const { username: otherUser, token: otherToken } = await createRandomTestUser(api, adminToken) @@ -401,7 +401,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler ) }, - unauthorized: async (api) => { + unauthorized: async () => { const { username, token } = await createRandomTestUser(api, adminToken) const { username: otherUser, token: otherToken } = await createRandomTestUser(api, adminToken) @@ -423,7 +423,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, }, test_createWithdrawal: { - "account-not-found": async (api) => { + "account-not-found": async () => { const userInfo = await succeedOrThrow(() => api.getAccount({ username: "admin", token: adminToken }) ) @@ -434,7 +434,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler ) }, - "insufficient-funds": async (api) => { + "insufficient-funds": async () => { const { username, token } = await createRandomTestUser(api, adminToken) const userInfo = await succeedOrThrow(() => api.getAccount({ username, token }) @@ -448,7 +448,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }) ) }, - success: async (api) => { + success: async () => { const { username, token } = await createRandomTestUser(api, adminToken) const userInfo = await succeedOrThrow(() => api.getAccount({ username, token }) @@ -460,7 +460,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler ) }, - unauthorized: async (api) => { + unauthorized: async () => { const { username, token } = await createRandomTestUser(api, adminToken) const userInfo = await succeedOrThrow(() => api.getAccount({ username, token }) @@ -474,7 +474,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, }, test_deleteAccount: { - "balance-not-zero": async (api) => { + "balance-not-zero": async () => { const { username, token } = await createRandomTestUser(api, adminToken) await failOrThrow("balance-not-zero", () => @@ -482,12 +482,12 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler ) }, - "not-found": async (api) => { + "not-found": async () => { await failOrThrow("not-found", () => api.deleteAccount({ username: "not-found", token: adminToken }) ) }, - "username-reserved": async (api) => { + "username-reserved": async () => { await failOrThrow("username-reserved", () => api.deleteAccount({ username: "admin", token: adminToken }) ) @@ -495,7 +495,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler api.deleteAccount({ username: "bank", token: adminToken }) ) }, - success: async (api) => { + success: async () => { const { username, token } = await createRandomTestUser(api, adminToken) const userInfo = await succeedOrThrow(() => @@ -518,7 +518,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler ) }, - unauthorized: async (api) => { + unauthorized: async () => { const username = "harness-" + encodeCrock(getRandomBytes(10)).toLowerCase(); await succeedOrThrow(() => @@ -536,19 +536,19 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, }, test_getAccount: { - "not-found": async (api) => { + "not-found": async () => { await failOrThrow("not-found", () => api.getAccount({ username: "not-found", token: adminToken }) ) }, - success: async (api) => { + success: async () => { const { username, token } = await createRandomTestUser(api, adminToken) await succeedOrThrow(() => api.getAccount({ username, token }) ) }, - unauthorized: async (api) => { + unauthorized: async () => { const { username, token } = await createRandomTestUser(api, adminToken) await failOrThrow("unauthorized", () => api.getAccount({ username, token: "wrongtoken" as AccessToken }) @@ -556,7 +556,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, }, test_getAccounts: { - success: async (api) => { + success: async () => { await succeedOrThrow(() => api.getAccounts(adminToken) ) @@ -573,14 +573,14 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }) ) }, - unauthorized: async (api) => { + unauthorized: async () => { await failOrThrow("unauthorized", () => api.getAccounts("ASDASD" as AccessToken) ) }, }, test_getConfig: { - success: async (api) => { + success: async () => { const config = await succeedOrThrow(() => api.getConfig()) if (!api.isCompatible(config.version)) { @@ -590,13 +590,13 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, }, test_getMonitor: { - "unauthorized": async (api) => { + "unauthorized": async () => { await failOrThrow("unauthorized", () => ( api.getMonitor("wrongtoken" as AccessToken) )) }, - "invalid-input": async (api) => { + "invalid-input": async () => { await failOrThrow("invalid-input", () => ( api.getMonitor(adminToken, { @@ -607,7 +607,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, "monitor-not-supported": undefined, - success: async (api) => { + success: async () => { await succeedOrThrow(() => ( api.getMonitor(adminToken) @@ -623,7 +623,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, }, test_getPublicAccounts: { - success: async (api) => { + success: async () => { await succeedOrThrow(() => ( api.getPublicAccounts() )) @@ -646,13 +646,13 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, }, test_getTransactionById: { - "not-found": async (api) => { + "not-found": async () => { const { username, token } = await createRandomTestUser(api, adminToken) await failOrThrow("not-found", () => api.getTransactionById({ username, token }, 123123123) ) }, - success: async (api) => { + success: async () => { const { username, token } = await createRandomTestUser(api, adminToken) const { username: otherUser, token: otherToken } = await createRandomTestUser(api, adminToken) @@ -683,7 +683,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler ) }, - unauthorized: async (api) => { + unauthorized: async () => { const { username, token } = await createRandomTestUser(api, adminToken) await failOrThrow("unauthorized", () => api.getTransactionById({ username, token: "wrongtoken" as AccessToken }, 123123123) @@ -691,13 +691,13 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, }, test_getTransactions: { - "not-found": async (api) => { + "not-found": async () => { await failOrThrow("not-found", () => api.getTransactions({ username: "not-found", token: adminToken, })) }, - success: async (api) => { + success: async () => { const { username, token } = await createRandomTestUser(api, adminToken) // await succeedOrThrow(() => api.getTransactions(creds)) const txs = await succeedOrThrow(() => api.getTransactions({ username, token }, { @@ -705,7 +705,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler order: "asc" })) }, - unauthorized: async (api) => { + unauthorized: async () => { const { username, token } = await createRandomTestUser(api, adminToken) await failOrThrow("unauthorized", () => api.getTransactions({ @@ -716,20 +716,20 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, }, test_getWithdrawalById: { - "invalid-id": async (api) => { + "invalid-id": async () => { await failOrThrow("invalid-id", () => api.getWithdrawalById("invalid") ) }, - "not-found": async (api) => { + "not-found": async () => { await failOrThrow("not-found", () => api.getWithdrawalById("11111111-1111-1111-1111-111111111111") ) }, - success: async (api) => { + success: async () => { const { username, token } = await createRandomTestUser(api, adminToken) const userInfo = await succeedOrThrow(() => api.getAccount({ username, token }) @@ -745,7 +745,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, }, test_updateAccount: { - "cant-change-legal-name-or-admin": async (api) => { + "cant-change-legal-name-or-admin": async () => { const { username, token } = await createRandomTestUser(api, adminToken) await failOrThrow("cant-change-legal-name-or-admin", () => @@ -755,7 +755,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler ) }, - "not-found": async (api) => { + "not-found": async () => { const { username, token } = await createRandomTestUser(api, adminToken) await failOrThrow("not-found", () => api.updateAccount({ username: "notfound", token }, { @@ -765,7 +765,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }) ) }, - success: async (api) => { + success: async () => { const { username, token } = await createRandomTestUser(api, adminToken) await succeedOrThrow(() => @@ -777,7 +777,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler ) }, - unauthorized: async (api) => { + unauthorized: async () => { await failOrThrow("unauthorized", () => api.updateAccount({ username: "notfound", token: "wrongtoken" as AccessToken }, { @@ -789,7 +789,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, }, test_updatePassword: { - "not-found": async (api) => { + "not-found": async () => { await failOrThrow("not-found", () => api.updatePassword({ username: "notfound", token: adminToken }, { @@ -800,7 +800,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, - "old-password-invalid-or-not-allowed": async (api) => { + "old-password-invalid-or-not-allowed": async () => { const { username, token } = await createRandomTestUser(api, adminToken) await failOrThrow("old-password-invalid-or-not-allowed", () => @@ -811,7 +811,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler ) }, - success: async (api) => { + success: async () => { const { username, token } = await createRandomTestUser(api, adminToken) await succeedOrThrow(() => @@ -823,7 +823,7 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler }, - unauthorized: async (api) => { + unauthorized: async () => { const { username, token } = await createRandomTestUser(api, adminToken) await failOrThrow("unauthorized", () => api.updatePassword({ username: "admin", token }, { @@ -838,6 +838,128 @@ export function createTestForBankCore(adminToken: AccessToken): TestForApi<Taler } } +export function createTestForBankRevenue(bank: TalerCoreBankHttpClient, adminToken: AccessToken): TestForApi<TalerRevenueHttpClient> { + + return { + test_getHistory: { + "endpoint-wrong-or-username-wrong": async () => { + const history = await failOrThrow("endpoint-wrong-or-username-wrong", () => + bank.getRevenueAPI("notfound").getHistory("wrongtoken" as AccessToken) + ) + }, + "invalid-input": undefined, + success: async () => { + const { token: exchangeToken, username: exchangeUsername } = await createRandomTestUser(bank, adminToken, { + is_taler_exchange: true + }) + const { token: merchantToken, username: merchantUsername } = await createRandomTestUser(bank, adminToken) + const config = await succeedOrThrow(() => bank.getConfig()) + + const merchantinfo = await succeedOrThrow(() => + bank.getAccount({ username: merchantUsername, token: merchantToken }) + ) + const account = parsePaytoUri(merchantinfo.payto_uri)! + account.params["message"] = "all" + + const amount = Amounts.stringify({ + currency: config.currency.name, + fraction: 0, + value: 1 + }) + + await succeedOrThrow(() => + bank.createTransaction({ username: exchangeUsername, token: exchangeToken }, { + payto_uri: stringifyPaytoUri(account), + amount + }) + ) + const history = await succeedOrThrow(() => + bank.getRevenueAPI(merchantUsername).getHistory(merchantToken) + ) + }, + unauthorized: async () => { + const { token: merchantToken, username: merchantUsername } = await createRandomTestUser(bank, adminToken) + const history = await failOrThrow("unauthorized", () => + bank.getRevenueAPI(merchantUsername).getHistory("wrongtoken" as AccessToken) + ) + }, + } + } +} + +export function createTestForBankWireGateway(bank: TalerCoreBankHttpClient, adminToken: AccessToken): TestForApi<TalerWireGatewayHttpClient> { + return { + //not used in production + test_addIncoming: { + "invalid-input": undefined, + "not-found": undefined, + "reserve-id-already-used": undefined, + success: undefined, + unauthorized: undefined, + }, + test_getHistoryIncoming: { + "invalid-input": async () => { + }, + "not-found": async () => { + }, + success: async () => { + }, + unauthorized: async () => { + }, + }, + test_getHistoryOutgoing: { + "invalid-input": async () => { + }, + "not-found": async () => { + }, + success: async () => { + }, + unauthorized: async () => { + }, + }, + test_transfer: { + "invalid-input": async () => { + }, + "not-found": async () => { + }, + "request-uid-already-used": async () => { + }, + success: async () => { + const { token: exchangeToken, username: exchangeUsername } = await createRandomTestUser(bank, adminToken, { + is_taler_exchange: true + }) + const { token: merchantToken, username: merchantUsername } = await createRandomTestUser(bank, adminToken) + const config = await succeedOrThrow(() => bank.getConfig()) + + const merchantInfo = await succeedOrThrow(() => + bank.getAccount({ username: merchantUsername, token: merchantToken }) + ) + const account = parsePaytoUri(merchantInfo.payto_uri)! + account.params["message"] = "all" + + const amount = Amounts.stringify({ + currency: config.currency.name, + fraction: 0, + value: 1 + }) + const resp = await succeedOrThrow(() => + bank.getWireGatewayAPI(merchantUsername).transfer(exchangeToken, { + amount, + credit_account: merchantInfo.payto_uri, + exchange_base_url: "", + request_uid: "", + wtid: "" + }) + ) + + }, + unauthorized: async () => { + }, + } + } +} + + export async function createRandomTestUser(api: TalerCoreBankHttpClient, adminToken: AccessToken, options: Partial<TalerCorebankApi.RegisterAccountRequest> = {}) { const username = "harness-" + encodeCrock(getRandomBytes(10)).toLowerCase(); await succeedOrThrow(() => diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts index af4e5c788..5bf60d54f 100644 --- a/packages/taler-harness/src/index.ts +++ b/packages/taler-harness/src/index.ts @@ -30,6 +30,7 @@ import { TalerCoreBankHttpClient, TalerCorebankApiClient, TalerError, + TestForApi, addPaytoQueryParams, decodeCrock, generateIban, @@ -656,12 +657,46 @@ deploymentCli process.exit(2); }); + +type TestResult = { testName: string, caseName: string, result: "skiped" | "ok" | "fail", error?: any } + +async function getTestSummary<T extends object>(filter: string | undefined, ...apis: Array<TestForApi<T>>) { + const regex = !filter ? undefined : new RegExp(filter) + const apiState = await Promise.all(apis.flatMap(api => Object.entries(api).flatMap(([testName, casesMap]) => { + return Object.entries(casesMap).map(async ([caseName, caseFunc]): Promise<TestResult> => { + if (!caseFunc) { + return { testName, caseName, result: "skiped" as const } + } + if (regex && !regex.test(`${testName}:${caseName}`)) { + return { testName, caseName, result: "skiped" as const } + } + return caseFunc() + .then(r => ({ testName, caseName, result: "ok" as const })) + .catch(error => ({ testName, caseName, result: "fail" as const, error })) + }) + }))) + + return apiState.reduce((prev, testResult) => { + if (testResult.result === "ok") { + prev.ok.push(testResult) + } + if (testResult.result === "skiped") { + prev.skiped.push(testResult) + } + if (testResult.result === "fail") { + prev.fail.push(testResult) + } + return prev + }, { "ok": [] as TestResult[], "skiped": [] as TestResult[], "fail": [] as TestResult[] }) +} + deploymentCli .subcommand("testBankAPI", "test-bank-api", { help: "test api compatibility.", }) .requiredArgument("corebankApiBaseUrl", clk.STRING) .maybeOption("adminPwd", ["--admin-password"], clk.STRING) + .maybeOption("filter", ["--filter"], clk.STRING) .flag("showCurl", ["--show-curl"]) .action(async (args) => { const httpLib = createPlatformHttpLib(); @@ -685,55 +720,33 @@ deploymentCli scope: "readwrite" }) + if (args.testBankAPI.showCurl) { + setPrintHttpRequestAsCurl(true) + } + if (resp.type === "fail") { console.log("wrong admin password") return; } - const tester = createTestForBankCore(resp.body.access_token) - if (args.testBankAPI.showCurl) { - setPrintHttpRequestAsCurl(true) - } + const bankCore = createTestForBankCore(api, resp.body.access_token) - const apiState = await Promise.all(Object.entries(tester).flatMap(([testName, casesMap]) => { - return Object.entries(casesMap).map(([caseName, caseFunc]) => { - if (!caseFunc) { - return { testName, caseName, result: "skiped" as const } - } - return caseFunc(api) - .then(r => ({ testName, caseName, result: "ok" as const })) - .catch(error => ({ testName, caseName, result: "fail" as const, error })) - }) - })) - - const total = apiState.reduce((prev, testResult) => { - if (testResult.result === "ok") { - prev.ok += 1 - } - if (testResult.result === "skiped") { - prev.skiped += 1 - } - if (testResult.result === "fail") { - prev.fail += 1 - } - return prev - }, { "ok": 0, "skiped": 0, "fail": 0 }) + const summary = await getTestSummary(args.testBankAPI.filter, bankCore) - console.log("successful tests:", total.ok) - console.log("uncompleted tests:", total.skiped) - apiState.forEach((testResult) => { + console.log("successful tests:", summary.ok.length) + console.log("uncompleted tests:", summary.skiped.length) + summary.skiped.forEach((testResult) => { if (testResult.result === "skiped") { console.log(" ", testResult.testName, testResult.caseName) } }) - console.log("failed tests:", total.fail) - apiState.filter(t => t.result === "fail").forEach((testResult, i) => { + console.log("failed tests:", summary.fail.length) + summary.fail.forEach((testResult, i) => { console.log(i, ")", testResult) }) }); - deploymentCli .subcommand("coincfg", "gen-coin-config", { help: "Generate a coin/denomination configuration for the exchange.", diff --git a/packages/taler-util/src/http-client/bank-integration.ts b/packages/taler-util/src/http-client/bank-integration.ts index 7cddcb9a9..b526805df 100644 --- a/packages/taler-util/src/http-client/bank-integration.ts +++ b/packages/taler-util/src/http-client/bank-integration.ts @@ -1,7 +1,7 @@ import { HttpRequestLibrary, readSuccessResponseJsonOrThrow } from "../http-common.js"; import { HttpStatusCode } from "../http-status-codes.js"; import { createPlatformHttpLib } from "../http.js"; -import { opKnownFailure, opSuccess, opUnknownFailure } from "../operation.js"; +import { FailCasesByMethod, ResultByMethod, opKnownFailure, opSuccess, opUnknownFailure } from "../operation.js"; import { TalerErrorCode } from "../taler-error-codes.js"; import { codecForTalerErrorDetail } from "../wallet-types.js"; import { @@ -11,6 +11,12 @@ import { codecForIntegrationBankConfig } from "./types.js"; +export type TalerBankIntegrationResultByMethod<prop extends keyof TalerBankIntegrationHttpClient> = ResultByMethod<TalerBankIntegrationHttpClient, prop> +export type TalerBankIntegrationErrorsByMethod<prop extends keyof TalerBankIntegrationHttpClient> = FailCasesByMethod<TalerBankIntegrationHttpClient, prop> + +/** + * The API is used by the wallets. + */ export class TalerBankIntegrationHttpClient { httpLib: HttpRequestLibrary; diff --git a/packages/taler-util/src/http-client/bank-revenue.ts b/packages/taler-util/src/http-client/bank-revenue.ts index c6adac557..14d93fbe6 100644 --- a/packages/taler-util/src/http-client/bank-revenue.ts +++ b/packages/taler-util/src/http-client/bank-revenue.ts @@ -1,10 +1,17 @@ import { HttpRequestLibrary, makeBasicAuthHeader, readSuccessResponseJsonOrThrow } from "../http-common.js"; import { HttpStatusCode } from "../http-status-codes.js"; import { createPlatformHttpLib } from "../http.js"; -import { opKnownFailure, opSuccess, opUnknownFailure } from "../operation.js"; +import { FailCasesByMethod, ResultByMethod, opKnownFailure, opSuccess, opUnknownFailure } from "../operation.js"; import { PaginationParams, TalerRevenueApi, codecForMerchantIncomingHistory } from "./types.js"; import { addPaginationParams } from "./utils.js"; +export type TalerBankRevenueResultByMethod<prop extends keyof TalerRevenueHttpClient> = ResultByMethod<TalerRevenueHttpClient, prop> +export type TalerBankRevenueErrorsByMethod<prop extends keyof TalerRevenueHttpClient> = FailCasesByMethod<TalerRevenueHttpClient, prop> + +/** + * The API is used by the merchant (or other parties) to query + * for incoming transactions to their account. + */ export class TalerRevenueHttpClient { httpLib: HttpRequestLibrary; diff --git a/packages/taler-util/src/http-client/bank-wire.ts b/packages/taler-util/src/http-client/bank-wire.ts index af0857ac5..0d312704e 100644 --- a/packages/taler-util/src/http-client/bank-wire.ts +++ b/packages/taler-util/src/http-client/bank-wire.ts @@ -1,10 +1,13 @@ -import { HttpRequestLibrary, makeBasicAuthHeader, readSuccessResponseJsonOrThrow } from "../http-common.js"; +import { HttpRequestLibrary, makeBasicAuthHeader } from "../http-common.js"; import { HttpStatusCode } from "../http-status-codes.js"; import { createPlatformHttpLib } from "../http.js"; -import { opEmptySuccess, opFixedSuccess, opKnownFailure, opSuccess, opUnknownFailure } from "../operation.js"; +import { FailCasesByMethod, ResultByMethod, opFixedSuccess, opKnownFailure, opSuccess, opUnknownFailure } from "../operation.js"; import { PaginationParams, TalerWireGatewayApi, codecForAddIncomingResponse, codecForIncomingHistory, codecForOutgoingHistory, codecForTransferResponse } from "./types.js"; import { addPaginationParams } from "./utils.js"; +export type TalerWireGatewayResultByMethod<prop extends keyof TalerWireGatewayHttpClient> = ResultByMethod<TalerWireGatewayHttpClient, prop> +export type TalerWireGatewayErrorsByMethod<prop extends keyof TalerWireGatewayHttpClient> = FailCasesByMethod<TalerWireGatewayHttpClient, prop> + /** * The API is used by the exchange to trigger transactions and query * incoming transactions, as well as by the auditor to query incoming diff --git a/packages/taler-util/src/http-client/exchange.ts b/packages/taler-util/src/http-client/exchange.ts new file mode 100644 index 000000000..52f5dc5a6 --- /dev/null +++ b/packages/taler-util/src/http-client/exchange.ts @@ -0,0 +1,37 @@ +import { HttpRequestLibrary } from "../http-common.js"; +import { HttpStatusCode } from "../http-status-codes.js"; +import { createPlatformHttpLib } from "../http.js"; +import { FailCasesByMethod, ResultByMethod, opSuccess, opUnknownFailure } from "../operation.js"; +import { codecForExchangeConfig } from "./types.js"; + +export type TalerExchangeResultByMethod<prop extends keyof TalerExchangeHttpClient> = ResultByMethod<TalerExchangeHttpClient, prop> +export type TalerExchangeErrorsByMethod<prop extends keyof TalerExchangeHttpClient> = FailCasesByMethod<TalerExchangeHttpClient, prop> + +/** + */ +export class TalerExchangeHttpClient { + httpLib: HttpRequestLibrary; + + constructor( + readonly baseUrl: string, + httpClient?: HttpRequestLibrary, + ) { + this.httpLib = httpClient ?? createPlatformHttpLib(); + } + + /** + * https://docs.taler.net/core/api-merchant.html#get--config + * + */ + async getConfig() { + const url = new URL(`config`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "GET" + }); + switch (resp.status) { + case HttpStatusCode.Ok: return opSuccess(resp, codecForExchangeConfig()) + default: return opUnknownFailure(resp, await resp.text()) + } + } + +}
\ No newline at end of file diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts new file mode 100644 index 000000000..5aace2d78 --- /dev/null +++ b/packages/taler-util/src/http-client/merchant.ts @@ -0,0 +1,37 @@ +import { HttpRequestLibrary } from "../http-common.js"; +import { HttpStatusCode } from "../http-status-codes.js"; +import { createPlatformHttpLib } from "../http.js"; +import { FailCasesByMethod, ResultByMethod, opSuccess, opUnknownFailure } from "../operation.js"; +import { codecForMerchantConfig } from "./types.js"; + +export type TalerMerchantResultByMethod<prop extends keyof TalerMerchantHttpClient> = ResultByMethod<TalerMerchantHttpClient, prop> +export type TalerMerchantErrorsByMethod<prop extends keyof TalerMerchantHttpClient> = FailCasesByMethod<TalerMerchantHttpClient, prop> + +/** + */ +export class TalerMerchantHttpClient { + httpLib: HttpRequestLibrary; + + constructor( + readonly baseUrl: string, + httpClient?: HttpRequestLibrary, + ) { + this.httpLib = httpClient ?? createPlatformHttpLib(); + } + + /** + * https://docs.taler.net/core/api-merchant.html#get--config + * + */ + async getConfig() { + const url = new URL(`config`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "GET" + }); + switch (resp.status) { + case HttpStatusCode.Ok: return opSuccess(resp, codecForMerchantConfig()) + default: return opUnknownFailure(resp, await resp.text()) + } + } + +}
\ No newline at end of file diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index 38df08f9a..fe69925f6 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -187,6 +187,7 @@ export namespace TalerAuthentication { } } +// DD51 https://docs.taler.net/design-documents/051-fractional-digits.html export interface CurrencySpecification { // Name of the currency. @@ -239,7 +240,8 @@ export const codecForIntegrationBankConfig = export const codecForCoreBankConfig = (): Codec<TalerCorebankApi.Config> => buildCodecForObject<TalerCorebankApi.Config>() - .property("name", codecForConstString("taler-corebank")) + .property("name", codecForConstString("libeufin-bank")) + // .property("name", codecForConstString("taler-corebank")) .property("version", codecForString()) .property("allow_deletions", codecForBoolean()) .property("allow_registrations", codecForBoolean()) @@ -249,6 +251,25 @@ export const codecForCoreBankConfig = .property("conversion_info", codecOptional(codecForConversionRatesResponse())) .build("TalerCorebankApi.Config") +export const codecForMerchantConfig = + (): Codec<TalerMerchantApi.VersionResponse> => + buildCodecForObject<TalerMerchantApi.VersionResponse>() + .property("name", codecForConstString("taler-merchant")) + .property("currency", codecForString()) + .property("version", codecForString()) + .property("currencies", codecForMap(codecForCurrencySpecificiation())) + .build("TalerMerchantApi.VersionResponse") + +export const codecForExchangeConfig = + (): Codec<TalerExchangeApi.ExchangeVersionResponse> => + buildCodecForObject<TalerExchangeApi.ExchangeVersionResponse>() + .property("version", codecForString()) + .property("name", codecForConstString("taler-exchange")) + .property("currency", codecForString()) + .property("currency_specification", codecForCurrencySpecificiation()) + .property("supported_kyc_requirements", codecForList(codecForString())) + .build("TalerExchangeApi.ExchangeVersionResponse") + const codecForBalance = (): Codec<TalerCorebankApi.Balance> => buildCodecForObject<TalerCorebankApi.Balance>() .property("amount", codecForAmountString()) @@ -552,12 +573,41 @@ export const codecForAddIncomingResponse = type EmailAddress = string; type PhoneNumber = string; +type EddsaSignature = string; +// base32 encoded RSA blinded signature. +type BlindedRsaSignature = string; +type Base32 = string; + type DecimalNumber = number; +type RsaSignature = string; +// The type of a coin's blinded envelope depends on the cipher that is used +// for signing with a denomination key. +type CoinEnvelope = RSACoinEnvelope | CSCoinEnvelope; +// For denomination signatures based on RSA, the planchet is just a blinded +// coin's public EdDSA key. +interface RSACoinEnvelope { + cipher: "RSA" | "RSA+age_restricted"; + rsa_blinded_planchet: string; // Crockford Base32 encoded +} +// For denomination signatures based on Blind Clause-Schnorr, the planchet +// consists of the public nonce and two Curve25519 scalars which are two +// blinded challenges in the Blinded Clause-Schnorr signature scheme. +// See https://taler.net/papers/cs-thesis.pdf for details. +interface CSCoinEnvelope { + cipher: "CS" | "CS+age_restricted"; + cs_nonce: string; // Crockford Base32 encoded + cs_blinded_c0: string; // Crockford Base32 encoded + cs_blinded_c1: string; // Crockford Base32 encoded +} +// Secret for blinding/unblinding. +// An RSA blinding secret, which is basically +// a 256-bit nonce, converted to Crockford Base32. +type DenominationBlindingKeyP = string; const codecForURL = codecForString const codecForLibtoolVersion = codecForString const codecForCurrencyName = codecForString -const codecForTalerWithdrawalURI = codecForString +const codecForEddsaSignature = codecForString const codecForDecimalNumber = codecForNumber enum TanChannel { @@ -869,7 +919,9 @@ export namespace TalerCorebankApi { } export interface Config { // Name of this API, always "taler-corebank". - name: "taler-corebank"; + name: "libeufin-bank"; + // name: "taler-corebank"; + // API version in the form $n:$n:$n version: string; @@ -1286,3 +1338,1928 @@ export namespace TalerCorebankApi { } + +export namespace TalerExchangeApi { + + + export interface ExchangeVersionResponse { + // libtool-style representation of the Exchange protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // Name of the protocol. + name: "taler-exchange"; + + // Currency supported by this exchange. + currency: string; + + // How wallets should render this currency. + currency_specification: CurrencySpecification; + + // Names of supported KYC requirements. + supported_kyc_requirements: string[]; + + } + + type AccountRestriction = + | RegexAccountRestriction + | DenyAllAccountRestriction + // Account restriction that disables this type of + // account for the indicated operation categorically. + interface DenyAllAccountRestriction { + + type: "deny"; + } + // Accounts interacting with this type of account + // restriction must have a payto://-URI matching + // the given regex. + interface RegexAccountRestriction { + + type: "regex"; + + // Regular expression that the payto://-URI of the + // partner account must follow. The regular expression + // should follow posix-egrep, but without support for character + // classes, GNU extensions, back-references or intervals. See + // https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html + // for a description of the posix-egrep syntax. Applications + // may support regexes with additional features, but exchanges + // must not use such regexes. + payto_regex: string; + + // Hint for a human to understand the restriction + // (that is hopefully easier to comprehend than the regex itself). + human_hint: string; + + // Map from IETF BCP 47 language tags to localized + // human hints. + human_hint_i18n?: { [lang_tag: string]: string }; + + } + + export interface WireAccount { + // payto:// URI identifying the account and wire method + payto_uri: PaytoString; + + // URI to convert amounts from or to the currency used by + // this wire account of the exchange. Missing if no + // conversion is applicable. + conversion_url?: string; + + // Restrictions that apply to bank accounts that would send + // funds to the exchange (crediting this exchange bank account). + // Optional, empty array for unrestricted. + credit_restrictions: AccountRestriction[]; + + // Restrictions that apply to bank accounts that would receive + // funds from the exchange (debiting this exchange bank account). + // Optional, empty array for unrestricted. + debit_restrictions: AccountRestriction[]; + + // Signature using the exchange's offline key over + // a TALER_MasterWireDetailsPS + // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS. + master_sig: EddsaSignature; + } + +} + +export namespace TalerMerchantApi { + export interface VersionResponse { + // libtool-style representation of the Merchant protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // Name of the protocol. + name: "taler-merchant"; + + // Default (!) currency supported by this backend. + // This is the currency that the backend should + // suggest by default to the user when entering + // amounts. See currencies for a list of + // supported currencies and how to render them. + currency: string; + + // How wallets should render currencies supported + // by this backend. Maps + // currency codes (e.g. "EUR" or "KUDOS") to + // the respective currency specification. + // All currencies in this map are supported by + // the backend. + currencies: { [currency: string]: CurrencySpecification }; + + } + + export interface ClaimRequest { + // Nonce to identify the wallet that claimed the order. + nonce: string; + + // Token that authorizes the wallet to claim the order. + // *Optional* as the merchant may not have required it + // (create_token set to false in PostOrderRequest). + token?: ClaimToken; + } + + export interface ClaimResponse { + // Contract terms of the claimed order + contract_terms: ContractTerms; + + // Signature by the merchant over the contract terms. + sig: EddsaSignature; + } + + export interface PaymentResponse { + // Signature on TALER_PaymentResponsePS with the public + // key of the merchant instance. + sig: EddsaSignature; + + // Text to be shown to the point-of-sale staff as a proof of + // payment. + pos_confirmation?: string; + + } + + interface PayRequest { + // The coins used to make the payment. + coins: CoinPaySig[]; + + // Custom inputs from the wallet for the contract. + wallet_data?: Object; + + // The session for which the payment is made (or replayed). + // Only set for session-based payments. + session_id?: string; + + } + export interface CoinPaySig { + // Signature by the coin. + coin_sig: EddsaSignature; + + // Public key of the coin being spent. + coin_pub: EddsaPublicKey; + + // Signature made by the denomination public key. + ub_sig: RsaSignature; + + // The hash of the denomination public key associated with this coin. + h_denom: HashCode; + + // The amount that is subtracted from this coin with this payment. + contribution: AmountString; + + // URL of the exchange this coin was withdrawn from. + exchange_url: string; + } + + + interface StatusPaid { + // Was the payment refunded (even partially, via refund or abort)? + refunded: boolean; + + // Is any amount of the refund still waiting to be picked up (even partially)? + refund_pending: boolean; + + // Amount that was refunded in total. + refund_amount: AmountString; + + // Amount that already taken by the wallet. + refund_taken: AmountString; + } + interface StatusGotoResponse { + // The client should go to the reorder URL, there a fresh + // order might be created as this one is taken by another + // customer or wallet (or repurchase detection logic may + // apply). + public_reorder_url: string; + } + interface StatusUnpaidResponse { + // URI that the wallet must process to complete the payment. + taler_pay_uri: string; + + // Status URL, can be used as a redirect target for the browser + // to show the order QR code / trigger the wallet. + fulfillment_url?: string; + + // Alternative order ID which was paid for already in the same session. + // Only given if the same product was purchased before in the same session. + already_paid_order_id?: string; + } + + interface PaidRefundStatusResponse { + + // Text to be shown to the point-of-sale staff as a proof of + // payment (present only if re-usable OTP algorithm is used). + pos_confirmation?: string; + + // True if the order has been subjected to + // refunds. False if it was simply paid. + refunded: boolean; + } + interface PaidRequest { + // Signature on TALER_PaymentResponsePS with the public + // key of the merchant instance. + sig: EddsaSignature; + + // Hash of the order's contract terms (this is used to authenticate the + // wallet/customer and to enable signature verification without + // database access). + h_contract: HashCode; + + // Hash over custom inputs from the wallet for the contract. + wallet_data_hash?: HashCode; + + // Session id for which the payment is proven. + session_id: string; + } + + interface AbortRequest { + + // Hash of the order's contract terms (this is used to authenticate the + // wallet/customer in case $ORDER_ID is guessable). + h_contract: HashCode; + + // List of coins the wallet would like to see refunds for. + // (Should be limited to the coins for which the original + // payment succeeded, as far as the wallet knows.) + coins: AbortingCoin[]; + } + interface AbortingCoin { + // Public key of a coin for which the wallet is requesting an abort-related refund. + coin_pub: EddsaPublicKey; + + // The amount to be refunded (matches the original contribution) + contribution: AmountString; + + // URL of the exchange this coin was withdrawn from. + exchange_url: string; + } + interface AbortResponse { + + // List of refund responses about the coins that the wallet + // requested an abort for. In the same order as the coins + // from the original request. + // The rtransaction_id is implied to be 0. + refunds: MerchantAbortPayRefundStatus[]; + } + type MerchantAbortPayRefundStatus = + | MerchantAbortPayRefundSuccessStatus + | MerchantAbortPayRefundFailureStatus; + // Details about why a refund failed. + interface MerchantAbortPayRefundFailureStatus { + // Used as tag for the sum type RefundStatus sum type. + type: "failure"; + + // HTTP status of the exchange request, must NOT be 200. + exchange_status: Integer; + + // Taler error code from the exchange reply, if available. + exchange_code?: Integer; + + // If available, HTTP reply from the exchange. + exchange_reply?: Object; + } + // Additional details needed to verify the refund confirmation signature + // (h_contract_terms and merchant_pub) are already known + // to the wallet and thus not included. + interface MerchantAbortPayRefundSuccessStatus { + // Used as tag for the sum type MerchantCoinRefundStatus sum type. + type: "success"; + + // HTTP status of the exchange request, 200 (integer) required for refund confirmations. + exchange_status: 200; + + // The EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the + // exchange affirming the successful refund. + exchange_sig: EddsaSignature; + + // Public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKey; + } + + interface WalletRefundRequest { + // Hash of the order's contract terms (this is used to authenticate the + // wallet/customer). + h_contract: HashCode; + } + interface WalletRefundResponse { + // Amount that was refunded in total. + refund_amount: AmountString; + + // Successful refunds for this payment, empty array for none. + refunds: MerchantCoinRefundStatus[]; + + // Public key of the merchant. + merchant_pub: EddsaPublicKey; + + } + type MerchantCoinRefundStatus = + | MerchantCoinRefundSuccessStatus + | MerchantCoinRefundFailureStatus; + // Details about why a refund failed. + interface MerchantCoinRefundFailureStatus { + // Used as tag for the sum type RefundStatus sum type. + type: "failure"; + + // HTTP status of the exchange request, must NOT be 200. + exchange_status: Integer; + + // Taler error code from the exchange reply, if available. + exchange_code?: Integer; + + // If available, HTTP reply from the exchange. + exchange_reply?: Object; + + // Refund transaction ID. + rtransaction_id: Integer; + + // Public key of a coin that was refunded. + coin_pub: EddsaPublicKey; + + // Amount that was refunded, including refund fee charged by the exchange + // to the customer. + refund_amount: AmountString; + + // Timestamp when the merchant approved the refund. + // Useful for grouping refunds. + execution_time: Timestamp; + } + // Additional details needed to verify the refund confirmation signature + // (h_contract_terms and merchant_pub) are already known + // to the wallet and thus not included. + interface MerchantCoinRefundSuccessStatus { + // Used as tag for the sum type MerchantCoinRefundStatus sum type. + type: "success"; + + // HTTP status of the exchange request, 200 (integer) required for refund confirmations. + exchange_status: 200; + + // The EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the + // exchange affirming the successful refund. + exchange_sig: EddsaSignature; + + // Public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKey; + + // Refund transaction ID. + rtransaction_id: Integer; + + // Public key of a coin that was refunded. + coin_pub: EddsaPublicKey; + + // Amount that was refunded, including refund fee charged by the exchange + // to the customer. + refund_amount: AmountString; + + // Timestamp when the merchant approved the refund. + // Useful for grouping refunds. + execution_time: Timestamp; + } + + interface RewardInformation { + + // Exchange from which the reward will be withdrawn. Needed by the + // wallet to determine denominations, fees, etc. + exchange_url: string; + + // URL where to go after obtaining the reward. + next_url: string; + + // (Remaining) amount of the reward (including fees). + reward_amount: AmountString; + + // Timestamp indicating when the reward is set to expire (may be in the past). + // Note that rewards that have expired MAY also result in a 404 response. + expiration: Timestamp; + } + + interface RewardPickupRequest { + + // List of planchets the wallet wants to use for the reward. + planchets: PlanchetDetail[]; + } + interface PlanchetDetail { + // Hash of the denomination's public key (hashed to reduce + // bandwidth consumption). + denom_pub_hash: HashCode; + + // Coin's blinded public key. + coin_ev: CoinEnvelope; + } + interface RewardResponse { + + // Blind RSA signatures over the planchets. + // The order of the signatures matches the planchets list. + blind_sigs: BlindSignature[]; + } + interface BlindSignature { + + // The (blind) RSA signature. Still needs to be unblinded. + blind_sig: BlindedRsaSignature; + } + + interface InstanceConfigurationMessage { + + // Name of the merchant instance to create (will become $INSTANCE). + // Must match the regex ^[A-Za-z0-9][A-Za-z0-9_.@-]+$. + id: string; + + // Merchant name corresponding to this instance. + name: string; + + // Type of the user (business or individual). + // Defaults to 'business'. Should become mandatory field + // in the future, left as optional for API compatibility for now. + user_type?: string; + + // Merchant email for customer contact. + email?: string; + + // Merchant public website. + website?: string; + + // Merchant logo. + logo?: ImageDataUrl; + + // Authentication settings for this instance + auth: InstanceAuthConfigurationMessage; + + // The merchant's physical address (to be put into contracts). + address: Location; + + // The jurisdiction under which the merchant conducts its business + // (to be put into contracts). + jurisdiction: Location; + + // Use STEFAN curves to determine default fees? + // If false, no fees are allowed by default. + // Can always be overridden by the frontend on a per-order basis. + use_stefan: boolean; + + // If the frontend does NOT specify an execution date, how long should + // we tell the exchange to wait to aggregate transactions before + // executing the wire transfer? This delay is added to the current + // time when we generate the advisory execution time for the exchange. + default_wire_transfer_delay: RelativeTime; + + // If the frontend does NOT specify a payment deadline, how long should + // offers we make be valid by default? + default_pay_delay: RelativeTime; + + } + + interface InstanceAuthConfigurationMessage { + // Type of authentication. + // "external": The mechant backend does not do + // any authentication checks. Instead an API + // gateway must do the authentication. + // "token": The merchant checks an auth token. + // See "token" for details. + method: "external" | "token"; + + // For method "token", this field is mandatory. + // The token MUST begin with the string "secret-token:". + // After the auth token has been set (with method "token"), + // the value must be provided in a "Authorization: Bearer $token" + // header. + token?: string; + + } + + interface LoginTokenRequest { + // Scope of the token (which kinds of operations it will allow) + scope: "readonly" | "write"; + + // Server may impose its own upper bound + // on the token validity duration + duration?: RelativeTime; + + // Can this token be refreshed? + // Defaults to false. + refreshable?: boolean; + } + interface LoginTokenSuccessResponse { + // The login token that can be used to access resources + // that are in scope for some time. Must be prefixed + // with "Bearer " when used in the "Authorization" HTTP header. + // Will already begin with the RFC 8959 prefix. + token: string; + + // Scope of the token (which kinds of operations it will allow) + scope: "readonly" | "write"; + + // Server may impose its own upper bound + // on the token validity duration + expiration: Timestamp; + + // Can this token be refreshed? + refreshable: boolean; + } + + interface InstanceReconfigurationMessage { + + // Merchant name corresponding to this instance. + name: string; + + // Type of the user (business or individual). + // Defaults to 'business'. Should become mandatory field + // in the future, left as optional for API compatibility for now. + user_type?: string; + + // Merchant email for customer contact. + email?: string; + + // Merchant public website. + website?: string; + + // Merchant logo. + logo?: ImageDataUrl; + + // The merchant's physical address (to be put into contracts). + address: Location; + + // The jurisdiction under which the merchant conducts its business + // (to be put into contracts). + jurisdiction: Location; + + // Use STEFAN curves to determine default fees? + // If false, no fees are allowed by default. + // Can always be overridden by the frontend on a per-order basis. + use_stefan: boolean; + + // If the frontend does NOT specify an execution date, how long should + // we tell the exchange to wait to aggregate transactions before + // executing the wire transfer? This delay is added to the current + // time when we generate the advisory execution time for the exchange. + default_wire_transfer_delay: RelativeTime; + + // If the frontend does NOT specify a payment deadline, how long should + // offers we make be valid by default? + default_pay_delay: RelativeTime; + + } + + interface InstancesResponse { + // List of instances that are present in the backend (see Instance). + instances: Instance[]; + } + + interface Instance { + // Merchant name corresponding to this instance. + name: string; + + // Type of the user ("business" or "individual"). + user_type: string; + + // Merchant public website. + website?: string; + + // Merchant logo. + logo?: ImageDataUrl; + + // Merchant instance this response is about ($INSTANCE). + id: string; + + // Public key of the merchant/instance, in Crockford Base32 encoding. + merchant_pub: EddsaPublicKey; + + // List of the payment targets supported by this instance. Clients can + // specify the desired payment target in /order requests. Note that + // front-ends do not have to support wallets selecting payment targets. + payment_targets: string[]; + + // Has this instance been deleted (but not purged)? + deleted: boolean; + } + + interface QueryInstancesResponse { + + // Merchant name corresponding to this instance. + name: string; + + // Type of the user ("business" or "individual"). + user_type: string; + + // Merchant email for customer contact. + email?: string; + + // Merchant public website. + website?: string; + + // Merchant logo. + logo?: ImageDataUrl; + + // Public key of the merchant/instance, in Crockford Base32 encoding. + merchant_pub: EddsaPublicKey; + + // The merchant's physical address (to be put into contracts). + address: Location; + + // The jurisdiction under which the merchant conducts its business + // (to be put into contracts). + jurisdiction: Location; + + // Use STEFAN curves to determine default fees? + // If false, no fees are allowed by default. + // Can always be overridden by the frontend on a per-order basis. + use_stefan: boolean; + + // If the frontend does NOT specify an execution date, how long should + // we tell the exchange to wait to aggregate transactions before + // executing the wire transfer? This delay is added to the current + // time when we generate the advisory execution time for the exchange. + default_wire_transfer_delay: RelativeTime; + + // If the frontend does NOT specify a payment deadline, how long should + // offers we make be valid by default? + default_pay_delay: RelativeTime; + + // Authentication configuration. + // Does not contain the token when token auth is configured. + auth: { + type: "external" | "token"; + }; + + } + + interface AccountKycRedirects { + + // Array of pending KYCs. + pending_kycs: MerchantAccountKycRedirect[]; + + // Array of exchanges with no reply. + timeout_kycs: ExchangeKycTimeout[]; + } + + interface MerchantAccountKycRedirect { + + // URL that the user should open in a browser to + // proceed with the KYC process (as returned + // by the exchange's /kyc-check/ endpoint). + // Optional, missing if the account is blocked + // due to AML and not due to KYC. + kyc_url?: string; + + // AML status of the account. + aml_status: Integer; + + // Base URL of the exchange this is about. + exchange_url: string; + + // Our bank wire account this is about. + payto_uri: PaytoString; + + } + + interface ExchangeKycTimeout { + + // Base URL of the exchange this is about. + exchange_url: string; + + // Numeric error code indicating errors the exchange + // returned, or TALER_EC_INVALID for none. + exchange_code: number; + + // HTTP status code returned by the exchange when we asked for + // information about the KYC status. + // 0 if there was no response at all. + exchange_http_status: number; + + } + + interface AccountAddDetails { + + // payto:// URI of the account. + payto_uri: PaytoString; + + // URL from where the merchant can download information + // about incoming wire transfers to this account. + credit_facade_url?: string; + + // Credentials to use when accessing the credit facade. + // Never returned on a GET (as this may be somewhat + // sensitive data). Can be set in POST + // or PATCH requests to update (or delete) credentials. + // To really delete credentials, set them to the type: "none". + credit_facade_credentials?: FacadeCredentials; + + } + + type FacadeCredentials = + | NoFacadeCredentials + | BasicAuthFacadeCredentials; + interface NoFacadeCredentials { + type: "none"; + }; + interface BasicAuthFacadeCredentials { + type: "basic"; + + // Username to use to authenticate + username: string; + + // Password to use to authenticate + password: string; + }; + interface AccountAddResponse { + + // Hash over the wire details (including over the salt). + h_wire: HashCode; + + // Salt used to compute h_wire. + salt: HashCode; + + } + + interface AccountPatchDetails { + + // URL from where the merchant can download information + // about incoming wire transfers to this account. + credit_facade_url?: string; + + // Credentials to use when accessing the credit facade. + // Never returned on a GET (as this may be somewhat + // sensitive data). Can be set in POST + // or PATCH requests to update (or delete) credentials. + // To really delete credentials, set them to the type: "none". + // If the argument is omitted, the old credentials + // are simply preserved. + credit_facade_credentials?: FacadeCredentials; + } + + interface AccountsSummaryResponse { + + // List of accounts that are known for the instance. + accounts: BankAccountEntry[]; + } + interface BankAccountEntry { + + // payto:// URI of the account. + payto_uri: PaytoString; + + // Hash over the wire details (including over the salt). + h_wire: HashCode; + + // Salt used to compute h_wire. + salt: HashCode; + + // URL from where the merchant can download information + // about incoming wire transfers to this account. + credit_facade_url?: string; + + // true if this account is active, + // false if it is historic. + active: boolean; + } + + interface ProductAddDetail { + + // Product ID to use. + product_id: string; + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions. + description_i18n?: { [lang_tag: string]: string }; + + // Unit in which the product is measured (liters, kilograms, packages, etc.). + unit: string; + + // The price for one unit of the product. Zero is used + // to imply that this product is not sold separately, or + // that the price is not fixed, and must be supplied by the + // front-end. If non-zero, this price MUST include applicable + // taxes. + price: AmountString; + + // An optional base64-encoded product image. + image?: ImageDataUrl; + + // A list of taxes paid by the merchant for one unit of this product. + taxes?: Tax[]; + + // Number of units of the product in stock in sum in total, + // including all existing sales ever. Given in product-specific + // units. + // A value of -1 indicates "infinite" (i.e. for "electronic" books). + total_stock: Integer; + + // Identifies where the product is in stock. + address?: Location; + + // Identifies when we expect the next restocking to happen. + next_restock?: Timestamp; + + // Minimum age buyer must have (in years). Default is 0. + minimum_age?: Integer; + + } + + interface ProductPatchDetail { + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions. + description_i18n?: { [lang_tag: string]: string }; + + // Unit in which the product is measured (liters, kilograms, packages, etc.). + unit: string; + + // The price for one unit of the product. Zero is used + // to imply that this product is not sold separately, or + // that the price is not fixed, and must be supplied by the + // front-end. If non-zero, this price MUST include applicable + // taxes. + price: AmountString; + + // An optional base64-encoded product image. + image?: ImageDataUrl; + + // A list of taxes paid by the merchant for one unit of this product. + taxes?: Tax[]; + + // Number of units of the product in stock in sum in total, + // including all existing sales ever. Given in product-specific + // units. + // A value of -1 indicates "infinite" (i.e. for "electronic" books). + total_stock: Integer; + + // Number of units of the product that were lost (spoiled, stolen, etc.). + total_lost?: Integer; + + // Identifies where the product is in stock. + address?: Location; + + // Identifies when we expect the next restocking to happen. + next_restock?: Timestamp; + + // Minimum age buyer must have (in years). Default is 0. + minimum_age?: Integer; + + } + + interface InventorySummaryResponse { + // List of products that are present in the inventory. + products: InventoryEntry[]; + } + + + interface InventoryEntry { + // Product identifier, as found in the product. + product_id: string; + + } + + interface ProductDetail { + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions. + description_i18n: { [lang_tag: string]: string }; + + // Unit in which the product is measured (liters, kilograms, packages, etc.). + unit: string; + + // The price for one unit of the product. Zero is used + // to imply that this product is not sold separately, or + // that the price is not fixed, and must be supplied by the + // front-end. If non-zero, this price MUST include applicable + // taxes. + price: AmountString; + + // An optional base64-encoded product image. + image: ImageDataUrl; + + // A list of taxes paid by the merchant for one unit of this product. + taxes: Tax[]; + + // Number of units of the product in stock in sum in total, + // including all existing sales ever. Given in product-specific + // units. + // A value of -1 indicates "infinite" (i.e. for "electronic" books). + total_stock: Integer; + + // Number of units of the product that have already been sold. + total_sold: Integer; + + // Number of units of the product that were lost (spoiled, stolen, etc.). + total_lost: Integer; + + // Identifies where the product is in stock. + address: Location; + + // Identifies when we expect the next restocking to happen. + next_restock?: Timestamp; + + // Minimum age buyer must have (in years). + minimum_age?: Integer; + + } + interface LockRequest { + + // UUID that identifies the frontend performing the lock + // Must be unique for the lifetime of the lock. + lock_uuid: string; + + // How long does the frontend intend to hold the lock? + duration: RelativeTime; + + // How many units should be locked? + quantity: Integer; + + } + + interface PostOrderRequest { + // The order must at least contain the minimal + // order detail, but can override all. + order: Order; + + // If set, the backend will then set the refund deadline to the current + // time plus the specified delay. If it's not set, refunds will not be + // possible. + refund_delay?: RelativeTime; + + // Specifies the payment target preferred by the client. Can be used + // to select among the various (active) wire methods supported by the instance. + payment_target?: string; + + // Specifies that some products are to be included in the + // order from the inventory. For these inventory management + // is performed (so the products must be in stock) and + // details are completed from the product data of the backend. + inventory_products?: MinimalInventoryProduct[]; + + // Specifies a lock identifier that was used to + // lock a product in the inventory. Only useful if + // inventory_products is set. Used in case a frontend + // reserved quantities of the individual products while + // the shopping cart was being built. Multiple UUIDs can + // be used in case different UUIDs were used for different + // products (i.e. in case the user started with multiple + // shopping sessions that were combined during checkout). + lock_uuids: string[]; + + // Should a token for claiming the order be generated? + // False can make sense if the ORDER_ID is sufficiently + // high entropy to prevent adversarial claims (like it is + // if the backend auto-generates one). Default is 'true'. + create_token?: boolean; + + // OTP device ID to associate with the order. + // This parameter is optional. + otp_id?: string; + } + + type Order = MinimalOrderDetail | ContractTerms; + + interface MinimalOrderDetail { + // Amount to be paid by the customer. + amount: AmountString; + + // Short summary of the order. + summary: string; + + // See documentation of fulfillment_url in ContractTerms. + // Either fulfillment_url or fulfillment_message must be specified. + fulfillment_url?: string; + + // See documentation of fulfillment_message in ContractTerms. + // Either fulfillment_url or fulfillment_message must be specified. + fulfillment_message?: string; + } + + interface MinimalInventoryProduct { + // Which product is requested (here mandatory!). + product_id: string; + + // How many units of the product are requested. + quantity: Integer; + } + + interface PostOrderResponse { + // Order ID of the response that was just created. + order_id: string; + + // Token that authorizes the wallet to claim the order. + // Provided only if "create_token" was set to 'true' + // in the request. + token?: ClaimToken; + } + interface OutOfStockResponse { + + // Product ID of an out-of-stock item. + product_id: string; + + // Requested quantity. + requested_quantity: Integer; + + // Available quantity (must be below requested_quantity). + available_quantity: Integer; + + // When do we expect the product to be again in stock? + // Optional, not given if unknown. + restock_expected?: Timestamp; + } + + interface OrderHistory { + // Timestamp-sorted array of all orders matching the query. + // The order of the sorting depends on the sign of delta. + orders: OrderHistoryEntry[]; + } + interface OrderHistoryEntry { + + // Order ID of the transaction related to this entry. + order_id: string; + + // Row ID of the order in the database. + row_id: number; + + // When the order was created. + timestamp: Timestamp; + + // The amount of money the order is for. + amount: AmountString; + + // The summary of the order. + summary: string; + + // Whether some part of the order is refundable, + // that is the refund deadline has not yet expired + // and the total amount refunded so far is below + // the value of the original transaction. + refundable: boolean; + + // Whether the order has been paid or not. + paid: boolean; + } + + type MerchantOrderStatusResponse = CheckPaymentPaidResponse | + CheckPaymentClaimedResponse | + CheckPaymentUnpaidResponse; + interface CheckPaymentPaidResponse { + // The customer paid for this contract. + order_status: "paid"; + + // Was the payment refunded (even partially)? + refunded: boolean; + + // True if there are any approved refunds that the wallet has + // not yet obtained. + refund_pending: boolean; + + // Did the exchange wire us the funds? + wired: boolean; + + // Total amount the exchange deposited into our bank account + // for this contract, excluding fees. + deposit_total: AmountString; + + // Numeric error code indicating errors the exchange + // encountered tracking the wire transfer for this purchase (before + // we even got to specific coin issues). + // 0 if there were no issues. + exchange_code: number; + + // HTTP status code returned by the exchange when we asked for + // information to track the wire transfer for this purchase. + // 0 if there were no issues. + exchange_http_status: number; + + // Total amount that was refunded, 0 if refunded is false. + refund_amount: AmountString; + + // Contract terms. + contract_terms: ContractTerms; + + // The wire transfer status from the exchange for this order if + // available, otherwise empty array. + wire_details: TransactionWireTransfer[]; + + // Reports about trouble obtaining wire transfer details, + // empty array if no trouble were encountered. + wire_reports: TransactionWireReport[]; + + // The refund details for this order. One entry per + // refunded coin; empty array if there are no refunds. + refund_details: RefundDetails[]; + + // Status URL, can be used as a redirect target for the browser + // to show the order QR code / trigger the wallet. + order_status_url: string; + } + interface CheckPaymentClaimedResponse { + // A wallet claimed the order, but did not yet pay for the contract. + order_status: "claimed"; + + // Contract terms. + contract_terms: ContractTerms; + + } + interface CheckPaymentUnpaidResponse { + // The order was neither claimed nor paid. + order_status: "unpaid"; + + // URI that the wallet must process to complete the payment. + taler_pay_uri: string; + + // when was the order created + creation_time: Timestamp; + + // Order summary text. + summary: string; + + // Total amount of the order (to be paid by the customer). + total_amount: AmountString; + + // Alternative order ID which was paid for already in the same session. + // Only given if the same product was purchased before in the same session. + already_paid_order_id?: string; + + // Fulfillment URL of an already paid order. Only given if under this + // session an already paid order with a fulfillment URL exists. + already_paid_fulfillment_url?: string; + + // Status URL, can be used as a redirect target for the browser + // to show the order QR code / trigger the wallet. + order_status_url: string; + + // We do we NOT return the contract terms here because they may not + // exist in case the wallet did not yet claim them. + } + interface RefundDetails { + // Reason given for the refund. + reason: string; + + // Set to true if a refund is still available for the wallet for this payment. + pending: boolean; + + // When was the refund approved. + timestamp: Timestamp; + + // Total amount that was refunded (minus a refund fee). + amount: AmountString; + } + interface TransactionWireTransfer { + // Responsible exchange. + exchange_url: string; + + // 32-byte wire transfer identifier. + wtid: Base32; + + // Execution time of the wire transfer. + execution_time: Timestamp; + + // Total amount that has been wire transferred + // to the merchant. + amount: AmountString; + + // Was this transfer confirmed by the merchant via the + // POST /transfers API, or is it merely claimed by the exchange? + confirmed: boolean; + } + interface TransactionWireReport { + // Numerical error code. + code: number; + + // Human-readable error description. + hint: string; + + // Numerical error code from the exchange. + exchange_code: number; + + // HTTP status code received from the exchange. + exchange_http_status: number; + + // Public key of the coin for which we got the exchange error. + coin_pub: CoinPublicKey; + } + + interface ForgetRequest { + + // Array of valid JSON paths to forgettable fields in the order's + // contract terms. + fields: string[]; + } + + interface RefundRequest { + // Amount to be refunded. + refund: AmountString; + + // Human-readable refund justification. + reason: string; + } + interface MerchantRefundResponse { + + // URL (handled by the backend) that the wallet should access to + // trigger refund processing. + // taler://refund/... + taler_refund_uri: string; + + // Contract hash that a client may need to authenticate an + // HTTP request to obtain the above URI in a wallet-friendly way. + h_contract: HashCode; + } + + interface TransferInformation { + // How much was wired to the merchant (minus fees). + credit_amount: AmountString; + + // Raw wire transfer identifier identifying the wire transfer (a base32-encoded value). + wtid: WireTransferIdentifierRawP; + + // Target account that received the wire transfer. + payto_uri: PaytoString; + + // Base URL of the exchange that made the wire transfer. + exchange_url: string; + } + + interface TransferList { + // List of all the transfers that fit the filter that we know. + transfers: TransferDetails[]; + } + interface TransferDetails { + // How much was wired to the merchant (minus fees). + credit_amount: AmountString; + + // Raw wire transfer identifier identifying the wire transfer (a base32-encoded value). + wtid: WireTransferIdentifierRawP; + + // Target account that received the wire transfer. + payto_uri: PaytoString; + + // Base URL of the exchange that made the wire transfer. + exchange_url: string; + + // Serial number identifying the transfer in the merchant backend. + // Used for filtering via offset. + transfer_serial_id: number; + + // Time of the execution of the wire transfer by the exchange, according to the exchange + // Only provided if we did get an answer from the exchange. + execution_time?: Timestamp; + + // True if we checked the exchange's answer and are happy with it. + // False if we have an answer and are unhappy, missing if we + // do not have an answer from the exchange. + verified?: boolean; + + // True if the merchant uses the POST /transfers API to confirm + // that this wire transfer took place (and it is thus not + // something merely claimed by the exchange). + confirmed?: boolean; + } + + interface ReserveCreateRequest { + // Amount that the merchant promises to put into the reserve. + initial_balance: AmountString; + + // Exchange the merchant intends to use for rewards. + exchange_url: string; + + // Desired wire method, for example "iban" or "x-taler-bank". + wire_method: string; + } + interface ReserveCreateConfirmation { + // Public key identifying the reserve. + reserve_pub: EddsaPublicKey; + + // Wire accounts of the exchange where to transfer the funds. + accounts: TalerExchangeApi.WireAccount[]; + } + + interface RewardReserveStatus { + // Array of all known reserves (possibly empty!). + reserves: ReserveStatusEntry[]; + } + interface ReserveStatusEntry { + // Public key of the reserve. + reserve_pub: EddsaPublicKey; + + // Timestamp when it was established. + creation_time: Timestamp; + + // Timestamp when it expires. + expiration_time: Timestamp; + + // Initial amount as per reserve creation call. + merchant_initial_amount: AmountString; + + // Initial amount as per exchange, 0 if exchange did + // not confirm reserve creation yet. + exchange_initial_amount: AmountString; + + // Amount picked up so far. + pickup_amount: AmountString; + + // Amount approved for rewards that exceeds the pickup_amount. + committed_amount: AmountString; + + // Is this reserve active (false if it was deleted but not purged)? + active: boolean; + } + + interface ReserveDetail { + // Timestamp when it was established. + creation_time: Timestamp; + + // Timestamp when it expires. + expiration_time: Timestamp; + + // Initial amount as per reserve creation call. + merchant_initial_amount: AmountString; + + // Initial amount as per exchange, 0 if exchange did + // not confirm reserve creation yet. + exchange_initial_amount: AmountString; + + // Amount picked up so far. + pickup_amount: AmountString; + + // Amount approved for rewards that exceeds the pickup_amount. + committed_amount: AmountString; + + // Array of all rewards created by this reserves (possibly empty!). + // Only present if asked for explicitly. + rewards?: RewardStatusEntry[]; + + // Is this reserve active (false if it was deleted but not purged)? + active: boolean; + + // Array of wire accounts of the exchange that could + // be used to fill the reserve, can be NULL + // if the reserve is inactive or was already filled + accounts?: TalerExchangeApi.WireAccount[]; + + // URL of the exchange hosting the reserve, + // NULL if the reserve is inactive + exchange_url: string; + } + interface RewardStatusEntry { + + // Unique identifier for the reward. + reward_id: HashCode; + + // Total amount of the reward that can be withdrawn. + total_amount: AmountString; + + // Human-readable reason for why the reward was granted. + reason: string; + } + + interface RewardCreateRequest { + // Amount that the customer should be rewarded. + amount: AmountString; + + // Justification for giving the reward. + justification: string; + + // URL that the user should be directed to after receiving the reward, + // will be included in the reward_token. + next_url: string; + } + interface RewardCreateConfirmation { + // Unique reward identifier for the reward that was created. + reward_id: HashCode; + + // taler://reward URI for the reward. + taler_reward_uri: string; + + // URL that will directly trigger processing + // the reward when the browser is redirected to it. + reward_status_url: string; + + // When does the reward expire? + reward_expiration: Timestamp; + } + + interface RewardDetails { + // Amount that we authorized for this reward. + total_authorized: AmountString; + + // Amount that was picked up by the user already. + total_picked_up: AmountString; + + // Human-readable reason given when authorizing the reward. + reason: string; + + // Timestamp indicating when the reward is set to expire (may be in the past). + expiration: Timestamp; + + // Reserve public key from which the reward is funded. + reserve_pub: EddsaPublicKey; + + // Array showing the pickup operations of the wallet (possibly empty!). + // Only present if asked for explicitly. + pickups?: PickupDetail[]; + } + interface PickupDetail { + // Unique identifier for the pickup operation. + pickup_id: HashCode; + + // Number of planchets involved. + num_planchets: Integer; + + // Total amount requested for this pickup_id. + requested_amount: AmountString; + } + + interface RewardsResponse { + + // List of rewards that are present in the backend. + rewards: Reward[]; + } + interface Reward { + + // ID of the reward in the backend database. + row_id: number; + + // Unique identifier for the reward. + reward_id: HashCode; + + // (Remaining) amount of the reward (including fees). + reward_amount: AmountString; + } + + interface OtpDeviceAddDetails { + + // Device ID to use. + otp_device_id: string; + + // Human-readable description for the device. + otp_device_description: string; + + // A base64-encoded key + otp_key: string; + + // Algorithm for computing the POS confirmation. + otp_algorithm: Integer; + + // Counter for counter-based OTP devices. + otp_ctr?: Integer; + } + + interface OtpDevicePatchDetails { + + // Human-readable description for the device. + otp_device_description: string; + + // A base64-encoded key + otp_key: string; + + // Algorithm for computing the POS confirmation. + otp_algorithm: Integer; + + // Counter for counter-based OTP devices. + otp_ctr?: Integer; + } + + interface OtpDeviceSummaryResponse { + + // Array of devices that are present in our backend. + otp_devices: OtpDeviceEntry[]; + } + interface OtpDeviceEntry { + + // Device identifier. + otp_device_id: string; + + // Human-readable description for the device. + device_description: string; + } + + interface OtpDeviceDetails { + + // Human-readable description for the device. + device_description: string; + + // Algorithm for computing the POS confirmation. + otp_algorithm: Integer; + + // Counter for counter-based OTP devices. + otp_ctr?: Integer; + + } + interface TemplateAddDetails { + + // Template ID to use. + template_id: string; + + // Human-readable description for the template. + template_description: string; + + // OTP device ID. + // This parameter is optional. + otp_id?: string; + + // Additional information in a separate template. + template_contract: TemplateContractDetails; + } + interface TemplateContractDetails { + + // Human-readable summary for the template. + summary?: string; + + // Required currency for payments to the template. + // The user may specify any amount, but it must be + // in this currency. + // This parameter is optional and should not be present + // if "amount" is given. + currency?: 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: Integer; + + // 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: RelativeTime; + + } + interface TemplatePatchDetails { + + // Human-readable description for the template. + template_description: string; + + // OTP device ID. + // This parameter is optional. + otp_id?: string; + + // Additional information in a separate template. + template_contract: TemplateContractDetails; + + } + + interface TemplateSummaryResponse { + + // List of templates that are present in our backend. + templates_list: TemplateEntry[]; + } + + + interface TemplateEntry { + + // Template identifier, as found in the template. + template_id: string; + + // Human-readable description for the template. + template_description: string; + + } + interface TemplateDetails { + + // Human-readable description for the template. + template_description: string; + + // OTP device ID. + // This parameter is optional. + otp_id?: string; + + // Additional information in a separate template. + template_contract: TemplateContractDetails; + } + interface UsingTemplateDetails { + + // Summary of the template + summary?: string; + + // The amount entered by the customer. + amount?: AmountString; + } + + interface WebhookAddDetails { + + // Webhook ID to use. + webhook_id: string; + + // The event of the webhook: why the webhook is used. + event_type: string; + + // URL of the webhook where the customer will be redirected. + url: string; + + // Method used by the webhook + http_method: string; + + // Header template of the webhook + header_template?: string; + + // Body template by the webhook + body_template?: string; + + } + + interface WebhookPatchDetails { + + // The event of the webhook: why the webhook is used. + event_type: string; + + // URL of the webhook where the customer will be redirected. + url: string; + + // Method used by the webhook + http_method: string; + + // Header template of the webhook + header_template?: string; + + // Body template by the webhook + body_template?: string; + + } + + interface WebhookSummaryResponse { + + // Return webhooks that are present in our backend. + webhooks: WebhookEntry[]; + + } + + + interface WebhookEntry { + + // Webhook identifier, as found in the webhook. + webhook_id: string; + + // The event of the webhook: why the webhook is used. + event_type: string; + + } + + interface WebhookDetails { + + // The event of the webhook: why the webhook is used. + event_type: string; + + // URL of the webhook where the customer will be redirected. + url: string; + + // Method used by the webhook + http_method: string; + + // Header template of the webhook + header_template?: string; + + // Body template by the webhook + body_template?: string; + + } + + interface ContractTerms { + // Human-readable description of the whole purchase. + summary: string; + + // Map from IETF BCP 47 language tags to localized summaries. + summary_i18n?: { [lang_tag: string]: string }; + + // Unique, free-form identifier for the proposal. + // Must be unique within a merchant instance. + // For merchants that do not store proposals in their DB + // before the customer paid for them, the order_id can be used + // by the frontend to restore a proposal from the information + // encoded in it (such as a short product identifier and timestamp). + order_id: string; + + // Total price for the transaction. + // The exchange will subtract deposit fees from that amount + // before transferring it to the merchant. + amount: AmountString; + + // URL where the same contract could be ordered again (if + // available). Returned also at the public order endpoint + // for people other than the actual buyer (hence public, + // in case order IDs are guessable). + public_reorder_url?: string; + + // URL that will show that the order was successful after + // it has been paid for. Optional. When POSTing to the + // merchant, the placeholder "${ORDER_ID}" will be + // replaced with the actual order ID (useful if the + // order ID is generated server-side and needs to be + // in the URL). + // Note that this placeholder can only be used once. + // Either fulfillment_url or fulfillment_message must be specified. + fulfillment_url?: string; + + // Message shown to the customer after paying for the order. + // Either fulfillment_url or fulfillment_message must be specified. + fulfillment_message?: string; + + // Map from IETF BCP 47 language tags to localized fulfillment + // messages. + fulfillment_message_i18n?: { [lang_tag: string]: string }; + + // Maximum total deposit fee accepted by the merchant for this contract. + // Overrides defaults of the merchant instance. + max_fee: AmountString; + + // List of products that are part of the purchase (see Product). + products: Product[]; + + // Time when this contract was generated. + timestamp: Timestamp; + + // After this deadline has passed, no refunds will be accepted. + refund_deadline: Timestamp; + + // After this deadline, the merchant won't accept payments for the contract. + pay_deadline: Timestamp; + + // Transfer deadline for the exchange. Must be in the + // deposit permissions of coins used to pay for this order. + wire_transfer_deadline: Timestamp; + + // Merchant's public key used to sign this proposal; this information + // is typically added by the backend. Note that this can be an ephemeral key. + merchant_pub: EddsaPublicKey; + + // Base URL of the (public!) merchant backend API. + // Must be an absolute URL that ends with a slash. + merchant_base_url: string; + + // More info about the merchant, see below. + merchant: Merchant; + + // The hash of the merchant instance's wire details. + h_wire: HashCode; + + // Wire transfer method identifier for the wire method associated with h_wire. + // The wallet may only select exchanges via a matching auditor if the + // exchange also supports this wire method. + // The wire transfer fees must be added based on this wire transfer method. + wire_method: string; + + // Exchanges that the merchant accepts even if it does not accept any auditors that audit them. + exchanges: Exchange[]; + + // Delivery location for (all!) products. + delivery_location?: Location; + + // Time indicating when the order should be delivered. + // May be overwritten by individual products. + delivery_date?: Timestamp; + + // Nonce generated by the wallet and echoed by the merchant + // in this field when the proposal is generated. + nonce: string; + + // Specifies for how long the wallet should try to get an + // automatic refund for the purchase. If this field is + // present, the wallet should wait for a few seconds after + // the purchase and then automatically attempt to obtain + // a refund. The wallet should probe until "delay" + // after the payment was successful (i.e. via long polling + // or via explicit requests with exponential back-off). + // + // In particular, if the wallet is offline + // at that time, it MUST repeat the request until it gets + // one response from the merchant after the delay has expired. + // If the refund is granted, the wallet MUST automatically + // recover the payment. This is used in case a merchant + // knows that it might be unable to satisfy the contract and + // desires for the wallet to attempt to get the refund without any + // customer interaction. Note that it is NOT an error if the + // merchant does not grant a refund. + auto_refund?: RelativeTime; + + // Extra data that is only interpreted by the merchant frontend. + // Useful when the merchant needs to store extra information on a + // contract without storing it separately in their database. + extra?: any; + } + + interface Product { + // Merchant-internal identifier for the product. + product_id?: string; + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions. + description_i18n?: { [lang_tag: string]: string }; + + // The number of units of the product to deliver to the customer. + quantity?: Integer; + + // Unit in which the product is measured (liters, kilograms, packages, etc.). + unit?: string; + + // The price of the product; this is the total price for quantity times unit of this product. + price?: AmountString; + + // An optional base64-encoded product image. + image?: ImageDataUrl; + + // A list of taxes paid by the merchant for this product. Can be empty. + taxes?: Tax[]; + + // Time indicating when this product should be delivered. + delivery_date?: Timestamp; + } + + interface Tax { + // The name of the tax. + name: string; + + // Amount paid in tax. + tax: AmountString; + } + interface Merchant { + // The merchant's legal name of business. + name: string; + + // Label for a location with the business address of the merchant. + email?: string; + + // Label for a location with the business address of the merchant. + website?: string; + + // An optional base64-encoded product image. + logo?: ImageDataUrl; + + // Label for a location with the business address of the merchant. + address?: Location; + + // Label for a location that denotes the jurisdiction for disputes. + // Some of the typical fields for a location (such as a street address) may be absent. + jurisdiction?: Location; + } + // Delivery location, loosely modeled as a subset of + // ISO20022's PostalAddress25. + interface Location { + // Nation with its own government. + country?: string; + + // Identifies a subdivision of a country such as state, region, county. + country_subdivision?: string; + + // Identifies a subdivision within a country sub-division. + district?: string; + + // Name of a built-up area, with defined boundaries, and a local government. + town?: string; + + // Specific location name within the town. + town_location?: string; + + // Identifier consisting of a group of letters and/or numbers that + // is added to a postal address to assist the sorting of mail. + post_code?: string; + + // Name of a street or thoroughfare. + street?: string; + + // Name of the building or house. + building_name?: string; + + // Number that identifies the position of a building on a street. + building_number?: string; + + // Free-form address lines, should not exceed 7 elements. + address_lines?: string[]; + } + interface Auditor { + // Official name. + name: string; + + // Auditor's public key. + auditor_pub: EddsaPublicKey; + + // Base URL of the auditor. + url: string; + } + interface Exchange { + // The exchange's base URL. + url: string; + + // How much would the merchant like to use this exchange. + // The wallet should use a suitable exchange with high + // priority. The following priority values are used, but + // it should be noted that they are NOT in any way normative. + // + // 0: likely it will not work (recently seen with account + // restriction that would be bad for this merchant) + // 512: merchant does not know, might be down (merchant + // did not yet get /wire response). + // 1024: good choice (recently confirmed working) + priority: Integer; + + // Master public key of the exchange. + master_pub: EddsaPublicKey; + } + +}
\ No newline at end of file diff --git a/packages/taler-util/src/operation.ts b/packages/taler-util/src/operation.ts index 9d7594d14..06bfe26bd 100644 --- a/packages/taler-util/src/operation.ts +++ b/packages/taler-util/src/operation.ts @@ -90,6 +90,6 @@ type AllKnownCases<t extends object, d extends keyof t> = "success" | FailCasesB export type TestForApi<ApiType extends object> = { [OpType in MethodsOfOperations<ApiType> as `test_${OpType & string}`]: { - [c in AllKnownCases<ApiType, OpType>]: undefined | ((api: ApiType) => Promise<void>); + [c in AllKnownCases<ApiType, OpType>]: undefined | (() => Promise<void>); }; }; |