aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-11-05 00:53:53 -0300
committerSebastian <sebasjm@gmail.com>2023-11-05 00:53:53 -0300
commit31cf3187e447e2c4ec8a473362c5bacc07a874f1 (patch)
tree74e528b91eb313cf4d991410afda6ce905a218d6
parent78240f6c0f600f905f29e6dceb75286461297f24 (diff)
downloadwallet-core-31cf3187e447e2c4ec8a473362c5bacc07a874f1.tar.xz
exchange and merchant api
-rw-r--r--packages/taler-harness/src/http-client/bank-core.ts244
-rw-r--r--packages/taler-harness/src/index.ts79
-rw-r--r--packages/taler-util/src/http-client/bank-integration.ts8
-rw-r--r--packages/taler-util/src/http-client/bank-revenue.ts9
-rw-r--r--packages/taler-util/src/http-client/bank-wire.ts7
-rw-r--r--packages/taler-util/src/http-client/exchange.ts37
-rw-r--r--packages/taler-util/src/http-client/merchant.ts37
-rw-r--r--packages/taler-util/src/http-client/types.ts1983
-rw-r--r--packages/taler-util/src/operation.ts2
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>);
};
};