mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-18 22:58:32 +02:00
feat(payments): wire provider registry into invoice creation + fit real BTCPay infra
Recon of the operator's LAN BTCPay box (ssh four-btcpay / 10.42.1.33) showed the
real topology: BTCPay Server 2.3.9 handles BTC/LTC only (BTCPAY_CHAINS=ltc,btc),
while XMR and WOW run through separate 'xmrcheckout' (:8080) and 'wowcheckout'
(:8180) apps — and crucially, both expose a BTCPay-Greenfield-COMPATIBLE API
(/api/v1/stores/{id}/invoices, token auth, Settled/New/Expired status). Grin
isn't deployed there yet. So one Greenfield client class serves all three.
- btcpayProvider: id is now constructor-configurable so the same class registers
as 'btcpay' (BTC/LTC), 'xmrcheckout' (XMR), and 'wowcheckout' (WOW).
- buildProviderRegistry: activates each gateway independently from its *_URL/
_STORE_ID/_API_KEY trio (BTCPAY_*, XMRCHECKOUT_*, WOWCHECKOUT_*), with
topology-correct default routing (BTC/LTC->btcpay, XMR/WOW->their checkout or
native), BTCPAY_CHAINS to widen, PAYMENT_ROUTING to override.
- gameModeManager: takes an optional paymentProviders registry (defaults to a
native-wrapping registry built from walletService). createPaymentRequest now
creates the invoice via getProvider(cryptoType).createInvoice(...). The native
provider delegates to walletService.createPaymentRequest, so the shipped
single-chain config is byte-for-byte unchanged — same subaddress, same
monitoring maps. paymentRequest now also carries invoiceId for the monitoring
wire to come.
- nativeMoneroProvider: surfaces addressIndex so the call site stays provider-agnostic.
Behavior-preserving: no gateway env => native-only, every chain routes native.
Monitoring + payout paths intentionally left on the native path this pass;
gateway monitoring (provider.startWatch) is the flagged next step. Tests: +4
(provider wiring routes to a gateway; default registry delegates to native).
Suite 53 suites / 365 tests green.
This commit is contained in:
@@ -18,6 +18,7 @@ const paymentConfig = require('../config/paymentConfig');
|
||||
const money = require('../money/atomic');
|
||||
const Entitlements = require('../multiplayer/entitlements');
|
||||
const ProductGrants = require('../payments/productGrants');
|
||||
const { buildProviderRegistry } = require('../payments/providers');
|
||||
|
||||
// A wallet "not enough (unlocked) money" error is raised BEFORE the tx is broadcast, so it
|
||||
// is SAFE to retry (no double-pay risk) — unlike an ambiguous post-broadcast failure. Monero
|
||||
@@ -31,12 +32,18 @@ const DEFAULT_SINGLE_GAME_PRICE = 5000000000; // 0.005 XMR or 0.05 WOW dependi
|
||||
const DEFAULT_CREDITS_PACKAGE_PRICE = 50000000000;
|
||||
|
||||
class GameModeManager {
|
||||
constructor(databaseManager, walletRPCService, debugManager, paymentConfigManager = null) {
|
||||
constructor(databaseManager, walletRPCService, debugManager, paymentConfigManager = null, paymentProviders = null) {
|
||||
this.db = databaseManager;
|
||||
this.walletService = walletRPCService;
|
||||
this.debugManager = debugManager;
|
||||
this.paymentConfigManager = paymentConfigManager || null;
|
||||
|
||||
// Modular payment providers (Pillar 3). Defaults to a registry whose only member is the
|
||||
// native Monero/Wownero provider wrapping this walletService — so with no gateway env set
|
||||
// every chain routes to the existing wallet-RPC path and behavior is unchanged. Injectable
|
||||
// for tests. See src/payments/providers/index.js + the btcpay-infra-topology memo.
|
||||
this.paymentProviders = paymentProviders || buildProviderRegistry({ walletService: walletRPCService });
|
||||
|
||||
this.cryptoType = process.env.CRYPTO_TYPE || 'XMR';
|
||||
this.currencyDecimals = this.inferCurrencyDecimals(this.cryptoType);
|
||||
|
||||
@@ -1214,13 +1221,31 @@ class GameModeManager {
|
||||
`, [user.id]);
|
||||
const expiredAddresses = expiredResult.rows.map(r => r.subaddress);
|
||||
|
||||
// Create payment request using wallet RPC with correct parameters
|
||||
const paymentResult = await this.walletService.createPaymentRequest(
|
||||
amount,
|
||||
description,
|
||||
user.id,
|
||||
socketId
|
||||
);
|
||||
// Create the payment request through the routed provider. The native provider (default
|
||||
// for every chain when no gateway env is set) delegates to walletService.createPaymentRequest,
|
||||
// so this is byte-for-byte the legacy path for the shipped single-chain config — same
|
||||
// subaddress, same walletService monitoring maps. A BTCPay/xmrcheckout/wowcheckout gateway
|
||||
// takes over only when the operator routes this.cryptoType to it.
|
||||
const provider = this.paymentProviders && this.paymentProviders.getProvider(this.cryptoType);
|
||||
let paymentResult;
|
||||
if (provider) {
|
||||
const invoice = await provider.createInvoice({
|
||||
chain: this.cryptoType,
|
||||
amountAtomic: amount,
|
||||
description,
|
||||
userId: user.id,
|
||||
orderId: socketId
|
||||
});
|
||||
paymentResult = {
|
||||
address: invoice.address,
|
||||
addressIndex: invoice.addressIndex ?? (invoice.raw && invoice.raw.addressIndex) ?? null,
|
||||
expiresAt: invoice.expiresAt,
|
||||
invoiceId: invoice.invoiceId || invoice.address
|
||||
};
|
||||
} else {
|
||||
// Defensive fallback: registry somehow empty -> exact legacy call.
|
||||
paymentResult = await this.walletService.createPaymentRequest(amount, description, user.id, socketId);
|
||||
}
|
||||
|
||||
const expiresAt = paymentResult.expiresAt || new Date(Date.now() + 30 * 60 * 1000);
|
||||
|
||||
@@ -1247,6 +1272,7 @@ class GameModeManager {
|
||||
return {
|
||||
id: insertedRow?.id,
|
||||
address: paymentResult.address,
|
||||
invoiceId: paymentResult.invoiceId || paymentResult.address,
|
||||
amount: amount,
|
||||
amountFormatted: this.formatAtomicHuman(amount, 4),
|
||||
currency: this.cryptoType,
|
||||
|
||||
@@ -3,16 +3,20 @@ const ChainProfile = require('../../chain/chainProfile');
|
||||
const Atomic = require('../../money/atomic');
|
||||
|
||||
/**
|
||||
* BTCPay Server provider — the first payment plugin. Talks to the BTCPay Greenfield API, so it
|
||||
* covers every chain the operator's BTCPay handles: BTC/LTC natively, XMR via xmrcheckout, WOW via
|
||||
* wowcheckout, and Grin via the in-house plugin. Config (env): BTCPAY_URL, BTCPAY_STORE_ID,
|
||||
* BTCPAY_API_KEY. The invoice is denominated in the chain's own crypto (currency = ticker).
|
||||
* BTCPay Greenfield provider — the first payment plugin. One client class serves every endpoint
|
||||
* that speaks the BTCPay Greenfield API. On the operator's LAN that is THREE endpoints (see the
|
||||
* btcpay-infra-topology memo): real BTCPay Server for BTC/LTC, plus the `xmrcheckout` and
|
||||
* `wowcheckout` shims which expose Greenfield-compatible `/api/v1/stores/{id}/invoices` routes for
|
||||
* XMR and WOW. So the same class is registered up to three times with different `id`/baseUrl/store/
|
||||
* key/chains (id defaults to 'btcpay'). Grin is not deployed yet. The invoice is denominated by
|
||||
* `currency` — pass the chain ticker (e.g. 'XMR') for a native-crypto amount. Auth is
|
||||
* `Authorization: token <apiKey>` on all three.
|
||||
*
|
||||
* fetchImpl is injectable for tests; defaults to the global fetch (Node >= 18).
|
||||
*/
|
||||
class BTCPayProvider extends PaymentProvider {
|
||||
constructor({ baseUrl, storeId, apiKey, chains = ['BTC', 'LTC', 'XMR', 'WOW', 'GRIN'], fetchImpl = null } = {}) {
|
||||
super('btcpay');
|
||||
constructor({ id = 'btcpay', baseUrl, storeId, apiKey, chains = ['BTC', 'LTC', 'XMR', 'WOW', 'GRIN'], fetchImpl = null } = {}) {
|
||||
super(id);
|
||||
this.baseUrl = String(baseUrl || '').replace(/\/+$/, '');
|
||||
this.storeId = storeId;
|
||||
this.apiKey = apiKey;
|
||||
|
||||
@@ -26,38 +26,65 @@ function parseRouting(raw) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Chains parsed from a "btc,ltc" style env list, uppercased.
|
||||
function parseChains(raw, fallback) {
|
||||
if (!raw) return fallback;
|
||||
const list = String(raw).split(',').map(s => s.trim().toUpperCase()).filter(Boolean);
|
||||
return list.length ? list : fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assemble the payment-provider registry from environment/config. Single wiring seam for Pillar 3.
|
||||
* Assemble the payment-provider registry from environment/config. Single wiring seam for Pillar 3,
|
||||
* shaped to the operator's real infra (see btcpay-infra-topology memo): three Greenfield endpoints
|
||||
* — real BTCPay (BTC/LTC), xmrcheckout (XMR), wowcheckout (WOW) — plus the native wallet-RPC.
|
||||
*
|
||||
* Behavior-preserving: with no BTCPAY_* env set (prod today), only the native Monero/Wownero
|
||||
* provider is registered and every chain routes to it — identical to the current flow. Set
|
||||
* BTCPAY_URL/BTCPAY_STORE_ID/BTCPAY_API_KEY and BTC/LTC/GRIN route to BTCPay automatically, while
|
||||
* XMR/WOW stay on the native wallet-RPC. PAYMENT_ROUTING overrides any chain->provider mapping.
|
||||
* Behavior-preserving: with NO gateway env set (prod today) only the native Monero/Wownero provider
|
||||
* is registered and every chain routes to it — identical to the current flow. Each endpoint
|
||||
* activates independently when its *_URL/_STORE_ID/_API_KEY trio is present:
|
||||
* BTCPAY_URL/BTCPAY_STORE_ID/BTCPAY_API_KEY -> id 'btcpay' (BTCPAY_CHAINS, default BTC,LTC)
|
||||
* XMRCHECKOUT_URL/XMRCHECKOUT_STORE_ID/..._API_KEY -> id 'xmrcheckout' (XMR)
|
||||
* WOWCHECKOUT_URL/WOWCHECKOUT_STORE_ID/..._API_KEY -> id 'wowcheckout' (WOW)
|
||||
* Default routing sends each chain to its gateway when present, else native for XMR/WOW.
|
||||
* PAYMENT_ROUTING (JSON or "CHAIN:provider,...") overrides any chain->provider mapping.
|
||||
*/
|
||||
function buildProviderRegistry({ env = process.env, walletService = null } = {}) {
|
||||
const registry = new PaymentProviderRegistry();
|
||||
let hasNative = false, hasBtcpay = false;
|
||||
const routing = {};
|
||||
let hasNative = false;
|
||||
|
||||
if (walletService) {
|
||||
registry.register(new NativeMoneroProvider({ walletService }));
|
||||
hasNative = true;
|
||||
routing.XMR = 'native-monero';
|
||||
routing.WOW = 'native-monero';
|
||||
}
|
||||
|
||||
const baseUrl = env.BTCPAY_URL, storeId = env.BTCPAY_STORE_ID, apiKey = env.BTCPAY_API_KEY;
|
||||
if (baseUrl && storeId && apiKey) {
|
||||
registry.register(new BTCPayProvider({ baseUrl, storeId, apiKey }));
|
||||
hasBtcpay = true;
|
||||
// Real BTCPay (BTC/LTC by default; operator may widen via BTCPAY_CHAINS).
|
||||
if (env.BTCPAY_URL && env.BTCPAY_STORE_ID && env.BTCPAY_API_KEY) {
|
||||
const chains = parseChains(env.BTCPAY_CHAINS, ['BTC', 'LTC']);
|
||||
registry.register(new BTCPayProvider({ id: 'btcpay', baseUrl: env.BTCPAY_URL, storeId: env.BTCPAY_STORE_ID, apiKey: env.BTCPAY_API_KEY, chains }));
|
||||
for (const c of chains) routing[c] = 'btcpay';
|
||||
}
|
||||
|
||||
// Sensible defaults, only for providers that are actually present.
|
||||
const routing = {};
|
||||
if (hasBtcpay) { routing.BTC = 'btcpay'; routing.LTC = 'btcpay'; routing.GRIN = 'btcpay'; }
|
||||
if (hasNative) { routing.XMR = 'native-monero'; routing.WOW = 'native-monero'; }
|
||||
else if (hasBtcpay) { routing.XMR = 'btcpay'; routing.WOW = 'btcpay'; }
|
||||
// xmrcheckout / wowcheckout — Greenfield-compatible shims, same provider class, different id.
|
||||
const shims = [
|
||||
{ prefix: 'XMRCHECKOUT', id: 'xmrcheckout', chain: 'XMR' },
|
||||
{ prefix: 'WOWCHECKOUT', id: 'wowcheckout', chain: 'WOW' }
|
||||
];
|
||||
for (const s of shims) {
|
||||
const url = env[`${s.prefix}_URL`], storeId = env[`${s.prefix}_STORE_ID`], apiKey = env[`${s.prefix}_API_KEY`];
|
||||
if (url && storeId && apiKey) {
|
||||
registry.register(new BTCPayProvider({ id: s.id, baseUrl: url, storeId, apiKey, chains: [s.chain] }));
|
||||
routing[s.chain] = s.id; // a dedicated checkout gateway wins over native for its chain
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(routing, parseRouting(env.PAYMENT_ROUTING)); // operator override wins
|
||||
Object.assign(routing, parseRouting(env.PAYMENT_ROUTING)); // explicit operator override wins over all
|
||||
registry.setRouting(routing);
|
||||
registry.setDefault(hasNative ? 'native-monero' : (hasBtcpay ? 'btcpay' : null));
|
||||
|
||||
// Default provider (for chains with no explicit route): native if present, else the first registered gateway.
|
||||
const firstId = registry.providers.keys().next().value || null;
|
||||
registry.setDefault(hasNative ? 'native-monero' : firstId);
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ class NativeMoneroProvider extends PaymentProvider {
|
||||
return {
|
||||
invoiceId: address, // native invoices are keyed by their subaddress
|
||||
address,
|
||||
addressIndex: (req && req.addressIndex != null) ? req.addressIndex : null, // Monero-specific; enables monitoring restore after restart
|
||||
uri: (req && req.uri) || null,
|
||||
amountAtomic: String(amountAtomic),
|
||||
expiresAt: (req && req.expiresAt) || null,
|
||||
|
||||
@@ -1,52 +1,71 @@
|
||||
const { buildProviderRegistry, parseRouting } = require('../src/payments/providers');
|
||||
|
||||
const fakeWallet = { createPaymentRequest: async () => ({}), checkPaymentStatus: async () => ({}), processBatchPayout: async () => ({}) };
|
||||
const CHECKOUT = { // full LAN infra: real BTCPay (BTC/LTC) + xmrcheckout + wowcheckout
|
||||
BTCPAY_URL: 'http://10.42.1.33', BTCPAY_STORE_ID: 'b', BTCPAY_API_KEY: 'k',
|
||||
XMRCHECKOUT_URL: 'http://10.42.1.33:8080', XMRCHECKOUT_STORE_ID: 'x', XMRCHECKOUT_API_KEY: 'k',
|
||||
WOWCHECKOUT_URL: 'http://10.42.1.33:8180', WOWCHECKOUT_STORE_ID: 'w', WOWCHECKOUT_API_KEY: 'k'
|
||||
};
|
||||
|
||||
describe('parseRouting', () => {
|
||||
test('parses JSON and compact forms, ignores garbage', () => {
|
||||
expect(parseRouting('{"btc":"btcpay","xmr":"native-monero"}')).toEqual({ BTC: 'btcpay', XMR: 'native-monero' });
|
||||
expect(parseRouting('BTC:btcpay, XMR:native-monero')).toEqual({ BTC: 'btcpay', XMR: 'native-monero' });
|
||||
expect(parseRouting('{"btc":"btcpay","xmr":"xmrcheckout"}')).toEqual({ BTC: 'btcpay', XMR: 'xmrcheckout' });
|
||||
expect(parseRouting('BTC:btcpay, XMR:xmrcheckout')).toEqual({ BTC: 'btcpay', XMR: 'xmrcheckout' });
|
||||
expect(parseRouting('')).toEqual({});
|
||||
expect(parseRouting('{not json')).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildProviderRegistry', () => {
|
||||
test('native-only (no BTCPay env): every chain routes to native — behavior-preserving', () => {
|
||||
test('native-only (no gateway env): every chain routes to native — behavior-preserving', () => {
|
||||
const reg = buildProviderRegistry({ env: {}, walletService: fakeWallet });
|
||||
expect(reg.getProvider('WOW').id).toBe('native-monero');
|
||||
expect(reg.getProvider('XMR').id).toBe('native-monero');
|
||||
expect(reg.getProvider('BTC').id).toBe('native-monero'); // default provider, no routing entry
|
||||
});
|
||||
|
||||
test('with BTCPay env: BTC/LTC/GRIN -> btcpay, XMR/WOW stay native', () => {
|
||||
test('real BTCPay + native wallet: BTC/LTC -> btcpay, XMR/WOW stay native', () => {
|
||||
const reg = buildProviderRegistry({
|
||||
env: { BTCPAY_URL: 'https://btcpay.lan', BTCPAY_STORE_ID: 's', BTCPAY_API_KEY: 'k' },
|
||||
env: { BTCPAY_URL: 'u', BTCPAY_STORE_ID: 's', BTCPAY_API_KEY: 'k' },
|
||||
walletService: fakeWallet
|
||||
});
|
||||
expect(reg.getProvider('BTC').id).toBe('btcpay');
|
||||
expect(reg.getProvider('LTC').id).toBe('btcpay');
|
||||
expect(reg.getProvider('GRIN').id).toBe('btcpay');
|
||||
expect(reg.getProvider('XMR').id).toBe('native-monero');
|
||||
expect(reg.getProvider('WOW').id).toBe('native-monero');
|
||||
});
|
||||
|
||||
test('BTCPay-only (no wallet): all five chains route to btcpay', () => {
|
||||
test('full LAN infra: BTC/LTC -> btcpay, XMR -> xmrcheckout, WOW -> wowcheckout', () => {
|
||||
const reg = buildProviderRegistry({ env: CHECKOUT, walletService: fakeWallet });
|
||||
expect(reg.getProvider('BTC').id).toBe('btcpay');
|
||||
expect(reg.getProvider('LTC').id).toBe('btcpay');
|
||||
expect(reg.getProvider('XMR').id).toBe('xmrcheckout'); // dedicated gateway wins over native
|
||||
expect(reg.getProvider('WOW').id).toBe('wowcheckout');
|
||||
});
|
||||
|
||||
test('a checkout gateway wins over the native wallet for its chain', () => {
|
||||
const reg = buildProviderRegistry({
|
||||
env: { BTCPAY_URL: 'https://btcpay.lan', BTCPAY_STORE_ID: 's', BTCPAY_API_KEY: 'k' },
|
||||
env: { XMRCHECKOUT_URL: 'http://10.42.1.33:8080', XMRCHECKOUT_STORE_ID: 'x', XMRCHECKOUT_API_KEY: 'k' },
|
||||
walletService: fakeWallet
|
||||
});
|
||||
expect(reg.getProvider('XMR').id).toBe('xmrcheckout');
|
||||
expect(reg.getProvider('WOW').id).toBe('native-monero'); // no wowcheckout -> native
|
||||
});
|
||||
|
||||
test('BTCPAY_CHAINS widens the real BTCPay gateway', () => {
|
||||
const reg = buildProviderRegistry({
|
||||
env: { BTCPAY_URL: 'u', BTCPAY_STORE_ID: 's', BTCPAY_API_KEY: 'k', BTCPAY_CHAINS: 'btc,ltc,xmr' },
|
||||
walletService: null
|
||||
});
|
||||
expect(reg.getProvider('XMR').id).toBe('btcpay');
|
||||
expect(reg.getProvider('WOW').id).toBe('btcpay');
|
||||
expect(reg.getProvider('BTC').id).toBe('btcpay');
|
||||
});
|
||||
|
||||
test('PAYMENT_ROUTING override wins', () => {
|
||||
test('PAYMENT_ROUTING override wins over defaults', () => {
|
||||
const reg = buildProviderRegistry({
|
||||
env: { BTCPAY_URL: 'u', BTCPAY_STORE_ID: 's', BTCPAY_API_KEY: 'k', PAYMENT_ROUTING: 'WOW:btcpay' },
|
||||
env: { ...CHECKOUT, PAYMENT_ROUTING: 'XMR:native-monero' },
|
||||
walletService: fakeWallet
|
||||
});
|
||||
expect(reg.getProvider('WOW').id).toBe('btcpay'); // overrode the native default
|
||||
expect(reg.getProvider('XMR').id).toBe('native-monero'); // untouched
|
||||
expect(reg.getProvider('XMR').id).toBe('native-monero'); // overrode the xmrcheckout default
|
||||
expect(reg.getProvider('WOW').id).toBe('wowcheckout'); // untouched
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Proves GameModeManager.createPaymentRequest routes invoice creation through the payment-provider
|
||||
* registry: a configured gateway is used, and with the default registry it delegates to the native
|
||||
* walletService (byte-for-byte legacy path).
|
||||
*/
|
||||
const GameModeManager = require('../src/game/gameModeManager');
|
||||
|
||||
function mockDb() {
|
||||
return {
|
||||
query: jest.fn(async (sql) => {
|
||||
const s = String(sql);
|
||||
if (/FROM users WHERE id/i.test(s)) return { rows: [{ id: 7, credits: 0, total_credits_purchased: 0 }] };
|
||||
if (/INSERT INTO payments/i.test(s)) return { rows: [{ id: 101, expires_at: null }] };
|
||||
return { rows: [] }; // reuse lookup, expire-stale, everything else
|
||||
}),
|
||||
withTransaction: jest.fn(async (cb) => cb({ query: jest.fn().mockResolvedValue({ rows: [] }) }))
|
||||
};
|
||||
}
|
||||
|
||||
const mockDebug = { CONSOLE_LOGGING: false };
|
||||
const mockConfig = {
|
||||
getConfig: () => ({
|
||||
paymentsEnabled: true,
|
||||
currency: { symbol: 'WOW', decimals: 11 },
|
||||
modes: { direct: { enabled: true, price: 100000000000n }, credits: { enabled: false, packages: [] } },
|
||||
payouts: { rules: { direct: { multipliers: {} }, credits: { enabled: false, multipliers: {} } } },
|
||||
preferences: {}
|
||||
}),
|
||||
getLegacyGameMode: () => 'PAID_SINGLE',
|
||||
eventBus: { on: () => {} }
|
||||
};
|
||||
|
||||
describe('createPaymentRequest routes through the provider registry', () => {
|
||||
test('a configured gateway provider is used for invoice creation', async () => {
|
||||
const gateway = {
|
||||
id: 'xmrcheckout',
|
||||
createInvoice: jest.fn(async () => ({ invoiceId: 'inv-uuid', address: 'GATEWAYADDR', addressIndex: null, expiresAt: null })),
|
||||
getProvider() { return this; }
|
||||
};
|
||||
const registry = { getProvider: jest.fn(() => gateway) };
|
||||
const walletService = { createPaymentRequest: jest.fn() };
|
||||
const gmm = new GameModeManager(mockDb(), walletService, mockDebug, mockConfig, registry);
|
||||
|
||||
const res = await gmm.createPaymentRequest('sock1', 'single_game', { userId: 7 });
|
||||
|
||||
expect(registry.getProvider).toHaveBeenCalledWith(gmm.cryptoType);
|
||||
expect(gateway.createInvoice).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ chain: gmm.cryptoType, userId: 7, orderId: 'sock1' })
|
||||
);
|
||||
expect(res.address).toBe('GATEWAYADDR');
|
||||
expect(res.invoiceId).toBe('inv-uuid');
|
||||
expect(walletService.createPaymentRequest).not.toHaveBeenCalled(); // routed away from native
|
||||
});
|
||||
|
||||
test('the default registry delegates to the native walletService (legacy path preserved)', async () => {
|
||||
const walletService = {
|
||||
createPaymentRequest: jest.fn(async () => ({ address: 'NATIVEADDR', addressIndex: 3, expiresAt: null }))
|
||||
};
|
||||
// No injected registry -> constructor builds one whose only provider wraps this walletService.
|
||||
const gmm = new GameModeManager(mockDb(), walletService, mockDebug, mockConfig);
|
||||
|
||||
const res = await gmm.createPaymentRequest('sock2', 'single_game', { userId: 7 });
|
||||
|
||||
expect(walletService.createPaymentRequest).toHaveBeenCalledWith(expect.anything(), expect.any(String), 7, 'sock2');
|
||||
expect(res.address).toBe('NATIVEADDR');
|
||||
expect(res.invoiceId).toBe('NATIVEADDR'); // native invoices keyed by subaddress
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user