diff --git a/packages/backend/src/open_payments/wallet_address/service.test.ts b/packages/backend/src/open_payments/wallet_address/service.test.ts index 910d00bb39..e363e111a9 100644 --- a/packages/backend/src/open_payments/wallet_address/service.test.ts +++ b/packages/backend/src/open_payments/wallet_address/service.test.ts @@ -59,7 +59,7 @@ describe('Open Payments Wallet Address Service', (): void => { await appContainer.shutdown() }) - describe('Create or Get Wallet Address3', (): void => { + describe('Create or Get Wallet Address', (): void => { let tenantId: string let options: CreateOptions @@ -606,6 +606,56 @@ describe('Open Payments Wallet Address Service', (): void => { ) ) + test( + 'creates wallet address not found event for tenant with matching prefix', + withConfigOverride( + () => config, + { walletAddressLookupTimeoutMs: 0 }, + async (): Promise => { + const walletAddressUrl = `https://${faker.internet.domainName()}/.well-known/pay` + const tenant = await createTenant(deps) + await createTenantSettings(deps, { + tenantId: tenant.id, + setting: [ + { + key: TenantSettingKeys.WALLET_ADDRESS_URL.name, + value: `${walletAddressUrl}/${uuid()}` + } + ] + }) + + await expect( + walletAddressService.getOrPollByUrl(walletAddressUrl) + ).resolves.toBeUndefined() + + const walletAddressNotFoundEvents = await WalletAddressEvent.query( + knex + ) + .where({ + type: WalletAddressEventType.WalletAddressNotFound + }) + .withGraphFetched('webhooks') + + expect(walletAddressNotFoundEvents).toHaveLength(1) + expect(walletAddressNotFoundEvents[0].webhooks).toHaveLength(2) + expect(walletAddressNotFoundEvents[0]).toMatchObject({ + data: { walletAddressUrl }, + webhooks: expect.arrayContaining([ + expect.objectContaining({ + recipientTenantId: tenant.id, + eventId: walletAddressNotFoundEvents[0].id, + processAt: expect.any(Date) + }), + expect.objectContaining({ + recipientTenantId: config.operatorTenantId, + eventId: walletAddressNotFoundEvents[0].id, + processAt: expect.any(Date) + }) + ]) + }) + } + ) + ) test( 'polls for wallet address', withConfigOverride( diff --git a/packages/backend/src/open_payments/wallet_address/service.ts b/packages/backend/src/open_payments/wallet_address/service.ts index 86ae1d2b62..79c9669e63 100644 --- a/packages/backend/src/open_payments/wallet_address/service.ts +++ b/packages/backend/src/open_payments/wallet_address/service.ts @@ -379,13 +379,25 @@ async function getOrPollByUrl( const existingWalletAddress = await getWalletAddressByUrl(deps, url) if (existingWalletAddress) return existingWalletAddress + let containsOperatorTenant = false + const webhookRecipients = ( + await deps.tenantSettingService.getSettingsByPrefix(url) + ).map((tenantSetting) => { + if (tenantSetting.tenantId === deps.config.operatorTenantId) + containsOperatorTenant = true + return { recipientTenantId: tenantSetting.tenantId } + }) + + if (!containsOperatorTenant) + webhookRecipients.push({ recipientTenantId: deps.config.operatorTenantId }) + await WalletAddressEvent.query(deps.knex).insertGraph({ type: WalletAddressEventType.WalletAddressNotFound, data: { walletAddressUrl: url }, tenantId: deps.config.operatorTenantId, - webhooks: [{ recipientTenantId: deps.config.operatorTenantId }] + webhooks: webhookRecipients }) deps.logger.debug( diff --git a/packages/backend/src/tenants/settings/service.test.ts b/packages/backend/src/tenants/settings/service.test.ts index f0631b14fc..5ceeb2556b 100644 --- a/packages/backend/src/tenants/settings/service.test.ts +++ b/packages/backend/src/tenants/settings/service.test.ts @@ -9,7 +9,7 @@ import { Tenant } from '../model' import { TenantService } from '../service' import { faker } from '@faker-js/faker' import { exchangeRatesSetting, randomSetting } from '../../tests/tenantSettings' -import { TenantSetting } from './model' +import { TenantSetting, TenantSettingKeys } from './model' import { CreateOptions, GetOptions, @@ -17,6 +17,8 @@ import { UpdateOptions } from './service' import { AuthServiceClient } from '../../auth-service-client/client' +import { v4 as uuid } from 'uuid' +import { createTenant } from '../../tests/tenant' describe('TenantSetting Service', (): void => { let deps: IocContract @@ -371,4 +373,67 @@ describe('TenantSetting Service', (): void => { expect(result).toEqual([]) }) }) + + describe('get settings by value', (): void => { + test('can get settings by wallet address prefix setting', async (): Promise => { + const secondTenant = await createTenant(deps) + const baseUrl = `https://${faker.internet.domainName()}/${uuid()}` + const settings = ( + await Promise.all([ + tenantSettingService.create({ + tenantId: tenant.id, + setting: [ + { + key: TenantSettingKeys.WALLET_ADDRESS_URL.name, + value: `${baseUrl}/${uuid()}` + } + ] + }), + tenantSettingService.create({ + tenantId: secondTenant.id, + setting: [ + { + key: TenantSettingKeys.WALLET_ADDRESS_URL.name, + value: `${baseUrl}/${uuid()}` + } + ] + }) + ]) + ).flat() + + const retrievedSettings = + await tenantSettingService.getSettingsByPrefix(baseUrl) + expect(retrievedSettings).toEqual(settings) + }) + + test('does not retrieve tenants if no wallet address prefix matches', async (): Promise => { + const secondTenant = await createTenant(deps) + const baseUrl = `https://${faker.internet.domainName()}/${uuid()}` + await Promise.all([ + tenantSettingService.create({ + tenantId: tenant.id, + setting: [ + { + key: TenantSettingKeys.WALLET_ADDRESS_URL.name, + value: `${baseUrl}/${uuid()}` + } + ] + }), + tenantSettingService.create({ + tenantId: secondTenant.id, + setting: [ + { + key: TenantSettingKeys.WALLET_ADDRESS_URL.name, + value: `${baseUrl}/${uuid()}` + } + ] + }) + ]) + + const retrievedSettings = await tenantSettingService.getSettingsByPrefix( + faker.internet.url() + ) + expect(retrievedSettings).toHaveLength(0) + }) + }) }) diff --git a/packages/backend/src/tenants/settings/service.ts b/packages/backend/src/tenants/settings/service.ts index 5623928bf1..9989f814a1 100644 --- a/packages/backend/src/tenants/settings/service.ts +++ b/packages/backend/src/tenants/settings/service.ts @@ -43,6 +43,7 @@ export interface TenantSettingService { pagination?: Pagination, sortOrder?: SortOrder ) => Promise + getSettingsByPrefix: (prefix: string) => Promise } export interface ServiceDependencies extends BaseService { @@ -68,7 +69,9 @@ export async function createTenantSettingService( tenantId: string, pagination?: Pagination, sortOrder?: SortOrder - ) => getTenantSettingPageForTenant(deps, tenantId, pagination, sortOrder) + ) => getTenantSettingPageForTenant(deps, tenantId, pagination, sortOrder), + getSettingsByPrefix: (prefix: string) => + getWalletAddressSettingsByPrefix(deps, prefix) } } @@ -147,3 +150,14 @@ async function getTenantSettingPageForTenant( .andWhere('tenantId', tenantId) .getPage(pagination, sortOrder) } + +async function getWalletAddressSettingsByPrefix( + deps: ServiceDependencies, + prefix: string +): Promise { + return await TenantSetting.query(deps.knex) + .whereILike('value', `${prefix}%`) + .andWhere({ + key: TenantSettingKeys.WALLET_ADDRESS_URL.name + }) +}