wallet: tipping protocol change / merchant version info
This commit is contained in:
parent
829a59e1a2
commit
ae8af3f27c
@ -1102,6 +1102,8 @@ export interface BackupExchange {
|
|||||||
|
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
|
protocol_version_range: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time when the pointer to the exchange details
|
* Time when the pointer to the exchange details
|
||||||
* was last updated.
|
* was last updated.
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as LibtoolVersion from "./libtool-version.js";
|
import { LibtoolVersion } from "./libtool-version.js";
|
||||||
|
|
||||||
import test from "ava";
|
import test from "ava";
|
||||||
|
|
||||||
|
@ -40,49 +40,51 @@ interface Version {
|
|||||||
age: number;
|
age: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export namespace LibtoolVersion {
|
||||||
* Compare two libtool-style version strings.
|
/**
|
||||||
*/
|
* Compare two libtool-style version strings.
|
||||||
export function compare(
|
*/
|
||||||
me: string,
|
export function compare(
|
||||||
other: string,
|
me: string,
|
||||||
): VersionMatchResult | undefined {
|
other: string,
|
||||||
const meVer = parseVersion(me);
|
): VersionMatchResult | undefined {
|
||||||
const otherVer = parseVersion(other);
|
const meVer = parseVersion(me);
|
||||||
|
const otherVer = parseVersion(other);
|
||||||
|
|
||||||
if (!(meVer && otherVer)) {
|
if (!(meVer && otherVer)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const compatible =
|
||||||
|
meVer.current - meVer.age <= otherVer.current &&
|
||||||
|
meVer.current >= otherVer.current - otherVer.age;
|
||||||
|
|
||||||
|
const currentCmp = Math.sign(meVer.current - otherVer.current);
|
||||||
|
|
||||||
|
return { compatible, currentCmp };
|
||||||
}
|
}
|
||||||
|
|
||||||
const compatible =
|
function parseVersion(v: string): Version | undefined {
|
||||||
meVer.current - meVer.age <= otherVer.current &&
|
const [currentStr, revisionStr, ageStr, ...rest] = v.split(":");
|
||||||
meVer.current >= otherVer.current - otherVer.age;
|
if (rest.length !== 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const current = Number.parseInt(currentStr);
|
||||||
|
const revision = Number.parseInt(revisionStr);
|
||||||
|
const age = Number.parseInt(ageStr);
|
||||||
|
|
||||||
const currentCmp = Math.sign(meVer.current - otherVer.current);
|
if (Number.isNaN(current)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return { compatible, currentCmp };
|
if (Number.isNaN(revision)) {
|
||||||
}
|
return undefined;
|
||||||
|
}
|
||||||
function parseVersion(v: string): Version | undefined {
|
|
||||||
const [currentStr, revisionStr, ageStr, ...rest] = v.split(":");
|
if (Number.isNaN(age)) {
|
||||||
if (rest.length !== 0) {
|
return undefined;
|
||||||
return undefined;
|
}
|
||||||
}
|
|
||||||
const current = Number.parseInt(currentStr);
|
return { current, revision, age };
|
||||||
const revision = Number.parseInt(revisionStr);
|
}
|
||||||
const age = Number.parseInt(ageStr);
|
|
||||||
|
|
||||||
if (Number.isNaN(current)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Number.isNaN(revision)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Number.isNaN(age)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { current, revision, age };
|
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
import * as nacl from "./nacl-fast.js";
|
import * as nacl from "./nacl-fast.js";
|
||||||
import { kdf } from "./kdf.js";
|
import { kdf } from "./kdf.js";
|
||||||
import bigint from "big-integer";
|
import bigint from "big-integer";
|
||||||
import { DenominationPubKey } from "./talerTypes.js";
|
import { DenominationPubKey, DenomKeyType } from "./talerTypes.js";
|
||||||
|
|
||||||
export function getRandomBytes(n: number): Uint8Array {
|
export function getRandomBytes(n: number): Uint8Array {
|
||||||
return nacl.randomBytes(n);
|
return nacl.randomBytes(n);
|
||||||
@ -350,7 +350,7 @@ export function hash(d: Uint8Array): Uint8Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function hashDenomPub(pub: DenominationPubKey): Uint8Array {
|
export function hashDenomPub(pub: DenominationPubKey): Uint8Array {
|
||||||
if (pub.cipher !== 1) {
|
if (pub.cipher !== DenomKeyType.Rsa) {
|
||||||
throw Error("unsupported cipher");
|
throw Error("unsupported cipher");
|
||||||
}
|
}
|
||||||
const pubBuf = decodeCrock(pub.rsa_public_key);
|
const pubBuf = decodeCrock(pub.rsa_public_key);
|
||||||
|
@ -598,9 +598,9 @@ export interface TipPickupRequest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reserve signature, defined as separate class to facilitate
|
* Reserve signature, defined as separate class to facilitate
|
||||||
* schema validation with "@Checkable".
|
* schema validation.
|
||||||
*/
|
*/
|
||||||
export interface BlindSigWrapper {
|
export interface MerchantBlindSigWrapperV1 {
|
||||||
/**
|
/**
|
||||||
* Reserve signature.
|
* Reserve signature.
|
||||||
*/
|
*/
|
||||||
@ -611,11 +611,26 @@ export interface BlindSigWrapper {
|
|||||||
* Response of the merchant
|
* Response of the merchant
|
||||||
* to the TipPickupRequest.
|
* to the TipPickupRequest.
|
||||||
*/
|
*/
|
||||||
export interface TipResponse {
|
export interface MerchantTipResponseV1 {
|
||||||
/**
|
/**
|
||||||
* The order of the signatures matches the planchets list.
|
* The order of the signatures matches the planchets list.
|
||||||
*/
|
*/
|
||||||
blind_sigs: BlindSigWrapper[];
|
blind_sigs: MerchantBlindSigWrapperV1[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MerchantBlindSigWrapperV2 {
|
||||||
|
blind_sig: BlindedDenominationSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response of the merchant
|
||||||
|
* to the TipPickupRequest.
|
||||||
|
*/
|
||||||
|
export interface MerchantTipResponseV2 {
|
||||||
|
/**
|
||||||
|
* The order of the signatures matches the planchets list.
|
||||||
|
*/
|
||||||
|
blind_sigs: MerchantBlindSigWrapperV2[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1032,13 +1047,14 @@ export interface BankWithdrawalOperationPostResponse {
|
|||||||
export type DenominationPubKey = RsaDenominationPubKey | CsDenominationPubKey;
|
export type DenominationPubKey = RsaDenominationPubKey | CsDenominationPubKey;
|
||||||
|
|
||||||
export interface RsaDenominationPubKey {
|
export interface RsaDenominationPubKey {
|
||||||
cipher: 1;
|
cipher: DenomKeyType.Rsa;
|
||||||
rsa_public_key: string;
|
rsa_public_key: string;
|
||||||
age_mask?: number;
|
age_mask?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CsDenominationPubKey {
|
export interface CsDenominationPubKey {
|
||||||
cipher: 2;
|
cipher: DenomKeyType.ClauseSchnorr;
|
||||||
|
// FIXME: finish definition
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForDenominationPubKey = () =>
|
export const codecForDenominationPubKey = () =>
|
||||||
@ -1201,15 +1217,25 @@ export const codecForMerchantRefundResponse = (): Codec<MerchantRefundResponse>
|
|||||||
.property("refunds", codecForList(codecForMerchantRefundPermission()))
|
.property("refunds", codecForList(codecForMerchantRefundPermission()))
|
||||||
.build("MerchantRefundResponse");
|
.build("MerchantRefundResponse");
|
||||||
|
|
||||||
export const codecForBlindSigWrapper = (): Codec<BlindSigWrapper> =>
|
export const codecForMerchantBlindSigWrapperV1 = (): Codec<MerchantBlindSigWrapperV1> =>
|
||||||
buildCodecForObject<BlindSigWrapper>()
|
buildCodecForObject<MerchantBlindSigWrapperV1>()
|
||||||
.property("blind_sig", codecForString())
|
.property("blind_sig", codecForString())
|
||||||
.build("BlindSigWrapper");
|
.build("BlindSigWrapper");
|
||||||
|
|
||||||
export const codecForTipResponse = (): Codec<TipResponse> =>
|
export const codecForMerchantTipResponseV1 = (): Codec<MerchantTipResponseV1> =>
|
||||||
buildCodecForObject<TipResponse>()
|
buildCodecForObject<MerchantTipResponseV1>()
|
||||||
.property("blind_sigs", codecForList(codecForBlindSigWrapper()))
|
.property("blind_sigs", codecForList(codecForMerchantBlindSigWrapperV1()))
|
||||||
.build("TipResponse");
|
.build("MerchantTipResponseV1");
|
||||||
|
|
||||||
|
export const codecForBlindSigWrapperV2 = (): Codec<MerchantBlindSigWrapperV2> =>
|
||||||
|
buildCodecForObject<MerchantBlindSigWrapperV2>()
|
||||||
|
.property("blind_sig", codecForBlindedDenominationSignature())
|
||||||
|
.build("MerchantBlindSigWrapperV2");
|
||||||
|
|
||||||
|
export const codecForMerchantTipResponseV2 = (): Codec<MerchantTipResponseV2> =>
|
||||||
|
buildCodecForObject<MerchantTipResponseV2>()
|
||||||
|
.property("blind_sigs", codecForList(codecForBlindSigWrapperV2()))
|
||||||
|
.build("MerchantTipResponseV2");
|
||||||
|
|
||||||
export const codecForRecoup = (): Codec<Recoup> =>
|
export const codecForRecoup = (): Codec<Recoup> =>
|
||||||
buildCodecForObject<Recoup>()
|
buildCodecForObject<Recoup>()
|
||||||
@ -1510,3 +1536,16 @@ export const codecForKeysManagementResponse = (): Codec<FutureKeysResponse> =>
|
|||||||
.property("denom_secmod_public_key", codecForAny())
|
.property("denom_secmod_public_key", codecForAny())
|
||||||
.property("signkey_secmod_public_key", codecForAny())
|
.property("signkey_secmod_public_key", codecForAny())
|
||||||
.build("FutureKeysResponse");
|
.build("FutureKeysResponse");
|
||||||
|
|
||||||
|
export interface MerchantConfigResponse {
|
||||||
|
currency: string;
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const codecForMerchantConfigResponse = (): Codec<MerchantConfigResponse> =>
|
||||||
|
buildCodecForObject<MerchantConfigResponse>()
|
||||||
|
.property("currency", codecForString())
|
||||||
|
.property("name", codecForString())
|
||||||
|
.property("version", codecForString())
|
||||||
|
.build("MerchantConfigResponse");
|
||||||
|
@ -66,7 +66,7 @@ import {
|
|||||||
encodeCrock,
|
encodeCrock,
|
||||||
getRandomBytes,
|
getRandomBytes,
|
||||||
hash,
|
hash,
|
||||||
stringToBytes
|
stringToBytes,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { CoinConfig } from "./denomStructures.js";
|
import { CoinConfig } from "./denomStructures.js";
|
||||||
import { LibeufinNexusApi, LibeufinSandboxApi } from "./libeufin-apis.js";
|
import { LibeufinNexusApi, LibeufinSandboxApi } from "./libeufin-apis.js";
|
||||||
@ -445,7 +445,7 @@ export async function pingProc(
|
|||||||
}
|
}
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
console.log(`pinging ${serviceName}`);
|
console.log(`pinging ${serviceName} at ${url}`);
|
||||||
const resp = await axios.get(url);
|
const resp = await axios.get(url);
|
||||||
console.log(`service ${serviceName} available`);
|
console.log(`service ${serviceName} available`);
|
||||||
return;
|
return;
|
||||||
@ -556,7 +556,6 @@ export namespace BankApi {
|
|||||||
debitAccountPayto: string;
|
debitAccountPayto: string;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|
||||||
let maybeBaseUrl = bank.baseUrl;
|
let maybeBaseUrl = bank.baseUrl;
|
||||||
if (process.env.WALLET_HARNESS_WITH_EUFIN) {
|
if (process.env.WALLET_HARNESS_WITH_EUFIN) {
|
||||||
maybeBaseUrl = (bank as EufinBankService).baseUrlDemobank;
|
maybeBaseUrl = (bank as EufinBankService).baseUrlDemobank;
|
||||||
@ -618,7 +617,6 @@ export namespace BankApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class BankServiceBase {
|
class BankServiceBase {
|
||||||
proc: ProcessWrapper | undefined;
|
proc: ProcessWrapper | undefined;
|
||||||
|
|
||||||
@ -641,7 +639,6 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
gc: GlobalTestState,
|
gc: GlobalTestState,
|
||||||
bc: BankConfig,
|
bc: BankConfig,
|
||||||
): Promise<EufinBankService> {
|
): Promise<EufinBankService> {
|
||||||
|
|
||||||
return new EufinBankService(gc, bc, "foo");
|
return new EufinBankService(gc, bc, "foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,16 +647,14 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
}
|
}
|
||||||
get nexusPort() {
|
get nexusPort() {
|
||||||
return this.bankConfig.httpPort + 1000;
|
return this.bankConfig.httpPort + 1000;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get nexusDbConn(): string {
|
get nexusDbConn(): string {
|
||||||
return `jdbc:sqlite:${this.globalTestState.testDir}/libeufin-nexus.sqlite3`;
|
return `jdbc:sqlite:${this.globalTestState.testDir}/libeufin-nexus.sqlite3`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get sandboxDbConn(): string {
|
get sandboxDbConn(): string {
|
||||||
return `jdbc:sqlite:${this.globalTestState.testDir}/libeufin-sandbox.sqlite3`;
|
return `jdbc:sqlite:${this.globalTestState.testDir}/libeufin-sandbox.sqlite3`;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get nexusBaseUrl(): string {
|
get nexusBaseUrl(): string {
|
||||||
@ -673,9 +668,9 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
|
|
||||||
get baseUrlAccessApi(): string {
|
get baseUrlAccessApi(): string {
|
||||||
let url = new URL("access-api/", this.baseUrlDemobank);
|
let url = new URL("access-api/", this.baseUrlDemobank);
|
||||||
return url.href;
|
return url.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
get baseUrlNetloc(): string {
|
get baseUrlNetloc(): string {
|
||||||
return `http://localhost:${this.bankConfig.httpPort}/`;
|
return `http://localhost:${this.bankConfig.httpPort}/`;
|
||||||
}
|
}
|
||||||
@ -686,7 +681,7 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
|
|
||||||
async setSuggestedExchange(
|
async setSuggestedExchange(
|
||||||
e: ExchangeServiceInterface,
|
e: ExchangeServiceInterface,
|
||||||
exchangePayto: string
|
exchangePayto: string,
|
||||||
) {
|
) {
|
||||||
await sh(
|
await sh(
|
||||||
this.globalTestState,
|
this.globalTestState,
|
||||||
@ -712,11 +707,9 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
*/
|
*/
|
||||||
await this.start();
|
await this.start();
|
||||||
await this.pingUntilAvailable();
|
await this.pingUntilAvailable();
|
||||||
await LibeufinSandboxApi.createDemobankAccount(
|
await LibeufinSandboxApi.createDemobankAccount(accountName, password, {
|
||||||
accountName,
|
baseUrl: this.baseUrlAccessApi,
|
||||||
password,
|
});
|
||||||
{ baseUrl: this.baseUrlAccessApi }
|
|
||||||
);
|
|
||||||
let bankAccountLabel = accountName;
|
let bankAccountLabel = accountName;
|
||||||
await LibeufinSandboxApi.createDemobankEbicsSubscriber(
|
await LibeufinSandboxApi.createDemobankEbicsSubscriber(
|
||||||
{
|
{
|
||||||
@ -725,47 +718,49 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
partnerID: "exchangeEbicsPartner",
|
partnerID: "exchangeEbicsPartner",
|
||||||
},
|
},
|
||||||
bankAccountLabel,
|
bankAccountLabel,
|
||||||
{ baseUrl: this.baseUrlDemobank }
|
{ baseUrl: this.baseUrlDemobank },
|
||||||
);
|
);
|
||||||
|
|
||||||
await LibeufinNexusApi.createUser(
|
await LibeufinNexusApi.createUser(
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
{ baseUrl: this.nexusBaseUrl },
|
||||||
{
|
{
|
||||||
username: accountName,
|
username: accountName,
|
||||||
password: password
|
password: password,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
await LibeufinNexusApi.createEbicsBankConnection(
|
await LibeufinNexusApi.createEbicsBankConnection(
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
{ baseUrl: this.nexusBaseUrl },
|
||||||
{
|
{
|
||||||
name: "ebics-connection", // connection name.
|
name: "ebics-connection", // connection name.
|
||||||
ebicsURL: (new URL("ebicsweb", this.baseUrlNetloc)).href,
|
ebicsURL: new URL("ebicsweb", this.baseUrlNetloc).href,
|
||||||
hostID: "talertestEbicsHost",
|
hostID: "talertestEbicsHost",
|
||||||
userID: "exchangeEbicsUser",
|
userID: "exchangeEbicsUser",
|
||||||
partnerID: "exchangeEbicsPartner",
|
partnerID: "exchangeEbicsPartner",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
await LibeufinNexusApi.connectBankConnection(
|
await LibeufinNexusApi.connectBankConnection(
|
||||||
{ baseUrl: this.nexusBaseUrl }, "ebics-connection"
|
{ baseUrl: this.nexusBaseUrl },
|
||||||
|
"ebics-connection",
|
||||||
);
|
);
|
||||||
await LibeufinNexusApi.fetchAccounts(
|
await LibeufinNexusApi.fetchAccounts(
|
||||||
{ baseUrl: this.nexusBaseUrl }, "ebics-connection"
|
{ baseUrl: this.nexusBaseUrl },
|
||||||
|
"ebics-connection",
|
||||||
);
|
);
|
||||||
await LibeufinNexusApi.importConnectionAccount(
|
await LibeufinNexusApi.importConnectionAccount(
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
{ baseUrl: this.nexusBaseUrl },
|
||||||
"ebics-connection", // connection name
|
"ebics-connection", // connection name
|
||||||
accountName, // offered account label
|
accountName, // offered account label
|
||||||
`${accountName}-nexus-label` // bank account label at Nexus
|
`${accountName}-nexus-label`, // bank account label at Nexus
|
||||||
);
|
);
|
||||||
await LibeufinNexusApi.createTwgFacade(
|
await LibeufinNexusApi.createTwgFacade(
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
{ baseUrl: this.nexusBaseUrl },
|
||||||
{
|
{
|
||||||
name: "exchange-facade",
|
name: "exchange-facade",
|
||||||
connectionName: "ebics-connection",
|
connectionName: "ebics-connection",
|
||||||
accountName: `${accountName}-nexus-label`,
|
accountName: `${accountName}-nexus-label`,
|
||||||
currency: "EUR",
|
currency: "EUR",
|
||||||
reserveTransferLevel: "report"
|
reserveTransferLevel: "report",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
await LibeufinNexusApi.postPermission(
|
await LibeufinNexusApi.postPermission(
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
{ baseUrl: this.nexusBaseUrl },
|
||||||
@ -778,7 +773,7 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
resourceId: "exchange-facade", // facade name
|
resourceId: "exchange-facade", // facade name
|
||||||
permissionName: "facade.talerWireGateway.transfer",
|
permissionName: "facade.talerWireGateway.transfer",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
await LibeufinNexusApi.postPermission(
|
await LibeufinNexusApi.postPermission(
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
{ baseUrl: this.nexusBaseUrl },
|
||||||
@ -791,7 +786,7 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
resourceId: "exchange-facade", // facade name
|
resourceId: "exchange-facade", // facade name
|
||||||
permissionName: "facade.talerWireGateway.history",
|
permissionName: "facade.talerWireGateway.history",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
// Set fetch task.
|
// Set fetch task.
|
||||||
await LibeufinNexusApi.postTask(
|
await LibeufinNexusApi.postTask(
|
||||||
@ -804,8 +799,9 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
params: {
|
params: {
|
||||||
level: "all",
|
level: "all",
|
||||||
rangeType: "all",
|
rangeType: "all",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
await LibeufinNexusApi.postTask(
|
await LibeufinNexusApi.postTask(
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
{ baseUrl: this.nexusBaseUrl },
|
||||||
`${accountName}-nexus-label`,
|
`${accountName}-nexus-label`,
|
||||||
@ -814,14 +810,16 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
cronspec: "* * *",
|
cronspec: "* * *",
|
||||||
type: "submit",
|
type: "submit",
|
||||||
params: {},
|
params: {},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
let facadesResp = await LibeufinNexusApi.getAllFacades({ baseUrl: this.nexusBaseUrl });
|
let facadesResp = await LibeufinNexusApi.getAllFacades({
|
||||||
|
baseUrl: this.nexusBaseUrl,
|
||||||
|
});
|
||||||
let accountInfoResp = await LibeufinSandboxApi.demobankAccountInfo(
|
let accountInfoResp = await LibeufinSandboxApi.demobankAccountInfo(
|
||||||
"admin",
|
"admin",
|
||||||
"secret",
|
"secret",
|
||||||
{ baseUrl: this.baseUrlAccessApi },
|
{ baseUrl: this.baseUrlAccessApi },
|
||||||
accountName // bank account label.
|
accountName, // bank account label.
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
accountName: accountName,
|
accountName: accountName,
|
||||||
@ -840,7 +838,7 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
* them if they weren't launched earlier.
|
* them if they weren't launched earlier.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Only go ahead if BOTH aren't running.
|
// Only go ahead if BOTH aren't running.
|
||||||
if (this.sandboxProc || this.nexusProc) {
|
if (this.sandboxProc || this.nexusProc) {
|
||||||
console.log("Nexus or Sandbox already running, not taking any action.");
|
console.log("Nexus or Sandbox already running, not taking any action.");
|
||||||
return;
|
return;
|
||||||
@ -864,7 +862,7 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxDbConn,
|
LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxDbConn,
|
||||||
LIBEUFIN_SANDBOX_ADMIN_PASSWORD: "secret",
|
LIBEUFIN_SANDBOX_ADMIN_PASSWORD: "secret",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await runCommand(
|
await runCommand(
|
||||||
this.globalTestState,
|
this.globalTestState,
|
||||||
"libeufin-nexus-superuser",
|
"libeufin-nexus-superuser",
|
||||||
@ -889,7 +887,7 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
await this.pingUntilAvailable();
|
await this.pingUntilAvailable();
|
||||||
LibeufinSandboxApi.createEbicsHost(
|
LibeufinSandboxApi.createEbicsHost(
|
||||||
{ baseUrl: this.baseUrlNetloc },
|
{ baseUrl: this.baseUrlNetloc },
|
||||||
"talertestEbicsHost"
|
"talertestEbicsHost",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -897,12 +895,12 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
await pingProc(
|
await pingProc(
|
||||||
this.sandboxProc,
|
this.sandboxProc,
|
||||||
`http://localhost:${this.bankConfig.httpPort}`,
|
`http://localhost:${this.bankConfig.httpPort}`,
|
||||||
"libeufin-sandbox"
|
"libeufin-sandbox",
|
||||||
);
|
);
|
||||||
await pingProc(
|
await pingProc(
|
||||||
this.nexusProc,
|
this.nexusProc,
|
||||||
`${this.nexusBaseUrl}/config`,
|
`${this.nexusBaseUrl}/config`,
|
||||||
"libeufin-nexus"
|
"libeufin-nexus",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -999,7 +997,6 @@ class PybankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a euFin or a pyBank implementation of
|
* Return a euFin or a pyBank implementation of
|
||||||
* the exported BankService class. This allows
|
* the exported BankService class. This allows
|
||||||
@ -1007,19 +1004,18 @@ class PybankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
* on a particular env variable.
|
* on a particular env variable.
|
||||||
*/
|
*/
|
||||||
function getBankServiceImpl(): {
|
function getBankServiceImpl(): {
|
||||||
prototype: typeof PybankService.prototype,
|
prototype: typeof PybankService.prototype;
|
||||||
create: typeof PybankService.create
|
create: typeof PybankService.create;
|
||||||
} {
|
} {
|
||||||
|
if (process.env.WALLET_HARNESS_WITH_EUFIN)
|
||||||
if (process.env.WALLET_HARNESS_WITH_EUFIN)
|
|
||||||
return {
|
return {
|
||||||
prototype: EufinBankService.prototype,
|
prototype: EufinBankService.prototype,
|
||||||
create: EufinBankService.create
|
create: EufinBankService.create,
|
||||||
}
|
};
|
||||||
return {
|
return {
|
||||||
prototype: PybankService.prototype,
|
prototype: PybankService.prototype,
|
||||||
create: PybankService.create
|
create: PybankService.create,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BankService = PybankService;
|
export type BankService = PybankService;
|
||||||
@ -2088,10 +2084,8 @@ export class WalletCli {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getRandomIban(salt: string | null = null): string {
|
export function getRandomIban(salt: string | null = null): string {
|
||||||
|
|
||||||
function getBban(salt: string | null): string {
|
function getBban(salt: string | null): string {
|
||||||
if (!salt)
|
if (!salt) return Math.random().toString().substring(2, 6);
|
||||||
return Math.random().toString().substring(2, 6);
|
|
||||||
let hashed = hash(stringToBytes(salt));
|
let hashed = hash(stringToBytes(salt));
|
||||||
let ret = "";
|
let ret = "";
|
||||||
for (let i = 0; i < hashed.length; i++) {
|
for (let i = 0; i < hashed.length; i++) {
|
||||||
@ -2101,19 +2095,21 @@ export function getRandomIban(salt: string | null = null): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let cc_no_check = "131400"; // == DE00
|
let cc_no_check = "131400"; // == DE00
|
||||||
let bban = getBban(salt)
|
let bban = getBban(salt);
|
||||||
let check_digits = (98 - (Number.parseInt(`${bban}${cc_no_check}`) % 97)).toString();
|
let check_digits = (
|
||||||
|
98 -
|
||||||
|
(Number.parseInt(`${bban}${cc_no_check}`) % 97)
|
||||||
|
).toString();
|
||||||
if (check_digits.length == 1) {
|
if (check_digits.length == 1) {
|
||||||
check_digits = `0${check_digits}`;
|
check_digits = `0${check_digits}`;
|
||||||
}
|
}
|
||||||
return `DE${check_digits}${bban}`;
|
return `DE${check_digits}${bban}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only used in one tipping test.
|
// Only used in one tipping test.
|
||||||
export function getWireMethod(): string {
|
export function getWireMethod(): string {
|
||||||
if (process.env.WALLET_HARNESS_WITH_EUFIN)
|
if (process.env.WALLET_HARNESS_WITH_EUFIN) return "iban";
|
||||||
return "iban"
|
return "x-taler-bank";
|
||||||
return "x-taler-bank"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2122,10 +2118,12 @@ export function getWireMethod(): string {
|
|||||||
*/
|
*/
|
||||||
export function getPayto(label: string): string {
|
export function getPayto(label: string): string {
|
||||||
if (process.env.WALLET_HARNESS_WITH_EUFIN)
|
if (process.env.WALLET_HARNESS_WITH_EUFIN)
|
||||||
return `payto://iban/SANDBOXX/${getRandomIban(label)}?receiver-name=${label}`
|
return `payto://iban/SANDBOXX/${getRandomIban(
|
||||||
return `payto://x-taler-bank/${label}`
|
label,
|
||||||
|
)}?receiver-name=${label}`;
|
||||||
|
return `payto://x-taler-bank/${label}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function waitMs(ms: number): Promise<void> {
|
function waitMs(ms: number): Promise<void> {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,21 @@ export interface TrustInfo {
|
|||||||
isAudited: boolean;
|
isAudited: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MerchantInfo {
|
||||||
|
supportsMerchantProtocolV1: boolean;
|
||||||
|
supportsMerchantProtocolV2: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for merchant-related operations.
|
||||||
|
*/
|
||||||
|
export interface MerchantOperations {
|
||||||
|
getMerchantInfo(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
merchantBaseUrl: string,
|
||||||
|
): Promise<MerchantInfo>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for exchange-related operations.
|
* Interface for exchange-related operations.
|
||||||
*/
|
*/
|
||||||
@ -131,8 +146,11 @@ export interface InternalWalletState {
|
|||||||
|
|
||||||
initCalled: boolean;
|
initCalled: boolean;
|
||||||
|
|
||||||
|
merchantInfoCache: Record<string, MerchantInfo>;
|
||||||
|
|
||||||
exchangeOps: ExchangeOperations;
|
exchangeOps: ExchangeOperations;
|
||||||
recoupOps: RecoupOperations;
|
recoupOps: RecoupOperations;
|
||||||
|
merchantOps: MerchantOperations;
|
||||||
|
|
||||||
db: DbAccess<typeof WalletStoresV1>;
|
db: DbAccess<typeof WalletStoresV1>;
|
||||||
http: HttpRequestLibrary;
|
http: HttpRequestLibrary;
|
||||||
|
@ -484,8 +484,14 @@ export interface WireInfo {
|
|||||||
|
|
||||||
export interface ExchangeDetailsPointer {
|
export interface ExchangeDetailsPointer {
|
||||||
masterPublicKey: string;
|
masterPublicKey: string;
|
||||||
|
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last observed protocol version range offered by the exchange.
|
||||||
|
*/
|
||||||
|
protocolVersionRange: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamp when the (masterPublicKey, currency) pointer
|
* Timestamp when the (masterPublicKey, currency) pointer
|
||||||
* has been updated.
|
* has been updated.
|
||||||
|
@ -273,6 +273,7 @@ export async function exportBackup(
|
|||||||
currency: dp.currency,
|
currency: dp.currency,
|
||||||
master_public_key: dp.masterPublicKey,
|
master_public_key: dp.masterPublicKey,
|
||||||
update_clock: dp.updateClock,
|
update_clock: dp.updateClock,
|
||||||
|
protocol_version_range: dp.protocolVersionRange,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -267,6 +267,7 @@ export async function importBackup(
|
|||||||
currency: backupExchange.currency,
|
currency: backupExchange.currency,
|
||||||
masterPublicKey: backupExchange.master_public_key,
|
masterPublicKey: backupExchange.master_public_key,
|
||||||
updateClock: backupExchange.update_clock,
|
updateClock: backupExchange.update_clock,
|
||||||
|
protocolVersionRange: backupExchange.protocol_version_range,
|
||||||
},
|
},
|
||||||
permanent: true,
|
permanent: true,
|
||||||
retryInfo: initRetryInfo(),
|
retryInfo: initRetryInfo(),
|
||||||
|
@ -23,7 +23,6 @@ import {
|
|||||||
canonicalizeBaseUrl,
|
canonicalizeBaseUrl,
|
||||||
codecForExchangeKeysJson,
|
codecForExchangeKeysJson,
|
||||||
codecForExchangeWireJson,
|
codecForExchangeWireJson,
|
||||||
compare,
|
|
||||||
Denomination,
|
Denomination,
|
||||||
Duration,
|
Duration,
|
||||||
durationFromSpec,
|
durationFromSpec,
|
||||||
@ -40,6 +39,7 @@ import {
|
|||||||
TalerErrorDetails,
|
TalerErrorDetails,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
hashDenomPub,
|
hashDenomPub,
|
||||||
|
LibtoolVersion,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util";
|
import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util";
|
||||||
import { CryptoApi } from "../crypto/workers/cryptoApi.js";
|
import { CryptoApi } from "../crypto/workers/cryptoApi.js";
|
||||||
@ -365,7 +365,10 @@ async function downloadKeysInfo(
|
|||||||
|
|
||||||
const protocolVersion = exchangeKeysJson.version;
|
const protocolVersion = exchangeKeysJson.version;
|
||||||
|
|
||||||
const versionRes = compare(WALLET_EXCHANGE_PROTOCOL_VERSION, protocolVersion);
|
const versionRes = LibtoolVersion.compare(
|
||||||
|
WALLET_EXCHANGE_PROTOCOL_VERSION,
|
||||||
|
protocolVersion,
|
||||||
|
);
|
||||||
if (versionRes?.compatible != true) {
|
if (versionRes?.compatible != true) {
|
||||||
const opErr = makeErrorDetails(
|
const opErr = makeErrorDetails(
|
||||||
TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE,
|
TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE,
|
||||||
@ -548,6 +551,7 @@ async function updateExchangeFromUrlImpl(
|
|||||||
masterPublicKey: details.masterPublicKey,
|
masterPublicKey: details.masterPublicKey,
|
||||||
// FIXME: only change if pointer really changed
|
// FIXME: only change if pointer really changed
|
||||||
updateClock: getTimestampNow(),
|
updateClock: getTimestampNow(),
|
||||||
|
protocolVersionRange: keysInfo.protocolVersion,
|
||||||
};
|
};
|
||||||
await tx.exchanges.put(r);
|
await tx.exchanges.put(r);
|
||||||
await tx.exchangeDetails.put(details);
|
await tx.exchangeDetails.put(details);
|
||||||
|
68
packages/taler-wallet-core/src/operations/merchants.ts
Normal file
68
packages/taler-wallet-core/src/operations/merchants.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2021 Taler Systems S.A..
|
||||||
|
|
||||||
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
canonicalizeBaseUrl,
|
||||||
|
Logger,
|
||||||
|
URL,
|
||||||
|
codecForMerchantConfigResponse,
|
||||||
|
LibtoolVersion,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { InternalWalletState, MerchantInfo } from "../common.js";
|
||||||
|
import { readSuccessResponseJsonOrThrow } from "../index.js";
|
||||||
|
|
||||||
|
const logger = new Logger("taler-wallet-core:merchants.ts");
|
||||||
|
|
||||||
|
export async function getMerchantInfo(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
merchantBaseUrl: string,
|
||||||
|
): Promise<MerchantInfo> {
|
||||||
|
const canonBaseUrl = canonicalizeBaseUrl(merchantBaseUrl);
|
||||||
|
|
||||||
|
const existingInfo = ws.merchantInfoCache[canonBaseUrl];
|
||||||
|
if (existingInfo) {
|
||||||
|
return existingInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
const configUrl = new URL("config", canonBaseUrl);
|
||||||
|
const resp = await ws.http.get(configUrl.href);
|
||||||
|
|
||||||
|
const configResp = await readSuccessResponseJsonOrThrow(
|
||||||
|
resp,
|
||||||
|
codecForMerchantConfigResponse(),
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`merchant "${canonBaseUrl}" reports protocol ${configResp.version}"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const merchantInfo: MerchantInfo = {
|
||||||
|
supportsMerchantProtocolV1: !!LibtoolVersion.compare(
|
||||||
|
"1:0:0",
|
||||||
|
configResp.version,
|
||||||
|
)?.compatible,
|
||||||
|
supportsMerchantProtocolV2: !!LibtoolVersion.compare(
|
||||||
|
"2:0:0",
|
||||||
|
configResp.version,
|
||||||
|
)?.compatible,
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.merchantInfoCache[canonBaseUrl] = merchantInfo;
|
||||||
|
return merchantInfo;
|
||||||
|
}
|
@ -27,10 +27,12 @@ import {
|
|||||||
NotificationType,
|
NotificationType,
|
||||||
TipPlanchetDetail,
|
TipPlanchetDetail,
|
||||||
TalerErrorCode,
|
TalerErrorCode,
|
||||||
codecForTipResponse,
|
codecForMerchantTipResponseV1,
|
||||||
Logger,
|
Logger,
|
||||||
URL,
|
URL,
|
||||||
DenomKeyType,
|
DenomKeyType,
|
||||||
|
BlindedDenominationSignature,
|
||||||
|
codecForMerchantTipResponseV2,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js";
|
import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js";
|
||||||
import {
|
import {
|
||||||
@ -304,31 +306,57 @@ async function processTipImpl(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await readSuccessResponseJsonOrThrow(
|
// FIXME: Do this earlier?
|
||||||
merchantResp,
|
const merchantInfo = await ws.merchantOps.getMerchantInfo(
|
||||||
codecForTipResponse(),
|
ws,
|
||||||
|
tipRecord.merchantBaseUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.blind_sigs.length !== planchets.length) {
|
let blindedSigs: BlindedDenominationSignature[] = [];
|
||||||
|
|
||||||
|
if (merchantInfo.supportsMerchantProtocolV2) {
|
||||||
|
const response = await readSuccessResponseJsonOrThrow(
|
||||||
|
merchantResp,
|
||||||
|
codecForMerchantTipResponseV2(),
|
||||||
|
);
|
||||||
|
blindedSigs = response.blind_sigs.map((x) => x.blind_sig);
|
||||||
|
} else if (merchantInfo.supportsMerchantProtocolV1) {
|
||||||
|
const response = await readSuccessResponseJsonOrThrow(
|
||||||
|
merchantResp,
|
||||||
|
codecForMerchantTipResponseV1(),
|
||||||
|
);
|
||||||
|
blindedSigs = response.blind_sigs.map((x) => ({
|
||||||
|
cipher: DenomKeyType.Rsa,
|
||||||
|
blinded_rsa_signature: x.blind_sig,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
throw Error("unsupported merchant protocol version");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blindedSigs.length !== planchets.length) {
|
||||||
throw Error("number of tip responses does not match requested planchets");
|
throw Error("number of tip responses does not match requested planchets");
|
||||||
}
|
}
|
||||||
|
|
||||||
const newCoinRecords: CoinRecord[] = [];
|
const newCoinRecords: CoinRecord[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < response.blind_sigs.length; i++) {
|
for (let i = 0; i < blindedSigs.length; i++) {
|
||||||
const blindedSig = response.blind_sigs[i].blind_sig;
|
const blindedSig = blindedSigs[i];
|
||||||
|
|
||||||
const denom = denomForPlanchet[i];
|
const denom = denomForPlanchet[i];
|
||||||
checkLogicInvariant(!!denom);
|
checkLogicInvariant(!!denom);
|
||||||
const planchet = planchets[i];
|
const planchet = planchets[i];
|
||||||
checkLogicInvariant(!!planchet);
|
checkLogicInvariant(!!planchet);
|
||||||
|
|
||||||
if (denom.denomPub.cipher !== 1) {
|
if (denom.denomPub.cipher !== DenomKeyType.Rsa) {
|
||||||
|
throw Error("unsupported cipher");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blindedSig.cipher !== DenomKeyType.Rsa) {
|
||||||
throw Error("unsupported cipher");
|
throw Error("unsupported cipher");
|
||||||
}
|
}
|
||||||
|
|
||||||
const denomSigRsa = await ws.cryptoApi.rsaUnblind(
|
const denomSigRsa = await ws.cryptoApi.rsaUnblind(
|
||||||
blindedSig,
|
blindedSig.blinded_rsa_signature,
|
||||||
planchet.blindingKey,
|
planchet.blindingKey,
|
||||||
denom.denomPub.rsa_public_key,
|
denom.denomPub.rsa_public_key,
|
||||||
);
|
);
|
||||||
|
@ -24,7 +24,6 @@ import {
|
|||||||
codecForTalerConfigResponse,
|
codecForTalerConfigResponse,
|
||||||
codecForWithdrawOperationStatusResponse,
|
codecForWithdrawOperationStatusResponse,
|
||||||
codecForWithdrawResponse,
|
codecForWithdrawResponse,
|
||||||
compare,
|
|
||||||
durationFromSpec,
|
durationFromSpec,
|
||||||
ExchangeListItem,
|
ExchangeListItem,
|
||||||
getDurationRemaining,
|
getDurationRemaining,
|
||||||
@ -42,6 +41,7 @@ import {
|
|||||||
WithdrawUriInfoResponse,
|
WithdrawUriInfoResponse,
|
||||||
VersionMatchResult,
|
VersionMatchResult,
|
||||||
DenomKeyType,
|
DenomKeyType,
|
||||||
|
LibtoolVersion,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
CoinRecord,
|
CoinRecord,
|
||||||
@ -285,7 +285,7 @@ export async function getBankWithdrawalInfo(
|
|||||||
codecForTalerConfigResponse(),
|
codecForTalerConfigResponse(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const versionRes = compare(
|
const versionRes = LibtoolVersion.compare(
|
||||||
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
||||||
config.version,
|
config.version,
|
||||||
);
|
);
|
||||||
@ -985,7 +985,7 @@ export async function getExchangeWithdrawalInfo(
|
|||||||
|
|
||||||
let versionMatch;
|
let versionMatch;
|
||||||
if (exchangeDetails.protocolVersion) {
|
if (exchangeDetails.protocolVersion) {
|
||||||
versionMatch = compare(
|
versionMatch = LibtoolVersion.compare(
|
||||||
WALLET_EXCHANGE_PROTOCOL_VERSION,
|
WALLET_EXCHANGE_PROTOCOL_VERSION,
|
||||||
exchangeDetails.protocolVersion,
|
exchangeDetails.protocolVersion,
|
||||||
);
|
);
|
||||||
|
@ -99,6 +99,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
ExchangeOperations,
|
ExchangeOperations,
|
||||||
InternalWalletState,
|
InternalWalletState,
|
||||||
|
MerchantInfo,
|
||||||
|
MerchantOperations,
|
||||||
NotificationListener,
|
NotificationListener,
|
||||||
RecoupOperations,
|
RecoupOperations,
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
@ -180,6 +182,7 @@ import {
|
|||||||
HttpRequestLibrary,
|
HttpRequestLibrary,
|
||||||
readSuccessResponseJsonOrThrow,
|
readSuccessResponseJsonOrThrow,
|
||||||
} from "./util/http.js";
|
} from "./util/http.js";
|
||||||
|
import { getMerchantInfo } from "./operations/merchants.js";
|
||||||
|
|
||||||
const builtinAuditors: AuditorTrustRecord[] = [
|
const builtinAuditors: AuditorTrustRecord[] = [
|
||||||
{
|
{
|
||||||
@ -1069,6 +1072,8 @@ class InternalWalletStateImpl implements InternalWalletState {
|
|||||||
memoProcessDeposit: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
|
memoProcessDeposit: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
|
||||||
cryptoApi: CryptoApi;
|
cryptoApi: CryptoApi;
|
||||||
|
|
||||||
|
merchantInfoCache: Record<string, MerchantInfo> = {};
|
||||||
|
|
||||||
timerGroup: TimerGroup = new TimerGroup();
|
timerGroup: TimerGroup = new TimerGroup();
|
||||||
latch = new AsyncCondition();
|
latch = new AsyncCondition();
|
||||||
stopped = false;
|
stopped = false;
|
||||||
@ -1088,6 +1093,10 @@ class InternalWalletStateImpl implements InternalWalletState {
|
|||||||
processRecoupGroup: processRecoupGroup,
|
processRecoupGroup: processRecoupGroup,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
merchantOps: MerchantOperations = {
|
||||||
|
getMerchantInfo: getMerchantInfo,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Promises that are waiting for a particular resource.
|
* Promises that are waiting for a particular resource.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user