From d61f58497b28cfb0ae8968d1e8bf0ec92809eba8 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Mon, 25 Sep 2023 12:02:05 -0400 Subject: [PATCH 01/39] feat(backend): unauthenticated get incoming payment (#1952) * feat(backend): mount resource server routes explicitly * chore: formatting * feat(backend): wip unauthenticated incoming payment get * fix(backend): token introspection middleware * chore(backend): cleanup * chore(backend): cleanup * test(backend): update incoming payment tests * test(backend): add tests for new option * feat(backend): add unauthed incoming payment * fix: accidentally removed postman request * refactor(backend): optional sig params * fix(backend): typo * fix(backend): unused args --------- Co-authored-by: Nathan Lie --- postman/collections/Interledger.json | 57 ++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/postman/collections/Interledger.json b/postman/collections/Interledger.json index 1ff5e31e5a..95ad6048ea 100644 --- a/postman/collections/Interledger.json +++ b/postman/collections/Interledger.json @@ -1662,6 +1662,63 @@ }, "response": [] }, + { + "name": "Get Incoming Payment (Unauthenticated)", + "event": [ + { + "listen": "test", + "script": { + "id": "836507d4-1c20-4b2c-ad6f-b374cb57d305", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "96baae4b-4a6a-4265-b1f5-29d0f16e11ad", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "id": "e53e9769-1a49-4d1d-a1f8-a94e54e4a4fd", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "GNAP {{accessToken}}", + "disabled": true + }, + { + "key": "Host", + "value": "happy-life-bank-backend" + } + ], + "url": { + "raw": "{{pfryPaymentPointer}}/incoming-payments/{{incomingPaymentId}}", + "host": [ + "{{pfryPaymentPointer}}" + ], + "path": [ + "incoming-payments", + "{{incomingPaymentId}}" + ] + } + }, + "response": [] + }, { "name": "List Incoming Payments", "event": [ From 5f35ed91bd5539d29dd92ae6543690ca87ab5b28 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Tue, 10 Oct 2023 12:42:49 -0400 Subject: [PATCH 02/39] fix(backend): rm unused connection middleware --- .../connection/middleware.test.ts | 70 ------------------- .../open_payments/connection/middleware.ts | 23 ------ 2 files changed, 93 deletions(-) delete mode 100644 packages/backend/src/open_payments/connection/middleware.test.ts delete mode 100644 packages/backend/src/open_payments/connection/middleware.ts diff --git a/packages/backend/src/open_payments/connection/middleware.test.ts b/packages/backend/src/open_payments/connection/middleware.test.ts deleted file mode 100644 index d25c12782d..0000000000 --- a/packages/backend/src/open_payments/connection/middleware.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import assert from 'assert' -import { v4 as uuid } from 'uuid' -import { connectionMiddleware, ConnectionContext } from './middleware' -import { Config } from '../../config/app' -import { IocContract } from '@adonisjs/fold' -import { initIocContainer } from '../../' -import { AppServices } from '../../app' -import { createTestApp, TestContainer } from '../../tests/app' -import { createAsset } from '../../tests/asset' -import { createContext } from '../../tests/context' -import { createIncomingPayment } from '../../tests/incomingPayment' -import { createWalletAddress } from '../../tests/walletAddress' -import { truncateTables } from '../../tests/tableManager' - -describe('Connection Middleware', (): void => { - let deps: IocContract - let appContainer: TestContainer - let ctx: ConnectionContext - let next: jest.MockedFunction<() => Promise> - - beforeAll(async (): Promise => { - deps = await initIocContainer(Config) - appContainer = await createTestApp(deps) - }) - - beforeEach((): void => { - ctx = createContext( - { - headers: { - Accept: 'application/json' - } - }, - {} - ) - ctx.container = deps - next = jest.fn() - }) - - afterEach(async (): Promise => { - await truncateTables(appContainer.knex) - }) - - afterAll(async (): Promise => { - await appContainer.shutdown() - }) - - test('returns 404 for unknown connection id', async (): Promise => { - ctx.params.id = uuid() - await expect(connectionMiddleware(ctx, next)).rejects.toMatchObject({ - status: 404, - message: 'Not Found' - }) - expect(next).not.toHaveBeenCalled() - }) - - test('sets the context incomingPayment and calls next', async (): Promise => { - const asset = await createAsset(deps) - const { id: walletAddressId } = await createWalletAddress(deps, { - assetId: asset.id - }) - const incomingPayment = await createIncomingPayment(deps, { - walletAddressId - }) - assert.ok(incomingPayment.connectionId) - ctx.params.id = incomingPayment.connectionId - await expect(connectionMiddleware(ctx, next)).resolves.toBeUndefined() - expect(next).toHaveBeenCalled() - expect(ctx.incomingPayment).toEqual(incomingPayment) - }) -}) diff --git a/packages/backend/src/open_payments/connection/middleware.ts b/packages/backend/src/open_payments/connection/middleware.ts deleted file mode 100644 index d517f4da5d..0000000000 --- a/packages/backend/src/open_payments/connection/middleware.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AppContext } from '../../app' -import { IncomingPayment } from '../payment/incoming/model' - -export interface ConnectionContext extends AppContext { - incomingPayment: IncomingPayment -} - -export const connectionMiddleware = async ( - ctx: Omit & { - incomingPayment: Partial - }, - next: () => Promise -): Promise => { - const incomingPaymentService = await ctx.container.use( - 'incomingPaymentService' - ) - const incomingPayment = await incomingPaymentService.getByConnection( - ctx.params.id - ) - if (!incomingPayment) return ctx.throw(404) - ctx.incomingPayment = incomingPayment - await next() -} From 2596f56f49ed5914533cade6f3a749fe98b1ab1e Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:55:55 -0400 Subject: [PATCH 03/39] fix(backend): rm get connection from receiver get --- .../src/open_payments/receiver/model.ts | 1 + .../open_payments/receiver/service.test.ts | 112 +----------------- .../src/open_payments/receiver/service.ts | 58 +-------- packages/backend/src/tests/outgoingPayment.ts | 1 + 4 files changed, 9 insertions(+), 163 deletions(-) diff --git a/packages/backend/src/open_payments/receiver/model.ts b/packages/backend/src/open_payments/receiver/model.ts index 03f17dc0cf..8bd228db5a 100644 --- a/packages/backend/src/open_payments/receiver/model.ts +++ b/packages/backend/src/open_payments/receiver/model.ts @@ -34,6 +34,7 @@ type ReceiverIncomingPayment = Readonly< > export class Receiver extends ConnectionBase { + // TODO: remove? still called 1 place static fromConnection( connection: OpenPaymentsConnection ): Receiver | undefined { diff --git a/packages/backend/src/open_payments/receiver/service.test.ts b/packages/backend/src/open_payments/receiver/service.test.ts index ab6bfc24c4..35ab5cdec3 100644 --- a/packages/backend/src/open_payments/receiver/service.test.ts +++ b/packages/backend/src/open_payments/receiver/service.test.ts @@ -10,7 +10,7 @@ import { mockWalletAddress, Grant as OpenPaymentsGrant, GrantRequest, - mockIncomingPaymentWithConnection + mockIncomingPaymentWithPaymentMethods } from '@interledger/open-payments' import { URL } from 'url' import { v4 as uuid } from 'uuid' @@ -31,7 +31,6 @@ import { GrantService } from '../grant/service' import { WalletAddressService } from '../wallet_address/service' import { Amount, parseAmount } from '../amount' import { RemoteIncomingPaymentService } from '../payment/incoming_remote/service' -import { Connection } from '../connection/model' import { IncomingPaymentError } from '../payment/incoming/errors' import { IncomingPaymentService } from '../payment/incoming/service' import { createAsset } from '../../tests/asset' @@ -78,103 +77,6 @@ describe('Receiver Service', (): void => { }) describe('get', () => { - describe('connections', () => { - const CONNECTION_PATH = 'connections' - - test('resolves local connection', async () => { - const walletAddress = await createWalletAddress(deps, { - mockServerPort: Config.openPaymentsPort - }) - const { connectionId } = await createIncomingPayment(deps, { - walletAddressId: walletAddress.id - }) - - const localUrl = `${Config.openPaymentsUrl}/${CONNECTION_PATH}/${connectionId}` - - const clientGetConnectionSpy = jest.spyOn( - openPaymentsClient.ilpStreamConnection, - 'get' - ) - - await expect(receiverService.get(localUrl)).resolves.toEqual({ - assetCode: walletAddress.asset.code, - assetScale: walletAddress.asset.scale, - incomingAmount: undefined, - receivedAmount: undefined, - ilpAddress: expect.any(String), - sharedSecret: expect.any(Buffer), - expiresAt: undefined - }) - expect(clientGetConnectionSpy).not.toHaveBeenCalled() - }) - - test('resolves remote connection', async () => { - const walletAddress = await createWalletAddress(deps) - const incomingPayment = await createIncomingPayment(deps, { - walletAddressId: walletAddress.id - }) - - const remoteUrl = new URL( - `${walletAddress.url}/${CONNECTION_PATH}/${incomingPayment.connectionId}` - ) - - const connection = connectionService.get(incomingPayment) - - assert(connection instanceof Connection) - - const clientGetConnectionSpy = jest - .spyOn(openPaymentsClient.ilpStreamConnection, 'get') - .mockImplementationOnce(async () => connection.toOpenPaymentsType()) - - await expect(receiverService.get(remoteUrl.href)).resolves.toEqual({ - assetCode: walletAddress.asset.code, - assetScale: walletAddress.asset.scale, - incomingAmount: undefined, - receivedAmount: undefined, - ilpAddress: expect.any(String), - sharedSecret: expect.any(Buffer), - expiresAt: undefined - }) - expect(clientGetConnectionSpy).toHaveBeenCalledWith({ - url: remoteUrl.href - }) - }) - - test('returns undefined for unknown local connection', async (): Promise => { - const walletAddress = await createWalletAddress(deps) - - await expect( - receiverService.get( - `${walletAddress.url}/${CONNECTION_PATH}/${uuid()}` - ) - ).resolves.toBeUndefined() - }) - - test('returns undefined when fetching remote connection throws', async (): Promise => { - const walletAddress = await createWalletAddress(deps) - const incomingPayment = await createIncomingPayment(deps, { - walletAddressId: walletAddress.id - }) - - const remoteUrl = new URL( - `${walletAddress.url}/${CONNECTION_PATH}/${incomingPayment.connectionId}` - ) - - const clientGetConnectionSpy = jest - .spyOn(openPaymentsClient.ilpStreamConnection, 'get') - .mockImplementationOnce(async () => { - throw new Error('Could not get connection') - }) - - await expect( - receiverService.get(remoteUrl.href) - ).resolves.toBeUndefined() - expect(clientGetConnectionSpy).toHaveBeenCalledWith({ - url: remoteUrl.href - }) - }) - }) - describe('incoming payments', () => { test('resolves local incoming payment', async () => { const walletAddress = await createWalletAddress(deps, { @@ -189,11 +91,6 @@ describe('Receiver Service', (): void => { } }) - const clientGetIncomingPaymentSpy = jest.spyOn( - openPaymentsClient.ilpStreamConnection, - 'get' - ) - await expect( receiverService.get(incomingPayment.getUrl(walletAddress)) ).resolves.toEqual({ @@ -213,7 +110,6 @@ describe('Receiver Service', (): void => { createdAt: new Date(incomingPayment.createdAt) } }) - expect(clientGetIncomingPaymentSpy).not.toHaveBeenCalled() }) describe.each` @@ -272,7 +168,7 @@ describe('Receiver Service', (): void => { walletAddress = mockWalletAddress({ authServer }) - incomingPayment = mockIncomingPaymentWithConnection({ + incomingPayment = mockIncomingPaymentWithPaymentMethods({ id: `${walletAddress.id}/incoming-payments/${uuid()}`, walletAddress: walletAddress.id }) @@ -516,7 +412,7 @@ describe('Receiver Service', (): void => { `( 'creates receiver from remote incoming payment ($#)', async ({ metadata, expiresAt, incomingAmount }): Promise => { - const incomingPayment = mockIncomingPaymentWithConnection({ + const incomingPayment = mockIncomingPaymentWithPaymentMethods({ metadata, expiresAt, incomingAmount @@ -540,7 +436,7 @@ describe('Receiver Service', (): void => { expect(receiver).toEqual({ assetCode: incomingPayment.receivedAmount.assetCode, assetScale: incomingPayment.receivedAmount.assetScale, - ilpAddress: incomingPayment.ilpStreamConnection?.ilpAddress, + ilpAddress: incomingPayment.methods[0].ilpAddress, sharedSecret: expect.any(Buffer), incomingPayment: { id: incomingPayment.id, diff --git a/packages/backend/src/open_payments/receiver/service.ts b/packages/backend/src/open_payments/receiver/service.ts index 80d6377a63..bb461dfeab 100644 --- a/packages/backend/src/open_payments/receiver/service.ts +++ b/packages/backend/src/open_payments/receiver/service.ts @@ -1,7 +1,6 @@ import { AuthenticatedClient, IncomingPayment as OpenPaymentsIncomingPayment, - ILPStreamConnection as OpenPaymentsConnection, isPendingGrant, AccessType, AccessAction @@ -46,7 +45,6 @@ interface ServiceDependencies extends BaseService { remoteIncomingPaymentService: RemoteIncomingPaymentService } -const CONNECTION_URL_REGEX = /\/connections\/(.){36}$/ const INCOMING_PAYMENT_URL_REGEX = /(?^(.)+)\/incoming-payments\/(?(.){36}$)/ @@ -141,59 +139,9 @@ async function getReceiver( deps: ServiceDependencies, url: string ): Promise { - if (url.match(CONNECTION_URL_REGEX)) { - const connection = await getConnection(deps, url) - if (connection) { - return Receiver.fromConnection(connection) - } - } else { - const incomingPayment = await getIncomingPayment(deps, url) - if (incomingPayment) { - return Receiver.fromIncomingPayment(incomingPayment) - } - } -} - -async function getLocalConnection( - deps: ServiceDependencies, - url: string -): Promise { - const incomingPayment = await deps.incomingPaymentService.getByConnection( - url.slice(-36) - ) - - if (!incomingPayment) { - return - } - - const connection = deps.connectionService.get(incomingPayment) - - if (!connection) { - return - } - - return connection.toOpenPaymentsType() -} - -async function getConnection( - deps: ServiceDependencies, - url: string -): Promise { - try { - if (url.startsWith(`${deps.openPaymentsUrl}/connections/`)) { - return await getLocalConnection(deps, url) - } - - return await deps.openPaymentsClient.ilpStreamConnection.get({ - url - }) - } catch (error) { - deps.logger.error( - { errorMessage: error instanceof Error && error.message }, - 'Could not get connection' - ) - - return undefined + const incomingPayment = await getIncomingPayment(deps, url) + if (incomingPayment) { + return Receiver.fromIncomingPayment(incomingPayment) } } diff --git a/packages/backend/src/tests/outgoingPayment.ts b/packages/backend/src/tests/outgoingPayment.ts index 35e4145f91..957a60d623 100644 --- a/packages/backend/src/tests/outgoingPayment.ts +++ b/packages/backend/src/tests/outgoingPayment.ts @@ -28,6 +28,7 @@ export async function createOutgoingPayment( const outgoingPaymentService = await deps.use('outgoingPaymentService') const receiverService = await deps.use('receiverService') if (options.validDestination === false) { + // TODO: mock differently? receiver.get not longer calls receiver.fromConnection. this is the only remaining caller of fromConnection const streamServer = await deps.use('streamServer') const { ilpAddress, sharedSecret } = streamServer.generateCredentials() jest.spyOn(receiverService, 'get').mockResolvedValueOnce( From 388a8ac0a27ac2062a178116340944e33cef123c Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:19:54 -0400 Subject: [PATCH 04/39] refactor(backend): toOpenPaymentsType --- .../src/open_payments/connection/service.ts | 15 ++-- .../payment/incoming/model.test.ts | 55 +++--------- .../open_payments/payment/incoming/model.ts | 47 +++++----- .../payment/incoming/routes.test.ts | 87 ++++++++++--------- .../open_payments/payment/incoming/routes.ts | 21 ++--- 5 files changed, 92 insertions(+), 133 deletions(-) diff --git a/packages/backend/src/open_payments/connection/service.ts b/packages/backend/src/open_payments/connection/service.ts index 50ce11852f..ffbbe87e06 100644 --- a/packages/backend/src/open_payments/connection/service.ts +++ b/packages/backend/src/open_payments/connection/service.ts @@ -2,7 +2,11 @@ import { StreamServer } from '@interledger/stream-receiver' import { BaseService } from '../../shared/baseService' import { IncomingPayment } from '../payment/incoming/model' -import { Connection } from './model' +import { IlpAddress } from 'ilp-packet' +export interface Connection { + ilpAddress: IlpAddress + sharedSecret: Buffer +} export interface ConnectionService { get(payment: IncomingPayment): Connection | undefined @@ -44,11 +48,10 @@ function getConnection( scale: payment.asset.scale } }) - return Connection.fromPayment({ - payment, - credentials, - openPaymentsUrl: deps.openPaymentsUrl - }) + return { + ilpAddress: credentials.ilpAddress, + sharedSecret: credentials.sharedSecret + } } function getConnectionUrl( diff --git a/packages/backend/src/open_payments/payment/incoming/model.test.ts b/packages/backend/src/open_payments/payment/incoming/model.test.ts index 1a3197b9b3..14a48d223e 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.test.ts @@ -6,7 +6,7 @@ import { AppServices } from '../../../app' import { createIncomingPayment } from '../../../tests/incomingPayment' import { createWalletAddress } from '../../../tests/walletAddress' import { truncateTables } from '../../../tests/tableManager' -import { Connection } from '../../connection/model' +import { Connection } from '../../connection/service' import { serializeAmount } from '../../amount' import { IlpAddress } from 'ilp-packet' import { IncomingPayment } from './model' @@ -54,33 +54,6 @@ describe('Incoming Payment Model', (): void => { }) }) - test('returns incoming payment with connection as string', async () => { - const walletAddress = await createWalletAddress(deps) - const incomingPayment = await createIncomingPayment(deps, { - walletAddressId: walletAddress.id, - metadata: { description: 'my payment' } - }) - - const connection = `${config.openPaymentsUrl}/connections/${incomingPayment.connectionId}` - - expect( - incomingPayment.toOpenPaymentsType(walletAddress, connection) - ).toEqual({ - id: `${walletAddress.url}${IncomingPayment.urlPath}/${incomingPayment.id}`, - walletAddress: walletAddress.url, - completed: incomingPayment.completed, - receivedAmount: serializeAmount(incomingPayment.receivedAmount), - incomingAmount: incomingPayment.incomingAmount - ? serializeAmount(incomingPayment.incomingAmount) - : undefined, - expiresAt: incomingPayment.expiresAt.toISOString(), - metadata: incomingPayment.metadata ?? undefined, - updatedAt: incomingPayment.updatedAt.toISOString(), - createdAt: incomingPayment.createdAt.toISOString(), - ilpStreamConnection: connection - }) - }) - test('returns incoming payment with connection as object', async () => { const walletAddress = await createWalletAddress(deps) const incomingPayment = await createIncomingPayment(deps, { @@ -88,14 +61,10 @@ describe('Incoming Payment Model', (): void => { metadata: { description: 'my payment' } }) - const connection = Connection.fromPayment({ - payment: incomingPayment, - openPaymentsUrl: config.openPaymentsUrl, - credentials: { - ilpAddress: 'test.ilp' as IlpAddress, - sharedSecret: Buffer.from('') - } - }) + const connection: Connection = { + ilpAddress: 'test.ilp' as IlpAddress, + sharedSecret: Buffer.from('') + } expect( incomingPayment.toOpenPaymentsType(walletAddress, connection) @@ -111,13 +80,13 @@ describe('Incoming Payment Model', (): void => { metadata: incomingPayment.metadata ?? undefined, updatedAt: incomingPayment.updatedAt.toISOString(), createdAt: incomingPayment.createdAt.toISOString(), - ilpStreamConnection: { - id: `${config.openPaymentsUrl}/connections/${incomingPayment.connectionId}`, - ilpAddress: 'test.ilp', - sharedSecret: expect.any(String), - assetCode: incomingPayment.asset.code, - assetScale: incomingPayment.asset.scale - } + methods: [ + { + type: 'ilp', + ilpAddress: 'test.ilp', + sharedSecret: expect.any(String) + } + ] }) }) }) diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index 2ea7a4709b..1cd11aba4a 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -2,7 +2,8 @@ import { Model, ModelOptions, QueryContext } from 'objection' import { v4 as uuid } from 'uuid' import { Amount, AmountJSON, serializeAmount } from '../../amount' -import { Connection } from '../../connection/model' +// import { Connection } from '../../connection/model' +import { Connection } from '../../connection/service' import { WalletAddress, WalletAddressSubresource @@ -13,9 +14,11 @@ import { ConnectorAccount } from '../../../payment-method/ilp/connector/core/raf import { WebhookEvent } from '../../../webhook/model' import { IncomingPayment as OpenPaymentsIncomingPayment, - IncomingPaymentWithConnection as OpenPaymentsIncomingPaymentWithConnection, - IncomingPaymentWithConnectionUrl as OpenPaymentsIncomingPaymentWithConnectionUrl + IncomingPaymentWithPaymentMethods as OpenPaymentsIncomingPaymentWithPaymentMethod + // IncomingPaymentWithConnection as OpenPaymentsIncomingPaymentWithConnection, + // IncomingPaymentWithConnectionUrl as OpenPaymentsIncomingPaymentWithConnectionUrl } from '@interledger/open-payments' +import base64url from 'base64url' export enum IncomingPaymentEventType { IncomingPaymentCreated = 'incoming_payment.created', @@ -216,25 +219,18 @@ export class IncomingPayment public toOpenPaymentsType( walletAddress: WalletAddress, ilpStreamConnection: Connection - ): OpenPaymentsIncomingPaymentWithConnection + ): OpenPaymentsIncomingPaymentWithPaymentMethod public toOpenPaymentsType( - walletAddress: WalletAddress, - ilpStreamConnection: string - ): OpenPaymentsIncomingPaymentWithConnectionUrl - public toOpenPaymentsType( - walletAddress: WalletAddress, - ilpStreamConnection?: Connection | string - ): - | OpenPaymentsIncomingPaymentWithConnection - | OpenPaymentsIncomingPaymentWithConnectionUrl + paymentPointer: WalletAddress, + ilpStreamConnection?: Connection + ): OpenPaymentsIncomingPayment | OpenPaymentsIncomingPaymentWithPaymentMethod public toOpenPaymentsType( walletAddress: WalletAddress, - ilpStreamConnection?: Connection | string + ilpStreamConnection?: Connection ): | OpenPaymentsIncomingPayment - | OpenPaymentsIncomingPaymentWithConnection - | OpenPaymentsIncomingPaymentWithConnectionUrl { + | OpenPaymentsIncomingPaymentWithPaymentMethod { const baseIncomingPayment: OpenPaymentsIncomingPayment = { id: this.getUrl(walletAddress), walletAddress: walletAddress.url, @@ -249,21 +245,20 @@ export class IncomingPayment expiresAt: this.expiresAt.toISOString() } - if (!ilpStreamConnection) { - return baseIncomingPayment - } - - if (typeof ilpStreamConnection === 'string') { + if (ilpStreamConnection) { return { ...baseIncomingPayment, - ilpStreamConnection + methods: [ + { + type: 'ilp', + ilpAddress: ilpStreamConnection.ilpAddress, + sharedSecret: base64url(ilpStreamConnection.sharedSecret) + } + ] } } - return { - ...baseIncomingPayment, - ilpStreamConnection: ilpStreamConnection.toOpenPaymentsType() - } + return baseIncomingPayment } public toPublicOpenPaymentsType(): Pick< diff --git a/packages/backend/src/open_payments/payment/incoming/routes.test.ts b/packages/backend/src/open_payments/payment/incoming/routes.test.ts index 455cc259f0..934daee8cb 100644 --- a/packages/backend/src/open_payments/payment/incoming/routes.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/routes.test.ts @@ -27,6 +27,7 @@ import { createWalletAddress } from '../../../tests/walletAddress' import { Asset } from '../../../asset/model' import { IncomingPaymentError, errorToCode, errorToMessage } from './errors' import { IncomingPaymentService } from './service' +import { IncomingPaymentWithPaymentMethods as OpenPaymentsIncomingPaymentWithPaymentMethods } from '@interledger/open-payments' describe('Incoming Payment Routes', (): void => { let deps: IocContract @@ -92,30 +93,33 @@ describe('Incoming Payment Routes', (): void => { get: (ctx) => incomingPaymentRoutes.get(ctx as ReadContextWithAuthenticatedStatus), getBody: (incomingPayment, list) => { - return { - id: incomingPayment.getUrl(walletAddress), - walletAddress: walletAddress.url, - completed: false, - incomingAmount: - incomingPayment.incomingAmount && - serializeAmount(incomingPayment.incomingAmount), - expiresAt: incomingPayment.expiresAt.toISOString(), - createdAt: incomingPayment.createdAt.toISOString(), - updatedAt: incomingPayment.updatedAt.toISOString(), - receivedAmount: serializeAmount(incomingPayment.receivedAmount), - metadata: incomingPayment.metadata, - ilpStreamConnection: list - ? `${config.openPaymentsUrl}/connections/${incomingPayment.connectionId}` - : { - id: `${config.openPaymentsUrl}/connections/${incomingPayment.connectionId}`, - ilpAddress: expect.stringMatching( - /^test\.rafiki\.[a-zA-Z0-9_-]{95}$/ - ), - sharedSecret: expect.stringMatching(/^[a-zA-Z0-9-_]{43}$/), - assetCode: incomingPayment.receivedAmount.assetCode, - assetScale: incomingPayment.receivedAmount.assetScale - } + const response: Partial = + { + id: incomingPayment.getUrl(walletAddress), + walletAddress: walletAddress.url, + completed: false, + incomingAmount: + incomingPayment.incomingAmount && + serializeAmount(incomingPayment.incomingAmount), + expiresAt: incomingPayment.expiresAt.toISOString(), + createdAt: incomingPayment.createdAt.toISOString(), + updatedAt: incomingPayment.updatedAt.toISOString(), + receivedAmount: serializeAmount(incomingPayment.receivedAmount), + metadata: incomingPayment.metadata + } + + if (!list) { + response.methods = [ + { + type: 'ilp', + ilpAddress: expect.stringMatching( + /^test\.rafiki\.[a-zA-Z0-9_-]{95}$/ + ), + sharedSecret: expect.any(String) + } + ] } + return response }, list: (ctx) => incomingPaymentRoutes.list(ctx), urlPath: IncomingPayment.urlPath @@ -205,21 +209,18 @@ describe('Incoming Payment Routes', (): void => { expiresAt: expiresAt ? new Date(expiresAt) : undefined, client }) + // TODO: this fails because the response has 'methods' (which is correct) and the spec has + // '#/components/schemas/incoming-payment' with additionalProperties = false. + // We are trying to include payment method via an allOf but this doesn't supercede the + // additionalProperties = false. Appears we need to tweak the spec. Should we also add + // jestOpenAPI to open-payments lib use .toSatisfyApiSpec() to verfiy in client tests? expect(ctx.response).toSatisfyApiSpec() const incomingPaymentId = ( (ctx.response.body as Record)['id'] as string ) .split('/') .pop() - const connectionId = ( - ( - (ctx.response.body as Record)[ - 'ilpStreamConnection' - ] as Record - )['id'] as string - ) - .split('/') - .pop() + expect(ctx.response.body).toEqual({ id: `${walletAddress.url}/incoming-payments/${incomingPaymentId}`, walletAddress: walletAddress.url, @@ -234,15 +235,15 @@ describe('Incoming Payment Routes', (): void => { }, metadata, completed: false, - ilpStreamConnection: { - id: `${config.openPaymentsUrl}/connections/${connectionId}`, - ilpAddress: expect.stringMatching( - /^test\.rafiki\.[a-zA-Z0-9_-]{95}$/ - ), - sharedSecret: expect.any(String), - assetCode: asset.code, - assetScale: asset.scale - } + methods: [ + { + type: 'ilp', + ilpAddress: expect.stringMatching( + /^test\.rafiki\.[a-zA-Z0-9_-]{95}$/ + ), + sharedSecret: expect.any(String) + } + ] }) } ) @@ -297,7 +298,7 @@ describe('Incoming Payment Routes', (): void => { describe('get unauthenticated incoming payment', (): void => { test('Can get incoming payment with public fields', async (): Promise => { const incomingPayment = await createIncomingPayment(deps, { - paymentPointerId: paymentPointer.id, + walletAddressId: walletAddress.id, expiresAt, incomingAmount, metadata @@ -312,7 +313,7 @@ describe('Incoming Payment Routes', (): void => { params: { id: incomingPayment.id }, - paymentPointer + walletAddress }) ctx.authenticated = false diff --git a/packages/backend/src/open_payments/payment/incoming/routes.ts b/packages/backend/src/open_payments/payment/incoming/routes.ts index 188afae0af..a5bd0eda8e 100644 --- a/packages/backend/src/open_payments/payment/incoming/routes.ts +++ b/packages/backend/src/open_payments/payment/incoming/routes.ts @@ -18,13 +18,12 @@ import { } from './errors' import { AmountJSON, parseAmount } from '../../amount' import { listSubresource } from '../../wallet_address/routes' -import { Connection } from '../../connection/model' +import { Connection } from '../../connection/service' import { ConnectionService } from '../../connection/service' import { AccessAction, IncomingPayment as OpenPaymentsIncomingPayment, - IncomingPaymentWithConnection as OpenPaymentsIncomingPaymentWithConnection, - IncomingPaymentWithConnectionUrl as OpenPaymentsIncomingPaymentWithConnectionUrl + IncomingPaymentWithPaymentMethods } from '@interledger/open-payments' import { WalletAddress } from '../../wallet_address/model' @@ -81,7 +80,7 @@ async function getIncomingPaymentPublic( const incomingPayment = await deps.incomingPaymentService.get({ id: ctx.params.id, client: ctx.accessAction === AccessAction.Read ? ctx.client : undefined, - paymentPointerId: ctx.paymentPointer.id + walletAddressId: ctx.walletAddress.id }) ctx.body = incomingPayment?.toPublicOpenPaymentsType() } catch (err) { @@ -185,12 +184,7 @@ async function listIncomingPayments( await listSubresource({ ctx, getWalletAddressPage: deps.incomingPaymentService.getWalletAddressPage, - toBody: (payment) => - incomingPaymentToBody( - ctx.walletAddress, - payment, - deps.connectionService.getUrl(payment) - ) + toBody: (payment) => incomingPaymentToBody(ctx.walletAddress, payment) }) } catch (err) { if (err instanceof Koa.HttpError) { @@ -202,10 +196,7 @@ async function listIncomingPayments( function incomingPaymentToBody( walletAddress: WalletAddress, incomingPayment: IncomingPayment, - ilpStreamConnection?: Connection | string -): - | OpenPaymentsIncomingPayment - | OpenPaymentsIncomingPaymentWithConnection - | OpenPaymentsIncomingPaymentWithConnectionUrl { + ilpStreamConnection?: Connection +): OpenPaymentsIncomingPayment | IncomingPaymentWithPaymentMethods { return incomingPayment.toOpenPaymentsType(walletAddress, ilpStreamConnection) } From a7beb8a44d45f95ee775f9d6fa538a58752d6827 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:20:45 -0400 Subject: [PATCH 05/39] refactor(backend): rm getByConnection --- .../payment/incoming/service.test.ts | 30 ------------------- .../open_payments/payment/incoming/service.ts | 16 +--------- 2 files changed, 1 insertion(+), 45 deletions(-) diff --git a/packages/backend/src/open_payments/payment/incoming/service.test.ts b/packages/backend/src/open_payments/payment/incoming/service.test.ts index a2ad7104fc..3c69334e28 100644 --- a/packages/backend/src/open_payments/payment/incoming/service.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/service.test.ts @@ -660,34 +660,4 @@ describe('Incoming Payment Service', (): void => { }) }) }) - describe('getByConnection', (): void => { - let incomingPayment: IncomingPayment - - beforeEach(async (): Promise => { - incomingPayment = (await incomingPaymentService.create({ - walletAddressId, - incomingAmount: { - value: BigInt(123), - assetCode: asset.code, - assetScale: asset.scale - }, - expiresAt: new Date(Date.now() + 30_000), - metadata: { - description: 'Test incoming payment', - externalRef: '#123' - } - })) as IncomingPayment - assert.ok(!isIncomingPaymentError(incomingPayment)) - }) - test('returns incoming payment id on correct connectionId', async (): Promise => { - await expect( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - incomingPaymentService.getByConnection(incomingPayment.connectionId!) - ).resolves.toEqual(incomingPayment) - }) - test('returns undefined on incorrect connectionId', async (): Promise => { - await expect(incomingPaymentService.getByConnection(uuid())).resolves - .toBeUndefined - }) - }) }) diff --git a/packages/backend/src/open_payments/payment/incoming/service.ts b/packages/backend/src/open_payments/payment/incoming/service.ts index 4279dc186f..607de37a1a 100644 --- a/packages/backend/src/open_payments/payment/incoming/service.ts +++ b/packages/backend/src/open_payments/payment/incoming/service.ts @@ -40,7 +40,6 @@ export interface IncomingPaymentService ): Promise complete(id: string): Promise processNext(): Promise - getByConnection(connectionId: string): Promise } export interface ServiceDependencies extends BaseService { @@ -65,9 +64,7 @@ export async function createIncomingPaymentService( create: (options, trx) => createIncomingPayment(deps, options, trx), complete: (id) => completeIncomingPayment(deps, id), getWalletAddressPage: (options) => getWalletAddressPage(deps, options), - processNext: () => processNextIncomingPayment(deps), - getByConnection: (connectionId) => - getIncomingPaymentByConnection(deps, connectionId) + processNext: () => processNextIncomingPayment(deps) } } @@ -82,17 +79,6 @@ async function getIncomingPayment( else return } -async function getIncomingPaymentByConnection( - deps: ServiceDependencies, - connectionId: string -): Promise { - const incomingPayment = await IncomingPayment.query(deps.knex) - .findOne({ connectionId }) - .withGraphFetched('[asset, walletAddress]') - if (incomingPayment) return await addReceivedAmount(deps, incomingPayment) - else return -} - async function createIncomingPayment( deps: ServiceDependencies, { From 9c8bf5fe1b3e751e5f49062f14ff010946bc4b1f Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:22:17 -0400 Subject: [PATCH 06/39] chore: update openapi spec --- openapi/auth-server.yaml | 34 ++-- openapi/resource-server.yaml | 164 +++++++++++------- openapi/schemas.yaml | 9 +- .../payment/incoming/routes.test.ts | 5 - pnpm-lock.yaml | 4 +- 5 files changed, 123 insertions(+), 93 deletions(-) diff --git a/openapi/auth-server.yaml b/openapi/auth-server.yaml index 6500db7dc0..66129f2f4d 100644 --- a/openapi/auth-server.yaml +++ b/openapi/auth-server.yaml @@ -10,7 +10,7 @@ info: contact: email: tech@interledger.org servers: - - url: 'https://rafiki.money/auth' + - url: 'https://auth.rafiki.money' tags: - name: grant description: Grant operations @@ -49,29 +49,29 @@ paths: Interaction instructions: value: interact: - redirect: 'https://rafiki.money/auth/4CF492MLVMSW9MKMXKHQ' + redirect: 'https://auth.rafiki.money/4CF492MLVMSW9MKMXKHQ' finish: 4105340a-05eb-4290-8739-f9e2b463bfa7 continue: access_token: value: 33OMUKMKSKU80UPRY5NM - uri: 'https://rafiki.money/auth/continue/4CF492MLVMSW9MKMXKHQ' + uri: 'https://auth.rafiki.money/continue/4CF492MLVMSW9MKMXKHQ' wait: 30 Grant: value: access_token: value: OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0 - manage: 'https://rafiki.money/auth/token/dd17a202-9982-4ed9-ae31-564947fb6379' + manage: 'https://auth.rafiki.money/token/dd17a202-9982-4ed9-ae31-564947fb6379' expires_in: 3600 access: - type: incoming-payment actions: - create - read - identifier: 'https://rafiki.money/bob' + identifier: 'https://ilp.rafiki.money/bob' continue: access_token: value: 33OMUKMKSKU80UPRY5NM - uri: 'https://rafiki.money/auth/continue/4CF492MLVMSW9MKMXKHQ' + uri: 'https://auth.rafiki.money/continue/4CF492MLVMSW9MKMXKHQ' '400': description: Bad Request '401': @@ -108,9 +108,9 @@ paths: actions: - create - read - identifier: 'https://rafiki.money/alice' + identifier: 'https://ilp.rafiki.money/alice' limits: - receiver: 'https://rafiki.money/connections/45a0d0ee-26dc-4c66-89e0-01fbf93156f7' + receiver: 'https://ilp.rafiki.money/connections/45a0d0ee-26dc-4c66-89e0-01fbf93156f7' interval: 'R12/2019-08-24T14:15:22Z/P1M' debitAmount: value: '500' @@ -132,7 +132,7 @@ paths: actions: - create - read - identifier: 'http://rafiki.money/bob' + identifier: 'http://ilp.rafiki.money/bob' client: 'https://webmonize.com/.well-known/pay' description: '' description: Make a new grant request @@ -169,16 +169,16 @@ paths: value: access_token: value: OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0 - manage: 'https://rafiki.money/auth/token/dd17a202-9982-4ed9-ae31-564947fb6379' + manage: 'https://auth.rafiki.money/token/dd17a202-9982-4ed9-ae31-564947fb6379' expires_in: 3600 access: - type: outgoing-payment actions: - create - read - identifier: 'https://rafiki.money/alice' + identifier: 'https://ilp.rafiki.money/alice' limits: - receiver: 'https://rafiki.money/bob/incoming-payments/48884225-b393-4872-90de-1b737e2491c2' + receiver: 'https://ilp.rafiki.money/bob/incoming-payments/48884225-b393-4872-90de-1b737e2491c2' interval: 'R12/2019-08-24T14:15:22Z/P1M' debitAmount: value: '500' @@ -187,14 +187,14 @@ paths: continue: access_token: value: 33OMUKMKSKU80UPRY5NM - uri: 'https://rafiki.money/auth/continue/4CF492MLVMSW9MKMXKHQ' + uri: 'https://auth.rafiki.money/continue/4CF492MLVMSW9MKMXKHQ' wait: 30 Continuing During Pending Interaction: value: continue: access_token: value: 33OMUKMKSKU80UPRY5NM - uri: 'https://rafiki.money/auth/continue/4CF492MLVMSW9MKMXKHQ' + uri: 'https://auth.rafiki.money/continue/4CF492MLVMSW9MKMXKHQ' wait: 30 '400': description: Bad Request @@ -264,17 +264,17 @@ paths: value: access_token: value: OZB8CDFONP219RP1LT0OS9M2PMHKUR64TB8N6BW7 - manage: 'https://rafiki.money/auth/token/8f69de01-5bf9-4603-91ed-eeca101081f1' + manage: 'https://auth.rafiki.money/token/8f69de01-5bf9-4603-91ed-eeca101081f1' expires_in: 3600 access: - type: outgoing-payment actions: - create - read - identifier: 'https://rafiki.money/alice' + identifier: 'https://ilp.rafiki.money/alice' limits: interval: 'R12/2019-08-24T14:15:22Z/P1M' - receiver: 'https://rafiki.money/bob/incoming-payments/48884225-b393-4872-90de-1b737e2491c2' + receiver: 'https://ilp.rafiki.money/bob/incoming-payments/48884225-b393-4872-90de-1b737e2491c2' debitAmount: value: '500' assetCode: USD diff --git a/openapi/resource-server.yaml b/openapi/resource-server.yaml index fb198a3eb6..55edbfa979 100644 --- a/openapi/resource-server.yaml +++ b/openapi/resource-server.yaml @@ -28,8 +28,10 @@ info: contact: email: tech@interledger.org servers: - - url: 'https://rafiki.money' - description: 'Server for wallet address subresources (ie https://rafiki.money/alice)' + - url: 'https://ilp.rafiki.money' + description: 'Server for wallet address subresources (ie https://ilp.rafiki.money/alice)' + - url: 'https://ilp.rafiki.money/.well-known/pay' + description: 'Server for when Payment Pointer has no pathname (ie https://ilp.rafiki.money)' tags: - name: wallet-address description: wallet address operations @@ -53,13 +55,13 @@ paths: schema: $ref: '#/components/schemas/wallet-address' examples: - Get wallet address for $rafiki.money/alice: + Get wallet address for $ilp.rafiki.money/alice: value: - id: 'https://rafiki.money/alice' + id: 'https://ilp.rafiki.money/alice' publicName: Alice assetCode: USD assetScale: 2 - authServer: 'https://rafiki.money/auth' + authServer: 'https://auth.rafiki.money' '404': description: Wallet Address Not Found operationId: get-wallet-address @@ -105,8 +107,8 @@ paths: examples: New Incoming Payment for $25: value: - id: 'https://rafiki.money/incoming-payments/08394f02-7b7b-45e2-b645-51d04e7c330c' - walletAddress: 'https://rafiki.money/alice/' + id: 'https://ilp.rafiki.money/incoming-payments/08394f02-7b7b-45e2-b645-51d04e7c330c' + walletAddress: 'https://ilp.rafiki.money/alice/' incomingAmount: value: '2500' assetCode: USD @@ -135,6 +137,8 @@ paths: type: object additionalProperties: false properties: + walletAddress: + $ref: ./schemas.yaml#/components/schemas/walletAddress incomingAmount: $ref: ./schemas.yaml#/components/schemas/amount description: The maximum amount that should be paid into the wallet address under this incoming payment. @@ -149,6 +153,7 @@ paths: examples: Create incoming payment for $25 to pay invoice INV2022-02-0137: value: + walletAddress: 'https://openpayments.guide/alice/' incomingAmount: value: '2500' assetCode: USD @@ -192,6 +197,7 @@ paths: type: array items: $ref: '#/components/schemas/incoming-payment' + additionalProperties: false examples: forward pagination: value: @@ -201,8 +207,8 @@ paths: hasPreviousPage: false hasNextPage: true result: - - id: 'https://rafiki.money/incoming-payments/016da9d5-c9a4-4c80-a354-86b915a04ff8' - walletAddress: 'https://rafiki.money/alice/' + - id: 'https://ilp.rafiki.money/incoming-payments/016da9d5-c9a4-4c80-a354-86b915a04ff8' + walletAddress: 'https://ilp.rafiki.money/alice/' incomingAmount: value: '250' assetCode: USD @@ -218,8 +224,8 @@ paths: createdAt: '2022-03-12T23:20:50.52Z' updatedAt: '2022-04-01T10:24:36.11Z' completed: true - - id: 'https://rafiki.money/incoming-payments/32abc219-3dc3-44ec-a225-790cacfca8fa' - walletAddress: 'https://rafiki.money/alice/' + - id: 'https://ilp.rafiki.money/incoming-payments/32abc219-3dc3-44ec-a225-790cacfca8fa' + walletAddress: 'https://ilp.rafiki.money/alice/' receivedAmount: value: '100' assetCode: USD @@ -238,8 +244,8 @@ paths: hasPreviousPage: true hasNextPage: false result: - - id: 'https://rafiki.money/incoming-payments/32abc219-3dc3-44ec-a225-790cacfca8fa' - walletAddress: 'https://rafiki.money/alice/' + - id: 'https://ilp.rafiki.money/incoming-payments/32abc219-3dc3-44ec-a225-790cacfca8fa' + walletAddress: 'https://ilp.rafiki.money/alice/' receivedAmount: value: '100' assetCode: USD @@ -250,8 +256,8 @@ paths: updatedAt: '2022-04-01T10:24:36.11Z' metadata: description: 'I love your website, Alice! Thanks for the great content' - - id: 'https://rafiki.money/incoming-payments/016da9d5-c9a4-4c80-a354-86b915a04ff8' - walletAddress: 'https://rafiki.money/alice/' + - id: 'https://ilp.rafiki.money/incoming-payments/016da9d5-c9a4-4c80-a354-86b915a04ff8' + walletAddress: 'https://ilp.rafiki.money/alice/' incomingAmount: value: '250' assetCode: USD @@ -273,6 +279,7 @@ paths: $ref: '#/components/responses/403' description: List all incoming payments on the wallet address parameters: + - $ref: '#/components/parameters/wallet-address' - $ref: '#/components/parameters/cursor' - $ref: '#/components/parameters/first' - $ref: '#/components/parameters/last' @@ -296,10 +303,10 @@ paths: examples: New Fixed Send Outgoing Payment for $25: value: - id: 'https://rafiki.money/outgoing-payments/8c68d3cc-0a0f-4216-98b4-4fa44a6c88cf' - walletAddress: 'https://rafiki.money/alice/' + id: 'https://ilp.rafiki.money/outgoing-payments/8c68d3cc-0a0f-4216-98b4-4fa44a6c88cf' + walletAddress: 'https://ilp.rafiki.money/alice/' failed: false - receiver: 'https://rafiki.money/bob/incoming-payments/48884225-b393-4872-90de-1b737e2491c2' + receiver: 'https://ilp.rafiki.money/bob/incoming-payments/48884225-b393-4872-90de-1b737e2491c2' debitAmount: value: '2600' assetCode: USD @@ -326,12 +333,15 @@ paths: examples: Create an outgoing payment based on a quote: value: - quoteId: 'https://rafiki.money/quotes/ab03296b-0c8b-4776-b94e-7ee27d868d4d' + walletAddress: 'https://ilp.rafiki.money/alice/' + quoteId: 'https://ilp.rafiki.money/quotes/ab03296b-0c8b-4776-b94e-7ee27d868d4d' metadata: externalRef: INV2022-02-0137 schema: type: object properties: + walletAddress: + $ref: ./schemas.yaml#/components/schemas/walletAddress quoteId: type: string format: uri @@ -342,6 +352,7 @@ paths: description: Additional metadata associated with the outgoing payment. (Optional) required: - quoteId + - walletAddress additionalProperties: false description: |- A subset of the outgoing payments schema is accepted as input to create a new outgoing payment. @@ -382,10 +393,10 @@ paths: hasPreviousPage: false hasNextPage: true result: - - id: 'https://rafiki.money/outgoing-payments/8c68d3cc-0a0f-4216-98b4-4fa44a6c88cf' - walletAddress: 'https://rafiki.money/alice/' + - id: 'https://ilp.rafiki.money/outgoing-payments/8c68d3cc-0a0f-4216-98b4-4fa44a6c88cf' + walletAddress: 'https://ilp.rafiki.money/alice/' failed: false - receiver: 'https://rafiki.money/aplusvideo/incoming-payments/45d495ad-b763-4882-88d7-aa14d261686e' + receiver: 'https://ilp.rafiki.money/aplusvideo/incoming-payments/45d495ad-b763-4882-88d7-aa14d261686e' receiveAmount: value: '2500' assetCode: USD @@ -403,10 +414,10 @@ paths: metadata: description: APlusVideo subscription externalRef: 'customer: 847458475' - - id: 'https://rafiki.money/outgoing-payments/0cffa5a4-58fd-4cc8-8e01-7145c72bf07c' - walletAddress: 'https://rafiki.money/alice/' + - id: 'https://ilp.rafiki.money/outgoing-payments/0cffa5a4-58fd-4cc8-8e01-7145c72bf07c' + walletAddress: 'https://ilp.rafiki.money/alice/' failed: false - receiver: 'https://rafiki.money/shoeshop/incoming-payments/2fe92c6f-ef0d-487c-8759-3784eae6bce9' + receiver: 'https://ilp.rafiki.money/shoeshop/incoming-payments/2fe92c6f-ef0d-487c-8759-3784eae6bce9' debitAmount: value: '7126' assetCode: USD @@ -432,10 +443,10 @@ paths: hasPreviousPage: true hasNextPage: false result: - - id: 'https://rafiki.money/outgoing-payments/0cffa5a4-58fd-4cc8-8e01-7145c72bf07c' - walletAddress: 'https://rafiki.money/alice/' + - id: 'https://ilp.rafiki.money/outgoing-payments/0cffa5a4-58fd-4cc8-8e01-7145c72bf07c' + walletAddress: 'https://ilp.rafiki.money/alice/' failed: false - receiver: 'https://rafiki.money/shoeshop/incoming-payments/2fe92c6f-ef0d-487c-8759-3784eae6bce9' + receiver: 'https://ilp.rafiki.money/shoeshop/incoming-payments/2fe92c6f-ef0d-487c-8759-3784eae6bce9' debitAmount: value: '7126' assetCode: USD @@ -453,10 +464,10 @@ paths: metadata: description: Thank you for your purchase at ShoeShop! externalRef: INV2022-8943756 - - id: 'https://rafiki.money/outgoing-payments/8c68d3cc-0a0f-4216-98b4-4fa44a6c88cf' - walletAddress: 'https://rafiki.money/alice/' + - id: 'https://ilp.rafiki.money/outgoing-payments/8c68d3cc-0a0f-4216-98b4-4fa44a6c88cf' + walletAddress: 'https://ilp.rafiki.money/alice/' failed: false - receiver: 'https://rafiki.money/aplusvideo/incoming-payments/45d495ad-b763-4882-88d7-aa14d261686e' + receiver: 'https://ilp.rafiki.money/aplusvideo/incoming-payments/45d495ad-b763-4882-88d7-aa14d261686e' receiveAmount: value: '2500' assetCode: USD @@ -480,6 +491,7 @@ paths: $ref: '#/components/responses/403' description: List all outgoing payments on the wallet address parameters: + - $ref: '#/components/parameters/wallet-address' - $ref: '#/components/parameters/cursor' - $ref: '#/components/parameters/first' - $ref: '#/components/parameters/last' @@ -503,9 +515,9 @@ paths: examples: New Fixed Send Quote for $25: value: - id: 'https://rafiki.money/quotes/8c68d3cc-0a0f-4216-98b4-4fa44a6c88cf' - walletAddress: 'https://rafiki.money/alice/' - receiver: 'https://rafiki.money/aplusvideo/incoming-payments/45d495ad-b763-4882-88d7-aa14d261686e' + id: 'https://ilp.rafiki.money/quotes/8c68d3cc-0a0f-4216-98b4-4fa44a6c88cf' + walletAddress: 'https://ilp.rafiki.money/alice/' + receiver: 'https://ilp.rafiki.money/aplusvideo/incoming-payments/45d495ad-b763-4882-88d7-aa14d261686e' debitAmount: value: '2500' assetCode: USD @@ -529,11 +541,13 @@ paths: examples: Create quote for an `receiver` that is an Incoming Payment with an `incomingAmount`: value: - receiver: 'https://rafiki.money/incoming-payments/37a0d0ee-26dc-4c66-89e0-01fbf93156f7' + walletAddress: 'https://ilp.rafiki.money/alice' + receiver: 'https://ilp.rafiki.money/incoming-payments/37a0d0ee-26dc-4c66-89e0-01fbf93156f7' method: ilp Create fixed-send amount quote for $25: value: - receiver: 'https://rafiki.money/incoming-payments/37a0d0ee-26dc-4c66-89e0-01fbf93156f7' + walletAddress: 'https://ilp.rafiki.money/alice' + receiver: 'https://ilp.rafiki.money/incoming-payments/37a0d0ee-26dc-4c66-89e0-01fbf93156f7' method: ilp debitAmount: value: '2500' @@ -541,7 +555,8 @@ paths: assetScale: 2 Create fixed-receive amount quote for $25: value: - receiver: 'https://rafiki.money/incoming-payments/37a0d0ee-26dc-4c66-89e0-01fbf93156f7' + walletAddress: 'https://ilp.rafiki.money/alice' + receiver: 'https://ilp.rafiki.money/incoming-payments/37a0d0ee-26dc-4c66-89e0-01fbf93156f7' method: ilp receiveAmount: value: '2500' @@ -551,16 +566,21 @@ paths: oneOf: - description: Create quote for an `receiver` that is an Incoming Payment with an `incomingAmount` properties: + walletAddress: + $ref: ./schemas.yaml#/components/schemas/walletAddress receiver: $ref: ./schemas.yaml#/components/schemas/receiver method: $ref: '#/components/schemas/payment-method' required: + - walletAddress - receiver - method additionalProperties: false - description: Create a quote with a fixed-receive amount properties: + walletAddress: + $ref: ./schemas.yaml#/components/schemas/walletAddress receiver: $ref: ./schemas.yaml#/components/schemas/receiver method: @@ -569,12 +589,15 @@ paths: description: The fixed amount that would be paid into the receiving wallet address given a successful outgoing payment. $ref: ./schemas.yaml#/components/schemas/amount required: + - walletAddress - receiver - method - receiveAmount additionalProperties: false - description: Create a quote with a fixed-send amount properties: + walletAddress: + $ref: ./schemas.yaml#/components/schemas/walletAddress receiver: $ref: ./schemas.yaml#/components/schemas/receiver method: @@ -583,6 +606,7 @@ paths: description: The fixed amount that would be sent from the sending wallet address given a successful outgoing payment. $ref: ./schemas.yaml#/components/schemas/amount required: + - walletAddress - receiver - method - debitAmount @@ -615,8 +639,8 @@ paths: examples: Incoming Payment for $25 with $12.34 received so far: value: - id: 'https://rafiki.money/incoming-payments/2f1b0150-db73-49e8-8713-628baa4a17ff' - walletAddress: 'https://rafiki.money/alice/' + id: 'https://ilp.rafiki.money/incoming-payments/2f1b0150-db73-49e8-8713-628baa4a17ff' + walletAddress: 'https://ilp.rafiki.money/alice/' incomingAmount: value: '2500' assetCode: USD @@ -665,8 +689,8 @@ paths: examples: Completed Incoming Payment: value: - id: 'https://rafiki.money/incoming-payments/016da9d5-c9a4-4c80-a354-86b915a04ff8' - walletAddress: 'https://rafiki.money/alice/' + id: 'https://ilp.rafiki.money/incoming-payments/016da9d5-c9a4-4c80-a354-86b915a04ff8' + walletAddress: 'https://ilp.rafiki.money/alice/' incomingAmount: value: '250' assetCode: USD @@ -713,10 +737,10 @@ paths: examples: Outgoing Payment with a fixed send amount of $25: value: - id: 'https://rafiki.money/bob/outgoing-payments/3859b39e-4666-4ce5-8745-72f1864c5371' - walletAddress: 'https://rafiki.money/bob/' + id: 'https://ilp.rafiki.money/bob/outgoing-payments/3859b39e-4666-4ce5-8745-72f1864c5371' + walletAddress: 'https://ilp.rafiki.money/bob/' failed: false - receiver: 'https://rafiki.money/incoming-payments/2f1b0150-db73-49e8-8713-628baa4a17ff' + receiver: 'https://ilp.rafiki.money/incoming-payments/2f1b0150-db73-49e8-8713-628baa4a17ff' debitAmount: value: '2500' assetCode: USD @@ -762,9 +786,9 @@ paths: examples: Quote with a fixed send amount of $25: value: - id: 'https://rafiki.money/bob/quotes/3859b39e-4666-4ce5-8745-72f1864c5371' - walletAddress: 'https://rafiki.money/bob/' - receiver: 'https://rafiki.money/incoming-payments/2f1b0150-db73-49e8-8713-628baa4a17ff' + id: 'https://ilp.rafiki.money/bob/quotes/3859b39e-4666-4ce5-8745-72f1864c5371' + walletAddress: 'https://ilp.rafiki.money/bob/' + receiver: 'https://ilp.rafiki.money/incoming-payments/2f1b0150-db73-49e8-8713-628baa4a17ff' debitAmount: value: '2500' assetCode: USD @@ -796,11 +820,11 @@ components: description: A **wallet address** resource is the root of the API and contains the public details of the financial account represented by the Wallet Address that is also the service endpoint URL. additionalProperties: false examples: - - id: 'https://rafiki.money/alice' + - id: 'https://ilp.rafiki.money/alice' publicName: Alice assetCode: USD assetScale: 2 - authServer: 'https://rafiki.money/auth' + authServer: 'https://auth.rafiki.money' properties: id: type: string @@ -851,8 +875,8 @@ components: description: 'An **incoming payment** resource represents a payment that will be, is currently being, or has been received by the account.' type: object examples: - - id: 'https://rafiki.money/incoming-payments/016da9d5-c9a4-4c80-a354-86b915a04ff8' - walletAddress: 'https://rafiki.money/alice/' + - id: 'https://ilp.rafiki.money/incoming-payments/016da9d5-c9a4-4c80-a354-86b915a04ff8' + walletAddress: 'https://ilp.rafiki.money/alice/' incomingAmount: value: '250' assetCode: USD @@ -868,8 +892,8 @@ components: metadata: description: 'Hi Mo, this is for the cappuccino I bought for you the other day.' externalRef: Coffee w/ Mo on 10 March 22 - - id: 'https://rafiki.money/incoming-payments/456da9d5-c9a4-4c80-a354-86b915a04ff8' - walletAddress: 'https://rafiki.money/alice/' + - id: 'https://ilp.rafiki.money/incoming-payments/456da9d5-c9a4-4c80-a354-86b915a04ff8' + walletAddress: 'https://ilp.rafiki.money/alice/' incomingAmount: value: '2500' assetCode: USD @@ -881,7 +905,6 @@ components: expiresAt: '2022-04-12T23:20:50.52Z' createdAt: '2022-03-12T23:20:50.52Z' updatedAt: '2022-03-12T23:20:50.52Z' - additionalProperties: false properties: id: type: string @@ -952,7 +975,7 @@ components: assetCode: USD assetScale: 2 properties: - receiveAmount: + receivedAmount: $ref: ./schemas.yaml#/components/schemas/amount unresolvedProperites: false outgoing-payment: @@ -960,10 +983,10 @@ components: description: 'An **outgoing payment** resource represents a payment that will be, is currently being, or has previously been, sent from the wallet address.' type: object examples: - - id: 'https://rafiki.money/outgoing-payments/8c68d3cc-0a0f-4216-98b4-4fa44a6c88cf' - walletAddress: 'https://rafiki.money/alice/' + - id: 'https://ilp.rafiki.money/outgoing-payments/8c68d3cc-0a0f-4216-98b4-4fa44a6c88cf' + walletAddress: 'https://ilp.rafiki.money/alice/' failed: false - receiver: 'https://rafiki.money/aplusvideo/incoming-payments/45d495ad-b763-4882-88d7-aa14d261686e' + receiver: 'https://ilp.rafiki.money/aplusvideo/incoming-payments/45d495ad-b763-4882-88d7-aa14d261686e' receiveAmount: value: '2500' assetCode: USD @@ -981,10 +1004,10 @@ components: metadata: description: APlusVideo subscription externalRef: 'customer: 847458475' - - id: 'https://rafiki.money/outgoing-payments/0cffa5a4-58fd-4cc8-8e01-7145c72bf07c' - walletAddress: 'https://rafiki.money/alice/' + - id: 'https://ilp.rafiki.money/outgoing-payments/0cffa5a4-58fd-4cc8-8e01-7145c72bf07c' + walletAddress: 'https://ilp.rafiki.money/alice/' failed: false - receiver: 'https://rafiki.money/shoeshop/2fe92c6f-ef0d-487c-8759-3784eae6bce9' + receiver: 'https://ilp.rafiki.money/shoeshop/2fe92c6f-ef0d-487c-8759-3784eae6bce9' debitAmount: value: '7126' assetCode: USD @@ -1056,9 +1079,9 @@ components: description: A **quote** resource represents the quoted amount details with which an Outgoing Payment may be created. type: object examples: - - id: 'https://rafiki.money/quotes/ab03296b-0c8b-4776-b94e-7ee27d868d4d' - walletAddress: 'https://rafiki.money/alice/' - receiver: 'https://rafiki.money/shoeshop/incoming-payments/2fe92c6f-ef0d-487c-8759-3784eae6bce9' + - id: 'https://ilp.rafiki.money/quotes/ab03296b-0c8b-4776-b94e-7ee27d868d4d' + walletAddress: 'https://ilp.rafiki.money/alice/' + receiver: 'https://ilp.rafiki.money/shoeshop/incoming-payments/2fe92c6f-ef0d-487c-8759-3784eae6bce9' receiveAmount: value: '2500' assetCode: USD @@ -1074,9 +1097,9 @@ components: method: ilp createdAt: '2022-03-12T23:20:50.52Z' expiresAt: '2022-04-12T23:20:50.52Z' - - id: 'https://rafiki.money/quotes/8c68d3cc-0a0f-4216-98b4-4fa44a6c88cf' - walletAddress: 'https://rafiki.money/alice/' - receiver: 'https://rafiki.money/aplusvideo/incoming-payments/45d495ad-b763-4882-88d7-aa14d261686e' + - id: 'https://ilp.rafiki.money/quotes/8c68d3cc-0a0f-4216-98b4-4fa44a6c88cf' + walletAddress: 'https://ilp.rafiki.money/alice/' + receiver: 'https://ilp.rafiki.money/aplusvideo/incoming-payments/45d495ad-b763-4882-88d7-aa14d261686e' debitAmount: value: '7126' assetCode: USD @@ -1280,6 +1303,13 @@ components: type: string description: Sub-resource identifier required: true + wallet-address: + name: wallet-address + in: query + schema: + type: string + description: 'URL of a wallet address hosted by a Rafiki instance.' + required: true signature: name: Signature in: header diff --git a/openapi/schemas.yaml b/openapi/schemas.yaml index a31d1ac596..fc0bce4435 100644 --- a/openapi/schemas.yaml +++ b/openapi/schemas.yaml @@ -44,5 +44,10 @@ components: format: uri pattern: '^https://(.+)/(incoming-payments|connections)/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' examples: - - 'https://openpayments.guide/incoming-payments/08394f02-7b7b-45e2-b645-51d04e7c330c' - - 'https://openpayments.guide/connections/016da9d5-c9a4-4c80-a354-86b915a04ff8' + - 'https://ilp.rafiki.money/incoming-payments/08394f02-7b7b-45e2-b645-51d04e7c330c' + - 'https://ilp.rafiki.money/connections/016da9d5-c9a4-4c80-a354-86b915a04ff8' + walletAddress: + title: Wallet Address + type: string + description: 'URL of a wallet address hosted by a Rafiki instance.' + format: uri diff --git a/packages/backend/src/open_payments/payment/incoming/routes.test.ts b/packages/backend/src/open_payments/payment/incoming/routes.test.ts index 934daee8cb..0fc8e07513 100644 --- a/packages/backend/src/open_payments/payment/incoming/routes.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/routes.test.ts @@ -209,11 +209,6 @@ describe('Incoming Payment Routes', (): void => { expiresAt: expiresAt ? new Date(expiresAt) : undefined, client }) - // TODO: this fails because the response has 'methods' (which is correct) and the spec has - // '#/components/schemas/incoming-payment' with additionalProperties = false. - // We are trying to include payment method via an allOf but this doesn't supercede the - // additionalProperties = false. Appears we need to tweak the spec. Should we also add - // jestOpenAPI to open-payments lib use .toSatisfyApiSpec() to verfiy in client tests? expect(ctx.response).toSatisfyApiSpec() const incomingPaymentId = ( (ctx.response.body as Record)['id'] as string diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d286819948..f2336f0247 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -303,8 +303,8 @@ importers: specifier: 1.1.0 version: 1.1.0 '@interledger/open-payments': - specifier: 5.0.0 - version: 5.0.0 + specifier: 5.2.1 + version: 5.2.1 '@interledger/openapi': specifier: 1.0.3 version: 1.0.3 From 77907db2a196e3b77ebed9a2a585be5f8dad75c5 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Tue, 17 Oct 2023 15:04:11 -0400 Subject: [PATCH 07/39] fix(backend): update receiver --- .../src/graphql/resolvers/receiver.test.ts | 6 +- .../payment/incoming_remote/service.ts | 9 +- .../src/open_payments/receiver/model.test.ts | 144 ++++++++++-------- .../src/open_payments/receiver/model.ts | 114 ++++++-------- .../src/open_payments/receiver/service.ts | 12 +- 5 files changed, 143 insertions(+), 142 deletions(-) diff --git a/packages/backend/src/graphql/resolvers/receiver.test.ts b/packages/backend/src/graphql/resolvers/receiver.test.ts index f0cdb16294..40fa320881 100644 --- a/packages/backend/src/graphql/resolvers/receiver.test.ts +++ b/packages/backend/src/graphql/resolvers/receiver.test.ts @@ -7,7 +7,7 @@ import { initIocContainer } from '../..' import { Config } from '../../config/app' import { Amount, serializeAmount } from '../../open_payments/amount' import { - mockIncomingPaymentWithConnection, + mockIncomingPaymentWithPaymentMethods, mockWalletAddress } from '@interledger/open-payments' import { CreateReceiverResponse } from '../generated/graphql' @@ -46,8 +46,8 @@ describe('Receiver Resolver', (): void => { `( 'creates receiver ($#)', async ({ metadata, expiresAt, incomingAmount }): Promise => { - const receiver = Receiver.fromIncomingPayment( - mockIncomingPaymentWithConnection({ + const receiver = new Receiver( + mockIncomingPaymentWithPaymentMethods({ id: `${walletAddress.id}/incoming-payments/${uuid()}`, walletAddress: walletAddress.id, metadata, diff --git a/packages/backend/src/open_payments/payment/incoming_remote/service.ts b/packages/backend/src/open_payments/payment/incoming_remote/service.ts index 4d38be5c0f..f5e46896e5 100644 --- a/packages/backend/src/open_payments/payment/incoming_remote/service.ts +++ b/packages/backend/src/open_payments/payment/incoming_remote/service.ts @@ -1,6 +1,7 @@ import { AuthenticatedClient, IncomingPayment as OpenPaymentsIncomingPayment, + IncomingPaymentWithPaymentMethods as OpenPaymentsIncomingPaymentWithPaymentMethods, isPendingGrant, AccessAction, WalletAddress as OpenPaymentsWalletAddress @@ -24,7 +25,9 @@ interface CreateRemoteIncomingPaymentArgs { export interface RemoteIncomingPaymentService { create( args: CreateRemoteIncomingPaymentArgs - ): Promise + ): Promise< + OpenPaymentsIncomingPaymentWithPaymentMethods | RemoteIncomingPaymentError + > } interface ServiceDependencies extends BaseService { @@ -52,7 +55,9 @@ export async function createRemoteIncomingPaymentService( async function create( deps: ServiceDependencies, args: CreateRemoteIncomingPaymentArgs -): Promise { +): Promise< + OpenPaymentsIncomingPaymentWithPaymentMethods | RemoteIncomingPaymentError +> { const { walletAddressUrl } = args const grantOrError = await getGrant(deps, walletAddressUrl, [ AccessAction.Create, diff --git a/packages/backend/src/open_payments/receiver/model.test.ts b/packages/backend/src/open_payments/receiver/model.test.ts index 6855f39730..d3c65e2301 100644 --- a/packages/backend/src/open_payments/receiver/model.test.ts +++ b/packages/backend/src/open_payments/receiver/model.test.ts @@ -9,8 +9,8 @@ import { truncateTables } from '../../tests/tableManager' import { ConnectionService } from '../connection/service' import { Receiver } from './model' import { IncomingPaymentState } from '../payment/incoming/model' -import { Connection } from '../connection/model' import assert from 'assert' +import base64url from 'base64url' describe('Receiver Model', (): void => { let deps: IocContract @@ -32,7 +32,7 @@ describe('Receiver Model', (): void => { await appContainer.shutdown() }) - describe('fromIncomingPayment', () => { + describe('constructor', () => { test('creates receiver', async () => { const walletAddress = await createWalletAddress(deps) const incomingPayment = await createIncomingPayment(deps, { @@ -40,11 +40,10 @@ describe('Receiver Model', (): void => { }) const connection = connectionService.get(incomingPayment) + assert(connection) - assert(connection instanceof Connection) - - const receiver = Receiver.fromIncomingPayment( - await incomingPayment.toOpenPaymentsType(walletAddress, connection) + const receiver = new Receiver( + incomingPayment.toOpenPaymentsType(walletAddress, connection) ) expect(receiver).toEqual({ @@ -60,7 +59,14 @@ describe('Receiver Model', (): void => { completed: incomingPayment.completed, receivedAmount: incomingPayment.receivedAmount, incomingAmount: incomingPayment.incomingAmount, - expiresAt: incomingPayment.expiresAt + expiresAt: incomingPayment.expiresAt, + methods: [ + { + type: 'ilp', + ilpAddress: connection.ilpAddress, + sharedSecret: base64url(connection.sharedSecret) + } + ] } }) }) @@ -73,14 +79,16 @@ describe('Receiver Model', (): void => { incomingPayment.state = IncomingPaymentState.Completed const connection = connectionService.get(incomingPayment) + assert(connection) - assert(connection instanceof Connection) - const openPaymentsIncomingPayment = - await incomingPayment.toOpenPaymentsType(walletAddress, connection) + const openPaymentsIncomingPayment = incomingPayment.toOpenPaymentsType( + walletAddress, + connection + ) - expect(() => - Receiver.fromIncomingPayment(openPaymentsIncomingPayment) - ).toThrow('Cannot create receiver from completed incoming payment') + expect(() => new Receiver(openPaymentsIncomingPayment)).toThrow( + 'Cannot create receiver from completed incoming payment' + ) }) test('throws if incoming payment is expired', async () => { @@ -92,13 +100,15 @@ describe('Receiver Model', (): void => { incomingPayment.expiresAt = new Date(Date.now() - 1) const connection = connectionService.get(incomingPayment) - assert(connection instanceof Connection) - const openPaymentsIncomingPayment = - await incomingPayment.toOpenPaymentsType(walletAddress, connection) + assert(connection) + const openPaymentsIncomingPayment = incomingPayment.toOpenPaymentsType( + walletAddress, + connection + ) - expect(() => - Receiver.fromIncomingPayment(openPaymentsIncomingPayment) - ).toThrow('Cannot create receiver from expired incoming payment') + expect(() => new Receiver(openPaymentsIncomingPayment)).toThrow( + 'Cannot create receiver from expired incoming payment' + ) }) test('throws if stream connection has invalid ILP address', async () => { @@ -108,57 +118,59 @@ describe('Receiver Model', (): void => { }) const connection = connectionService.get(incomingPayment) - assert(connection instanceof Connection) + assert(connection) ;(connection.ilpAddress as string) = 'not base 64 encoded' - const openPaymentsIncomingPayment = - await incomingPayment.toOpenPaymentsType(walletAddress, connection) - - expect(() => - Receiver.fromIncomingPayment(openPaymentsIncomingPayment) - ).toThrow('Invalid ILP address on stream connection') - }) - }) - - describe('fromConnection', () => { - test('creates receiver', async () => { - const walletAddress = await createWalletAddress(deps) - const incomingPayment = await createIncomingPayment(deps, { - walletAddressId: walletAddress.id - }) - - const connection = connectionService.get(incomingPayment) - assert(connection instanceof Connection) - - const receiver = Receiver.fromConnection(connection.toOpenPaymentsType()) - - expect(receiver).toEqual({ - assetCode: incomingPayment.asset.code, - assetScale: incomingPayment.asset.scale, - incomingAmountValue: undefined, - receivedAmountValue: undefined, - ilpAddress: expect.any(String), - sharedSecret: expect.any(Buffer), - expiresAt: undefined - }) - }) - - test('returns undefined if invalid ilpAdress', async () => { - const walletAddress = await createWalletAddress(deps) - const incomingPayment = await createIncomingPayment(deps, { - walletAddressId: walletAddress.id - }) - - const connection = connectionService.get(incomingPayment) - assert(connection instanceof Connection) - - const openPaymentsConnection = connection.toOpenPaymentsType() - - openPaymentsConnection.ilpAddress = 'not base 64 encoded' + const openPaymentsIncomingPayment = incomingPayment.toOpenPaymentsType( + walletAddress, + connection + ) - expect(() => Receiver.fromConnection(openPaymentsConnection)).toThrow( - 'Invalid ILP address on stream connection' + expect(() => new Receiver(openPaymentsIncomingPayment)).toThrow( + 'Invalid ILP address on ilp payment method' ) }) }) + + // describe('fromConnection', () => { + // test('creates receiver', async () => { + // const paymentPointer = await createPaymentPointer(deps) + // const incomingPayment = await createIncomingPayment(deps, { + // paymentPointerId: paymentPointer.id + // }) + + // const connection = connectionService.get(incomingPayment) + // assert(connection instanceof Connection) + + // const receiver = Receiver.fromConnection(connection.toOpenPaymentsType()) + + // expect(receiver).toEqual({ + // assetCode: incomingPayment.asset.code, + // assetScale: incomingPayment.asset.scale, + // incomingAmountValue: undefined, + // receivedAmountValue: undefined, + // ilpAddress: expect.any(String), + // sharedSecret: expect.any(Buffer), + // expiresAt: undefined + // }) + // }) + + // test('returns undefined if invalid ilpAdress', async () => { + // const paymentPointer = await createPaymentPointer(deps) + // const incomingPayment = await createIncomingPayment(deps, { + // paymentPointerId: paymentPointer.id + // }) + + // const connection = connectionService.get(incomingPayment) + // assert(connection instanceof Connection) + + // const openPaymentsConnection = connection.toOpenPaymentsType() + + // openPaymentsConnection.ilpAddress = 'not base 64 encoded' + + // expect(() => Receiver.fromConnection(openPaymentsConnection)).toThrow( + // 'Invalid ILP address on stream connection' + // ) + // }) + // }) }) diff --git a/packages/backend/src/open_payments/receiver/model.ts b/packages/backend/src/open_payments/receiver/model.ts index 8bd228db5a..77f0cbdde6 100644 --- a/packages/backend/src/open_payments/receiver/model.ts +++ b/packages/backend/src/open_payments/receiver/model.ts @@ -3,22 +3,12 @@ import base64url from 'base64url' import { Amount, parseAmount } from '../amount' import { AssetOptions } from '../../asset/service' -import { - IncomingPaymentWithConnection as OpenPaymentsIncomingPaymentWithConnection, - ILPStreamConnection as OpenPaymentsConnection -} from '@interledger/open-payments' -import { ConnectionBase } from '../connection/model' +import { IncomingPaymentWithPaymentMethods as OpenPaymentsIncomingPaymentWithPaymentMethod } from '@interledger/open-payments' import { IlpAddress, isValidIlpAddress } from 'ilp-packet' -interface OpenPaymentsConnectionWithIlpAddress - extends Omit { - ilpAddress: IlpAddress -} - type ReceiverIncomingPayment = Readonly< Omit< - OpenPaymentsIncomingPaymentWithConnection, - | 'ilpStreamConnection' + OpenPaymentsIncomingPaymentWithPaymentMethod, | 'expiresAt' | 'receivedAmount' | 'incomingAmount' @@ -33,29 +23,31 @@ type ReceiverIncomingPayment = Readonly< } > -export class Receiver extends ConnectionBase { - // TODO: remove? still called 1 place - static fromConnection( - connection: OpenPaymentsConnection - ): Receiver | undefined { - if (!isValidIlpAddress(connection.ilpAddress)) { - throw new Error('Invalid ILP address on stream connection') - } - - return new this({ - id: connection.id, - assetCode: connection.assetCode, - assetScale: connection.assetScale, - sharedSecret: connection.sharedSecret, - ilpAddress: connection.ilpAddress - }) - } +export class Receiver { + public readonly ilpAddress: IlpAddress + public readonly sharedSecret: Buffer + public readonly assetCode: string + public readonly assetScale: number + public readonly incomingPayment: ReceiverIncomingPayment - static fromIncomingPayment( - incomingPayment: OpenPaymentsIncomingPaymentWithConnection - ): Receiver { - if (!incomingPayment.ilpStreamConnection) { - throw new Error('Missing stream connection on incoming payment') + // TODO: remove? still called 1 place + // static fromConnection(connection: Connection): Receiver | undefined { + // if (!isValidIlpAddress(connection.ilpAddress)) { + // throw new Error('Invalid ILP address on stream connection') + // } + + // return new this({ + // // id: connection.id, + // // assetCode: connection.assetCode, + // // assetScale: connection.assetScale, + // sharedSecret: connection.sharedSecret, + // ilpAddress: connection.ilpAddress + // }) + // } + + constructor(incomingPayment: OpenPaymentsIncomingPaymentWithPaymentMethod) { + if (!incomingPayment.methods.length) { + throw new Error('Missing payment method(s) on incoming payment') } if (incomingPayment.completed) { @@ -75,39 +67,31 @@ export class Receiver extends ConnectionBase { : undefined const receivedAmount = parseAmount(incomingPayment.receivedAmount) - if (!isValidIlpAddress(incomingPayment.ilpStreamConnection.ilpAddress)) { - throw new Error('Invalid ILP address on stream connection') - } - - return new this( - { - ...incomingPayment.ilpStreamConnection, - ilpAddress: incomingPayment.ilpStreamConnection.ilpAddress - }, - { - id: incomingPayment.id, - completed: incomingPayment.completed, - walletAddress: incomingPayment.walletAddress, - expiresAt, - receivedAmount, - incomingAmount, - metadata: incomingPayment.metadata, - createdAt: new Date(incomingPayment.createdAt), - updatedAt: new Date(incomingPayment.updatedAt) - } + // TODO: handle multiple payment methods + const ilpMethod = incomingPayment.methods.find( + (method) => method.type === 'ilp' ) - } + if (!ilpMethod) { + throw new Error('Cannot create receiver from unsupported payment method') + } + if (!isValidIlpAddress(ilpMethod.ilpAddress)) { + throw new Error('Invalid ILP address on ilp payment method') + } - private constructor( - connection: OpenPaymentsConnectionWithIlpAddress, - public incomingPayment?: ReceiverIncomingPayment - ) { - super( - connection.ilpAddress, - base64url.toBuffer(connection.sharedSecret), - connection.assetCode, - connection.assetScale - ) + this.ilpAddress = ilpMethod.ilpAddress + this.sharedSecret = base64url.toBuffer(ilpMethod.sharedSecret) + // TODO: ensure these are the right assetCode/Scales + this.assetCode = incomingPayment.receivedAmount.assetCode + this.assetScale = incomingPayment.receivedAmount.assetScale + + this.incomingPayment = { + ...incomingPayment, + expiresAt, + receivedAmount, + incomingAmount, + createdAt: new Date(incomingPayment.createdAt), + updatedAt: new Date(incomingPayment.updatedAt) + } } public get asset(): AssetOptions { diff --git a/packages/backend/src/open_payments/receiver/service.ts b/packages/backend/src/open_payments/receiver/service.ts index bb461dfeab..a637462062 100644 --- a/packages/backend/src/open_payments/receiver/service.ts +++ b/packages/backend/src/open_payments/receiver/service.ts @@ -1,6 +1,6 @@ import { AuthenticatedClient, - IncomingPayment as OpenPaymentsIncomingPayment, + IncomingPaymentWithPaymentMethods as OpenPaymentsIncomingPaymentWithPaymentMethods, isPendingGrant, AccessType, AccessAction @@ -82,7 +82,7 @@ async function createReceiver( } try { - return Receiver.fromIncomingPayment(incomingPaymentOrError) + return new Receiver(incomingPaymentOrError) } catch (error) { const errorMessage = 'Could not create receiver from incoming payment' deps.logger.error( @@ -103,7 +103,7 @@ async function createLocalIncomingPayment( deps: ServiceDependencies, args: CreateReceiverArgs, walletAddress: WalletAddress -): Promise { +): Promise { const { expiresAt, incomingAmount, metadata } = args const incomingPaymentOrError = await deps.incomingPaymentService.create({ @@ -141,7 +141,7 @@ async function getReceiver( ): Promise { const incomingPayment = await getIncomingPayment(deps, url) if (incomingPayment) { - return Receiver.fromIncomingPayment(incomingPayment) + return new Receiver(incomingPayment) } } @@ -162,7 +162,7 @@ function parseIncomingPaymentUrl( async function getIncomingPayment( deps: ServiceDependencies, url: string -): Promise { +): Promise { try { const urlParseResult = parseIncomingPaymentUrl(url) if (!urlParseResult) { @@ -209,7 +209,7 @@ async function getLocalIncomingPayment({ deps: ServiceDependencies id: string walletAddress: WalletAddress -}): Promise { +}): Promise { const incomingPayment = await deps.incomingPaymentService.get({ id, walletAddressId: walletAddress.id From 5a25a694d6f5e0f7cf3a4b43c052d282c52c2339 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Tue, 17 Oct 2023 22:12:52 -0400 Subject: [PATCH 08/39] fix(backend): rm connection tests --- .../payment/outgoing/service.test.ts | 586 ++++++++---------- 1 file changed, 275 insertions(+), 311 deletions(-) diff --git a/packages/backend/src/open_payments/payment/outgoing/service.test.ts b/packages/backend/src/open_payments/payment/outgoing/service.test.ts index fc52812649..4d5c7770bf 100644 --- a/packages/backend/src/open_payments/payment/outgoing/service.test.ts +++ b/packages/backend/src/open_payments/payment/outgoing/service.test.ts @@ -327,11 +327,7 @@ describe('OutgoingPaymentService', (): void => { } }) - describe.each` - toConnection | description - ${true} | ${'connection receiver'} - ${false} | ${'incoming payment receiver'} - `('$description', ({ toConnection }): void => { + describe('incoming payment receiver', (): void => { it.each` outgoingPeer | description ${false} | ${''} @@ -341,11 +337,6 @@ describe('OutgoingPaymentService', (): void => { async ({ outgoingPeer }): Promise => { const peerService = await deps.use('peerService') const peer = await createPeer(deps) - if (toConnection) { - const fetchedReceiver = connectionService.getUrl(incomingPayment) - assert.ok(fetchedReceiver) - receiver = fetchedReceiver - } const quote = await createQuote(deps, { walletAddressId, receiver, @@ -802,352 +793,325 @@ describe('OutgoingPaymentService', (): void => { }) describe('processNext', (): void => { - describe.each` - toConnection | description - ${true} | ${'connection'} - ${false} | ${'incoming payment'} - `('SENDING (to $description) → ', ({ toConnection }): void => { - const receiveAmount = { - value: BigInt(123), - assetCode: destinationAsset.code, - assetScale: destinationAsset.scale - } + const receiveAmount = { + value: BigInt(123), + assetCode: destinationAsset.code, + assetScale: destinationAsset.scale + } - beforeEach((): void => { - if (toConnection) { - const fetchedReceiver = connectionService.getUrl(incomingPayment) - assert.ok(fetchedReceiver) - receiver = fetchedReceiver - } + async function setup( + opts: Omit, + incomingAmount?: Amount + ): Promise { + if (incomingAmount) { + await incomingPayment.$query(knex).patch({ incomingAmount }) + } + const payment = await createOutgoingPayment(deps, { + walletAddressId, + ...opts }) - async function setup( - opts: Omit, - incomingAmount?: Amount - ): Promise { - if (incomingAmount) { - await incomingPayment.$query(knex).patch({ incomingAmount }) - } - const payment = await createOutgoingPayment(deps, { - walletAddressId, - ...opts - }) - - trackAmountDelivered(payment.id) + trackAmountDelivered(payment.id) - await expect( - outgoingPaymentService.fund({ - id: payment.id, - amount: payment.debitAmount.value, - transferId: uuid() - }) - ).resolves.toMatchObject({ - state: OutgoingPaymentState.Sending + await expect( + outgoingPaymentService.fund({ + id: payment.id, + amount: payment.debitAmount.value, + transferId: uuid() }) + ).resolves.toMatchObject({ + state: OutgoingPaymentState.Sending + }) - return payment.id - } - - test.each` - debitAmount | receiveAmount - ${debitAmount} | ${undefined} - ${undefined} | ${receiveAmount} - `('COMPLETED', async ({ debitAmount, receiveAmount }): Promise => { - const paymentId = await setup({ - receiver, - debitAmount, - receiveAmount - }) + return payment.id + } - const payment = await processNext( - paymentId, - OutgoingPaymentState.Completed - ) - const amountSent = payment.receiveAmount.value * BigInt(2) - await expectOutcome(payment, { - accountBalance: payment.debitAmount.value - amountSent, - amountSent, - amountDelivered: payment.receiveAmount.value, - incomingPaymentReceived: payment.receiveAmount.value, - withdrawAmount: payment.debitAmount.value - amountSent - }) + test.each` + debitAmount | receiveAmount + ${debitAmount} | ${undefined} + ${undefined} | ${receiveAmount} + `('COMPLETED', async ({ debitAmount, receiveAmount }): Promise => { + const paymentId = await setup({ + receiver, + debitAmount, + receiveAmount }) - it('COMPLETED (receiveAmount < incomingPayment.incomingAmount)', async (): Promise => { - incomingPayment = await createIncomingPayment(deps, { - walletAddressId: receiverWalletAddress.id, - incomingAmount: { - value: receiveAmount.value * 2n, - assetCode: receiverWalletAddress.asset.code, - assetScale: receiverWalletAddress.asset.scale - } - }) + const payment = await processNext( + paymentId, + OutgoingPaymentState.Completed + ) + const amountSent = payment.receiveAmount.value * BigInt(2) + await expectOutcome(payment, { + accountBalance: payment.debitAmount.value - amountSent, + amountSent, + amountDelivered: payment.receiveAmount.value, + incomingPaymentReceived: payment.receiveAmount.value, + withdrawAmount: payment.debitAmount.value - amountSent + }) + }) - const fetchedReceiver = connectionService.getUrl(incomingPayment) - assert.ok(fetchedReceiver) - assert.ok(incomingPayment.walletAddress) - const paymentId = await setup({ - receiver: toConnection - ? fetchedReceiver - : incomingPayment.getUrl(incomingPayment.walletAddress), - receiveAmount - }) + it('COMPLETED (receiveAmount < incomingPayment.incomingAmount)', async (): Promise => { + incomingPayment = await createIncomingPayment(deps, { + walletAddressId: receiverWalletAddress.id, + incomingAmount: { + value: receiveAmount.value * 2n, + assetCode: receiverWalletAddress.asset.code, + assetScale: receiverWalletAddress.asset.scale + } + }) + assert.ok(incomingPayment.walletAddress) + const paymentId = await setup({ + receiver: incomingPayment.getUrl(incomingPayment.walletAddress), + receiveAmount + }) - const payment = await processNext( - paymentId, - OutgoingPaymentState.Completed - ) - const amountSent = payment.receiveAmount.value * BigInt(2) - await expectOutcome(payment, { - accountBalance: payment.debitAmount.value - amountSent, - amountSent, - amountDelivered: payment.receiveAmount.value, - incomingPaymentReceived: payment.receiveAmount.value, - withdrawAmount: payment.debitAmount.value - amountSent - }) + const payment = await processNext( + paymentId, + OutgoingPaymentState.Completed + ) + const amountSent = payment.receiveAmount.value * BigInt(2) + await expectOutcome(payment, { + accountBalance: payment.debitAmount.value - amountSent, + amountSent, + amountDelivered: payment.receiveAmount.value, + incomingPaymentReceived: payment.receiveAmount.value, + withdrawAmount: payment.debitAmount.value - amountSent }) + }) - it('COMPLETED (with incoming payment initially partially paid)', async (): Promise => { - const paymentId = await setup( - { - receiver, - receiveAmount: toConnection && receiveAmount - }, + it('COMPLETED (with incoming payment initially partially paid)', async (): Promise => { + const paymentId = await setup( + { + receiver, receiveAmount - ) + }, + receiveAmount + ) - const amountAlreadyDelivered = BigInt(34) - await payIncomingPayment(amountAlreadyDelivered) - const payment = await processNext( - paymentId, - OutgoingPaymentState.Completed - ) - // The amountAlreadyDelivered is unknown to the sender when sending to - // the connection (instead of the incoming payment), so the entire - // receive amount is delivered by the outgoing payment ("overpaying" - // the incoming payment). - // Incoming payments allow overpayment (above the incomingAmount) for - // one packet. In this case, the full payment amount was completed in a - // single packet. With a different combination of amounts and - // maxPacketAmount limits, an outgoing payment to a connection could - // overpay the corresponding incoming payment's incomingAmount without - // the full outgoing payment receive amount being delivered. - const amountSent = toConnection - ? payment.receiveAmount.value * BigInt(2) - : (payment.receiveAmount.value - amountAlreadyDelivered) * BigInt(2) - await expectOutcome(payment, { - accountBalance: payment.debitAmount.value - amountSent, - amountSent, - amountDelivered: toConnection - ? payment.receiveAmount.value - : payment.receiveAmount.value - amountAlreadyDelivered, - incomingPaymentReceived: toConnection - ? payment.receiveAmount.value + amountAlreadyDelivered - : payment.receiveAmount.value, - withdrawAmount: payment.debitAmount.value - amountSent - }) + const amountAlreadyDelivered = BigInt(34) + await payIncomingPayment(amountAlreadyDelivered) + const payment = await processNext( + paymentId, + OutgoingPaymentState.Completed + ) + // The amountAlreadyDelivered is unknown to the sender when sending to + // the connection (instead of the incoming payment), so the entire + // receive amount is delivered by the outgoing payment ("overpaying" + // the incoming payment). + // Incoming payments allow overpayment (above the incomingAmount) for + // one packet. In this case, the full payment amount was completed in a + // single packet. With a different combination of amounts and + // maxPacketAmount limits, an outgoing payment to a connection could + // overpay the corresponding incoming payment's incomingAmount without + // the full outgoing payment receive amount being delivered. + const amountSent = + (payment.receiveAmount.value - amountAlreadyDelivered) * BigInt(2) + await expectOutcome(payment, { + accountBalance: payment.debitAmount.value - amountSent, + amountSent, + amountDelivered: payment.receiveAmount.value - amountAlreadyDelivered, + incomingPaymentReceived: payment.receiveAmount.value, + withdrawAmount: payment.debitAmount.value - amountSent }) + }) - it('SENDING (partial payment then retryable Pay error)', async (): Promise => { - mockPay( - { - maxSourceAmount: BigInt(10), - minDeliveryAmount: BigInt(5) - }, - Pay.PaymentError.ClosedByReceiver - ) + it('SENDING (partial payment then retryable Pay error)', async (): Promise => { + mockPay( + { + maxSourceAmount: BigInt(10), + minDeliveryAmount: BigInt(5) + }, + Pay.PaymentError.ClosedByReceiver + ) - const paymentId = await setup({ - receiver, - debitAmount - }) + const paymentId = await setup({ + receiver, + debitAmount + }) - for (let i = 0; i < 4; i++) { - const payment = await processNext( - paymentId, - OutgoingPaymentState.Sending - ) - expect(payment.stateAttempts).toBe(i + 1) - await expectOutcome(payment, { - amountSent: BigInt(10 * (i + 1)), - amountDelivered: BigInt(5 * (i + 1)) - }) - // Skip through the backoff timer. - fastForwardToAttempt(payment.stateAttempts) - } - // Last attempt fails, but no more retries. + for (let i = 0; i < 4; i++) { const payment = await processNext( paymentId, - OutgoingPaymentState.Failed, - Pay.PaymentError.ClosedByReceiver + OutgoingPaymentState.Sending ) - expect(payment.stateAttempts).toBe(0) - // "mockPay" allows a small amount of money to be paid every attempt. + expect(payment.stateAttempts).toBe(i + 1) await expectOutcome(payment, { - accountBalance: BigInt(123 - 10 * 5), - amountSent: BigInt(10 * 5), - amountDelivered: BigInt(5 * 5), - withdrawAmount: BigInt(123 - 10 * 5) + amountSent: BigInt(10 * (i + 1)), + amountDelivered: BigInt(5 * (i + 1)) }) + // Skip through the backoff timer. + fastForwardToAttempt(payment.stateAttempts) + } + // Last attempt fails, but no more retries. + const payment = await processNext( + paymentId, + OutgoingPaymentState.Failed, + Pay.PaymentError.ClosedByReceiver + ) + expect(payment.stateAttempts).toBe(0) + // "mockPay" allows a small amount of money to be paid every attempt. + await expectOutcome(payment, { + accountBalance: BigInt(123 - 10 * 5), + amountSent: BigInt(10 * 5), + amountDelivered: BigInt(5 * 5), + withdrawAmount: BigInt(123 - 10 * 5) }) + }) - it('FAILED (non-retryable Pay error)', async (): Promise => { - mockPay( - { - maxSourceAmount: BigInt(10), - minDeliveryAmount: BigInt(5) - }, - Pay.PaymentError.ReceiverProtocolViolation - ) - const paymentId = await setup({ - receiver, - debitAmount - }) + it('FAILED (non-retryable Pay error)', async (): Promise => { + mockPay( + { + maxSourceAmount: BigInt(10), + minDeliveryAmount: BigInt(5) + }, + Pay.PaymentError.ReceiverProtocolViolation + ) + const paymentId = await setup({ + receiver, + debitAmount + }) - const payment = await processNext( - paymentId, - OutgoingPaymentState.Failed, - Pay.PaymentError.ReceiverProtocolViolation - ) - await expectOutcome(payment, { - accountBalance: BigInt(123 - 10), - amountSent: BigInt(10), - amountDelivered: BigInt(5), - withdrawAmount: BigInt(123 - 10) - }) + const payment = await processNext( + paymentId, + OutgoingPaymentState.Failed, + Pay.PaymentError.ReceiverProtocolViolation + ) + await expectOutcome(payment, { + accountBalance: BigInt(123 - 10), + amountSent: BigInt(10), + amountDelivered: BigInt(5), + withdrawAmount: BigInt(123 - 10) }) + }) - it('SENDING→COMPLETED (partial payment, resume, complete)', async (): Promise => { - const mockFn = mockPay( - { - maxSourceAmount: BigInt(10), - minDeliveryAmount: BigInt(5) - }, - Pay.PaymentError.ClosedByReceiver - ) - const paymentId = await setup({ - receiver, - receiveAmount - }) + it('SENDING→COMPLETED (partial payment, resume, complete)', async (): Promise => { + const mockFn = mockPay( + { + maxSourceAmount: BigInt(10), + minDeliveryAmount: BigInt(5) + }, + Pay.PaymentError.ClosedByReceiver + ) + const paymentId = await setup({ + receiver, + receiveAmount + }) - const payment = await processNext( - paymentId, - OutgoingPaymentState.Sending - ) - mockFn.mockRestore() - fastForwardToAttempt(1) - await expectOutcome(payment, { - accountBalance: payment.debitAmount.value - BigInt(10), - amountSent: BigInt(10), - amountDelivered: BigInt(5) - }) + const payment = await processNext(paymentId, OutgoingPaymentState.Sending) + mockFn.mockRestore() + fastForwardToAttempt(1) + await expectOutcome(payment, { + accountBalance: payment.debitAmount.value - BigInt(10), + amountSent: BigInt(10), + amountDelivered: BigInt(5) + }) - // The next attempt is without the mock, so it succeeds. - const payment2 = await processNext( - paymentId, - OutgoingPaymentState.Completed - ) - const sentAmount = payment.receiveAmount.value * BigInt(2) - await expectOutcome(payment2, { - accountBalance: payment.debitAmount.value - sentAmount, - amountSent: sentAmount, - amountDelivered: payment.receiveAmount.value - }) + // The next attempt is without the mock, so it succeeds. + const payment2 = await processNext( + paymentId, + OutgoingPaymentState.Completed + ) + const sentAmount = payment.receiveAmount.value * BigInt(2) + await expectOutcome(payment2, { + accountBalance: payment.debitAmount.value - sentAmount, + amountSent: sentAmount, + amountDelivered: payment.receiveAmount.value }) + }) - // Caused by retry after failed SENDING→COMPLETED transition commit. - it('COMPLETED (already fully paid)', async (): Promise => { - const paymentId = await setup( - { - receiver, - receiveAmount - }, + // Caused by retry after failed SENDING→COMPLETED transition commit. + it('COMPLETED (already fully paid)', async (): Promise => { + const paymentId = await setup( + { + receiver, receiveAmount - ) + }, + receiveAmount + ) - await processNext(paymentId, OutgoingPaymentState.Completed) - // Pretend that the transaction didn't commit. - await OutgoingPayment.query(knex) - .findById(paymentId) - .patch({ state: OutgoingPaymentState.Sending }) - const payment = await processNext( - paymentId, - OutgoingPaymentState.Completed - ) - const sentAmount = payment.receiveAmount.value * BigInt(2) - await expectOutcome(payment, { - accountBalance: payment.debitAmount.value - sentAmount, - amountSent: sentAmount, - amountDelivered: payment.receiveAmount.value - }) + await processNext(paymentId, OutgoingPaymentState.Completed) + // Pretend that the transaction didn't commit. + await OutgoingPayment.query(knex) + .findById(paymentId) + .patch({ state: OutgoingPaymentState.Sending }) + const payment = await processNext( + paymentId, + OutgoingPaymentState.Completed + ) + const sentAmount = payment.receiveAmount.value * BigInt(2) + await expectOutcome(payment, { + accountBalance: payment.debitAmount.value - sentAmount, + amountSent: sentAmount, + amountDelivered: payment.receiveAmount.value }) + }) - it('COMPLETED (already fully paid)', async (): Promise => { - const paymentId = await setup( - { - receiver, - receiveAmount - }, + it('COMPLETED (already fully paid)', async (): Promise => { + const paymentId = await setup( + { + receiver, receiveAmount - ) - // The quote thinks there's a full amount to pay, but actually sending will find the incoming payment has been paid (e.g. by another payment). - await payIncomingPayment(receiveAmount.value) + }, + receiveAmount + ) + // The quote thinks there's a full amount to pay, but actually sending will find the incoming payment has been paid (e.g. by another payment). + await payIncomingPayment(receiveAmount.value) - const payment = await processNext( - paymentId, - OutgoingPaymentState.Completed - ) - await expectOutcome(payment, { - accountBalance: payment.debitAmount.value, - amountSent: BigInt(0), - amountDelivered: BigInt(0), - incomingPaymentReceived: receiveAmount.value, - withdrawAmount: payment.debitAmount.value - }) + const payment = await processNext( + paymentId, + OutgoingPaymentState.Completed + ) + await expectOutcome(payment, { + accountBalance: payment.debitAmount.value, + amountSent: BigInt(0), + amountDelivered: BigInt(0), + incomingPaymentReceived: receiveAmount.value, + withdrawAmount: payment.debitAmount.value }) + }) - it('FAILED (source asset changed)', async (): Promise => { - const paymentId = await setup({ - receiver, - debitAmount - }) - const { id: assetId } = await createAsset(deps, { - code: asset.code, - scale: asset.scale + 1 + it('FAILED (source asset changed)', async (): Promise => { + const paymentId = await setup({ + receiver, + debitAmount + }) + const { id: assetId } = await createAsset(deps, { + code: asset.code, + scale: asset.scale + 1 + }) + await OutgoingPayment.relatedQuery('paymentPointer') + .for(paymentId) + .patch({ + assetId }) - await OutgoingPayment.relatedQuery('walletAddress') - .for(paymentId) - .patch({ - assetId - }) - await processNext( - paymentId, - OutgoingPaymentState.Failed, - LifecycleError.SourceAssetConflict - ) - }) + await processNext( + paymentId, + OutgoingPaymentState.Failed, + LifecycleError.SourceAssetConflict + ) + }) - it('FAILED (destination asset changed)', async (): Promise => { - const paymentId = await setup({ - receiver, - debitAmount - }) - // Pretend that the destination asset was initially different. - await OutgoingPayment.relatedQuery('quote') - .for(paymentId) - .patch({ - receiveAmount: { - ...receiveAmount, - assetScale: 55 - } - }) - await processNext( - paymentId, - OutgoingPaymentState.Failed, - Pay.PaymentError.DestinationAssetConflict - ) + it('FAILED (destination asset changed)', async (): Promise => { + const paymentId = await setup({ + receiver, + debitAmount }) + // Pretend that the destination asset was initially different. + await OutgoingPayment.relatedQuery('quote') + .for(paymentId) + .patch({ + receiveAmount: { + ...receiveAmount, + assetScale: 55 + } + }) + await processNext( + paymentId, + OutgoingPaymentState.Failed, + Pay.PaymentError.DestinationAssetConflict + ) }) }) From 250fdfd74a42f8ed5eb9d256232a176dc8b6efd9 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Oct 2023 09:40:21 -0400 Subject: [PATCH 09/39] refactor(backend): remove connectionService.getUrl --- .../open_payments/connection/service.test.ts | 37 ------------- .../src/open_payments/connection/service.ts | 16 +----- .../src/open_payments/quote/service.test.ts | 54 +++++++++---------- 3 files changed, 26 insertions(+), 81 deletions(-) diff --git a/packages/backend/src/open_payments/connection/service.test.ts b/packages/backend/src/open_payments/connection/service.test.ts index 35c42a4d5a..77a754d4a7 100644 --- a/packages/backend/src/open_payments/connection/service.test.ts +++ b/packages/backend/src/open_payments/connection/service.test.ts @@ -1,6 +1,4 @@ -import base64url from 'base64url' import { Knex } from 'knex' - import { createTestApp, TestContainer } from '../../tests/app' import { ConnectionService } from './service' import { @@ -50,44 +48,9 @@ describe('Connection Service', (): void => { const connection = connectionService.get(incomingPayment) assert.ok(connection) expect(connection).toMatchObject({ - id: incomingPayment.connectionId, ilpAddress: expect.stringMatching(/^test\.rafiki\.[a-zA-Z0-9_-]{95}$/), sharedSecret: expect.any(Buffer) }) - expect(connection.url).toEqual( - `${Config.openPaymentsUrl}/connections/${incomingPayment.connectionId}` - ) - expect(connection.toOpenPaymentsType()).toEqual({ - id: connection.url, - ilpAddress: connection.ilpAddress, - sharedSecret: base64url(connection.sharedSecret || ''), - assetCode: incomingPayment.asset.code, - assetScale: incomingPayment.asset.scale - }) - }) - - test.each` - state - ${IncomingPaymentState.Completed} - ${IncomingPaymentState.Expired} - `( - `returns undefined for $state incoming payment`, - async ({ state }): Promise => { - await incomingPayment.$query(knex).patch({ - state, - expiresAt: - state === IncomingPaymentState.Expired ? new Date() : undefined - }) - expect(connectionService.get(incomingPayment)).toBeUndefined() - } - ) - }) - - describe('getUrl', (): void => { - test('returns connection url for incoming payment', (): void => { - expect(connectionService.getUrl(incomingPayment)).toEqual( - `${Config.openPaymentsUrl}/connections/${incomingPayment.connectionId}` - ) }) test.each` diff --git a/packages/backend/src/open_payments/connection/service.ts b/packages/backend/src/open_payments/connection/service.ts index ffbbe87e06..47ca9d26d3 100644 --- a/packages/backend/src/open_payments/connection/service.ts +++ b/packages/backend/src/open_payments/connection/service.ts @@ -1,8 +1,8 @@ import { StreamServer } from '@interledger/stream-receiver' - import { BaseService } from '../../shared/baseService' import { IncomingPayment } from '../payment/incoming/model' import { IlpAddress } from 'ilp-packet' + export interface Connection { ilpAddress: IlpAddress sharedSecret: Buffer @@ -10,7 +10,6 @@ export interface Connection { export interface ConnectionService { get(payment: IncomingPayment): Connection | undefined - getUrl(payment: IncomingPayment): string | undefined } export interface ServiceDependencies extends BaseService { @@ -29,8 +28,7 @@ export async function createConnectionService( logger: log } return { - get: (payment) => getConnection(deps, payment), - getUrl: (payment) => getConnectionUrl(deps, payment) + get: (payment) => getConnection(deps, payment) } } @@ -53,13 +51,3 @@ function getConnection( sharedSecret: credentials.sharedSecret } } - -function getConnectionUrl( - deps: ServiceDependencies, - payment: IncomingPayment -): string | undefined { - if (!payment.connectionId) { - return undefined - } - return `${deps.openPaymentsUrl}/connections/${payment.connectionId}` -} diff --git a/packages/backend/src/open_payments/quote/service.test.ts b/packages/backend/src/open_payments/quote/service.test.ts index 395d41abb1..d85d47a155 100644 --- a/packages/backend/src/open_payments/quote/service.test.ts +++ b/packages/backend/src/open_payments/quote/service.test.ts @@ -127,11 +127,10 @@ describe('QuoteService', (): void => { } describe.each` - toConnection | incomingAmount | description - ${true} | ${undefined} | ${'connection'} - ${false} | ${undefined} | ${'incomingPayment'} - ${false} | ${incomingAmount} | ${'incomingPayment.incomingAmount'} - `('$description', ({ toConnection, incomingAmount }): void => { + incomingAmount | description + ${undefined} | ${'incomingPayment'} + ${incomingAmount} | ${'incomingPayment.incomingAmount'} + `('$description', ({ incomingAmount }): void => { describe.each` debitAmount | receiveAmount | description ${debitAmount} | ${undefined} | ${'debitAmount'} @@ -147,12 +146,9 @@ describe('QuoteService', (): void => { walletAddressId: receivingWalletAddress.id, incomingAmount }) - const connectionService = await deps.use('connectionService') options = { walletAddressId: sendingWalletAddress.id, - receiver: toConnection - ? connectionService.getUrl(incomingPayment) - : incomingPayment.getUrl(receivingWalletAddress) + receiver: incomingPayment.getUrl(receivingWalletAddress) } if (debitAmount) options.debitAmount = debitAmount if (receiveAmount) options.receiveAmount = receiveAmount @@ -308,27 +304,25 @@ describe('QuoteService', (): void => { ) } - if (!toConnection) { - test.each` - state - ${IncomingPaymentState.Completed} - ${IncomingPaymentState.Expired} - `( - `returns ${QuoteError.InvalidReceiver} on $state receiver`, - async ({ state }): Promise => { - await incomingPayment.$query(knex).patch({ - state, - expiresAt: - state === IncomingPaymentState.Expired - ? new Date() - : undefined - }) - await expect(quoteService.create(options)).resolves.toEqual( - QuoteError.InvalidReceiver - ) - } - ) - } + test.each` + state + ${IncomingPaymentState.Completed} + ${IncomingPaymentState.Expired} + `( + `returns ${QuoteError.InvalidReceiver} on $state receiver`, + async ({ state }): Promise => { + await incomingPayment.$query(knex).patch({ + state, + expiresAt: + state === IncomingPaymentState.Expired + ? new Date() + : undefined + }) + await expect(quoteService.create(options)).resolves.toEqual( + QuoteError.InvalidReceiver + ) + } + ) } }) }) From fedc3d2ca238e8a56c013bf0d4e53d0d4313696f Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Oct 2023 11:18:52 -0400 Subject: [PATCH 10/39] refactor(backend): rm Receiver.fromConnection --- .../src/open_payments/receiver/model.test.ts | 42 ------------------- .../src/open_payments/receiver/model.ts | 15 ------- packages/backend/src/tests/outgoingPayment.ts | 28 ++++++++----- 3 files changed, 18 insertions(+), 67 deletions(-) diff --git a/packages/backend/src/open_payments/receiver/model.test.ts b/packages/backend/src/open_payments/receiver/model.test.ts index d3c65e2301..e6e705f2a4 100644 --- a/packages/backend/src/open_payments/receiver/model.test.ts +++ b/packages/backend/src/open_payments/receiver/model.test.ts @@ -131,46 +131,4 @@ describe('Receiver Model', (): void => { ) }) }) - - // describe('fromConnection', () => { - // test('creates receiver', async () => { - // const paymentPointer = await createPaymentPointer(deps) - // const incomingPayment = await createIncomingPayment(deps, { - // paymentPointerId: paymentPointer.id - // }) - - // const connection = connectionService.get(incomingPayment) - // assert(connection instanceof Connection) - - // const receiver = Receiver.fromConnection(connection.toOpenPaymentsType()) - - // expect(receiver).toEqual({ - // assetCode: incomingPayment.asset.code, - // assetScale: incomingPayment.asset.scale, - // incomingAmountValue: undefined, - // receivedAmountValue: undefined, - // ilpAddress: expect.any(String), - // sharedSecret: expect.any(Buffer), - // expiresAt: undefined - // }) - // }) - - // test('returns undefined if invalid ilpAdress', async () => { - // const paymentPointer = await createPaymentPointer(deps) - // const incomingPayment = await createIncomingPayment(deps, { - // paymentPointerId: paymentPointer.id - // }) - - // const connection = connectionService.get(incomingPayment) - // assert(connection instanceof Connection) - - // const openPaymentsConnection = connection.toOpenPaymentsType() - - // openPaymentsConnection.ilpAddress = 'not base 64 encoded' - - // expect(() => Receiver.fromConnection(openPaymentsConnection)).toThrow( - // 'Invalid ILP address on stream connection' - // ) - // }) - // }) }) diff --git a/packages/backend/src/open_payments/receiver/model.ts b/packages/backend/src/open_payments/receiver/model.ts index 77f0cbdde6..814e64b8ec 100644 --- a/packages/backend/src/open_payments/receiver/model.ts +++ b/packages/backend/src/open_payments/receiver/model.ts @@ -30,21 +30,6 @@ export class Receiver { public readonly assetScale: number public readonly incomingPayment: ReceiverIncomingPayment - // TODO: remove? still called 1 place - // static fromConnection(connection: Connection): Receiver | undefined { - // if (!isValidIlpAddress(connection.ilpAddress)) { - // throw new Error('Invalid ILP address on stream connection') - // } - - // return new this({ - // // id: connection.id, - // // assetCode: connection.assetCode, - // // assetScale: connection.assetScale, - // sharedSecret: connection.sharedSecret, - // ilpAddress: connection.ilpAddress - // }) - // } - constructor(incomingPayment: OpenPaymentsIncomingPaymentWithPaymentMethod) { if (!incomingPayment.methods.length) { throw new Error('Missing payment method(s) on incoming payment') diff --git a/packages/backend/src/tests/outgoingPayment.ts b/packages/backend/src/tests/outgoingPayment.ts index 957a60d623..58b6172a83 100644 --- a/packages/backend/src/tests/outgoingPayment.ts +++ b/packages/backend/src/tests/outgoingPayment.ts @@ -8,6 +8,8 @@ import { isOutgoingPaymentError } from '../open_payments/payment/outgoing/errors import { OutgoingPayment } from '../open_payments/payment/outgoing/model' import { CreateOutgoingPaymentOptions } from '../open_payments/payment/outgoing/service' import { LiquidityAccountType } from '../accounting/service' +import { createIncomingPayment } from './incomingPayment' +import assert from 'assert' export async function createOutgoingPayment( deps: IocContract, @@ -28,18 +30,24 @@ export async function createOutgoingPayment( const outgoingPaymentService = await deps.use('outgoingPaymentService') const receiverService = await deps.use('receiverService') if (options.validDestination === false) { - // TODO: mock differently? receiver.get not longer calls receiver.fromConnection. this is the only remaining caller of fromConnection + const paymentPointerService = await deps.use('paymentPointerService') const streamServer = await deps.use('streamServer') - const { ilpAddress, sharedSecret } = streamServer.generateCredentials() - jest.spyOn(receiverService, 'get').mockResolvedValueOnce( - Receiver.fromConnection({ - id: options.receiver, - ilpAddress, - sharedSecret: base64url(sharedSecret), - assetCode: quote.receiveAmount.assetCode, - assetScale: quote.receiveAmount.assetScale - }) + const connection = streamServer.generateCredentials() + + const incomingPayment = await createIncomingPayment(deps, { + paymentPointerId: options.paymentPointerId + }) + const paymentPointer = await paymentPointerService.get( + options.paymentPointerId ) + assert(paymentPointer) + jest + .spyOn(receiverService, 'get') + .mockResolvedValueOnce( + new Receiver( + incomingPayment.toOpenPaymentsType(paymentPointer, connection) + ) + ) } const outgoingPaymentOrError = await outgoingPaymentService.create({ ...options, From 3fce224edb7be5b949e6e97976cee665d646729a Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Oct 2023 11:57:32 -0400 Subject: [PATCH 11/39] fix(backend): spsps middleware ctx type --- packages/backend/src/payment-method/ilp/spsp/middleware.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/payment-method/ilp/spsp/middleware.ts b/packages/backend/src/payment-method/ilp/spsp/middleware.ts index 3d36447f77..3e1eba8a9c 100644 --- a/packages/backend/src/payment-method/ilp/spsp/middleware.ts +++ b/packages/backend/src/payment-method/ilp/spsp/middleware.ts @@ -1,9 +1,10 @@ -import { WalletAddressContext, SPSPContext } from '../../../app' -import { ConnectionContext } from '../../../open_payments/connection/middleware' +import { AppContext, WalletAddressContext, SPSPContext } from '../../../app' +import { IncomingPayment } from '../../../open_payments/payment/incoming/model' -export type SPSPConnectionContext = ConnectionContext & +export type SPSPConnectionContext = AppContext & SPSPContext & { walletAddress?: never + incomingPayment: IncomingPayment } export type SPSPWalletAddressContext = WalletAddressContext & From 29c315429d37be3324bc32b32ab0d2a5bb7ab86c Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Oct 2023 12:16:41 -0400 Subject: [PATCH 12/39] refactor(backend): remove connection model --- .../src/open_payments/connection/model.ts | 59 ------------------- 1 file changed, 59 deletions(-) delete mode 100644 packages/backend/src/open_payments/connection/model.ts diff --git a/packages/backend/src/open_payments/connection/model.ts b/packages/backend/src/open_payments/connection/model.ts deleted file mode 100644 index ded90af834..0000000000 --- a/packages/backend/src/open_payments/connection/model.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { StreamCredentials } from '@interledger/stream-receiver' -import base64url from 'base64url' -import { IlpAddress } from 'ilp-packet' -import { ILPStreamConnection } from '@interledger/open-payments' -import { IncomingPayment } from '../payment/incoming/model' - -export abstract class ConnectionBase { - protected constructor( - public readonly ilpAddress: IlpAddress, - public readonly sharedSecret: Buffer, - public readonly assetCode: string, - public readonly assetScale: number - ) {} -} - -export class Connection extends ConnectionBase { - static fromPayment(options: { - payment: IncomingPayment - credentials: StreamCredentials - openPaymentsUrl: string - }): Connection | undefined { - if (!options.payment.connectionId) { - return undefined - } - return new this( - options.payment.connectionId, - options.openPaymentsUrl, - options.credentials.ilpAddress, - options.credentials.sharedSecret, - options.payment.asset.code, - options.payment.asset.scale - ) - } - - private constructor( - public readonly id: string, - private readonly openPaymentsUrl: string, - ilpAddress: IlpAddress, - sharedSecret: Buffer, - assetCode: string, - assetScale: number - ) { - super(ilpAddress, sharedSecret, assetCode, assetScale) - } - - public get url(): string { - return `${this.openPaymentsUrl}/connections/${this.id}` - } - - public toOpenPaymentsType(): ILPStreamConnection { - return { - id: this.url, - ilpAddress: this.ilpAddress, - sharedSecret: base64url(this.sharedSecret), - assetCode: this.assetCode, - assetScale: this.assetScale - } - } -} From ed18d01dea28477e27246abac4d55730ca796928 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Oct 2023 12:25:03 -0400 Subject: [PATCH 13/39] refactor(backend): use updated mocks --- .../open_payments/payment/incoming_remote/service.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/open_payments/payment/incoming_remote/service.test.ts b/packages/backend/src/open_payments/payment/incoming_remote/service.test.ts index d3af1379dd..946c190e29 100644 --- a/packages/backend/src/open_payments/payment/incoming_remote/service.test.ts +++ b/packages/backend/src/open_payments/payment/incoming_remote/service.test.ts @@ -14,7 +14,7 @@ import { mockPendingGrant, mockGrant, mockWalletAddress, - mockIncomingPaymentWithConnection + mockIncomingPaymentWithPaymentMethods } from '@interledger/open-payments' import { GrantService } from '../../grant/service' import { RemoteIncomingPaymentError } from './errors' @@ -130,7 +130,7 @@ describe('Remote Incoming Payment Service', (): void => { ${undefined} | ${undefined} | ${undefined} ${amount} | ${new Date(Date.now() + 30_000)} | ${{ description: 'Test incoming payment', externalRef: '#123' }} `('creates remote incoming payment ($#)', async (args): Promise => { - const mockedIncomingPayment = mockIncomingPaymentWithConnection({ + const mockedIncomingPayment = mockIncomingPaymentWithPaymentMethods({ ...args, walletAddressUrl: walletAddress.id }) @@ -197,7 +197,7 @@ describe('Remote Incoming Payment Service', (): void => { ] } } as AccessToken - const mockedIncomingPayment = mockIncomingPaymentWithConnection({ + const mockedIncomingPayment = mockIncomingPaymentWithPaymentMethods({ ...args, walletAddressUrl: walletAddress.id }) @@ -270,7 +270,7 @@ describe('Remote Incoming Payment Service', (): void => { ${undefined} | ${undefined} | ${undefined} ${amount} | ${new Date(Date.now() + 30_000)} | ${{ description: 'Test incoming payment', externalRef: '#123' }} `('creates remote incoming payment ($#)', async (args): Promise => { - const mockedIncomingPayment = mockIncomingPaymentWithConnection({ + const mockedIncomingPayment = mockIncomingPaymentWithPaymentMethods({ ...args, walletAddressUrl: walletAddress.id }) From 7a9dd1f4593f5eaa773d853944160a23383c9a3e Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Oct 2023 12:27:07 -0400 Subject: [PATCH 14/39] fix(backend): comment cleanup --- packages/backend/src/open_payments/payment/incoming/model.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index 1cd11aba4a..c3f4abeeb5 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -2,7 +2,6 @@ import { Model, ModelOptions, QueryContext } from 'objection' import { v4 as uuid } from 'uuid' import { Amount, AmountJSON, serializeAmount } from '../../amount' -// import { Connection } from '../../connection/model' import { Connection } from '../../connection/service' import { WalletAddress, @@ -15,8 +14,6 @@ import { WebhookEvent } from '../../../webhook/model' import { IncomingPayment as OpenPaymentsIncomingPayment, IncomingPaymentWithPaymentMethods as OpenPaymentsIncomingPaymentWithPaymentMethod - // IncomingPaymentWithConnection as OpenPaymentsIncomingPaymentWithConnection, - // IncomingPaymentWithConnectionUrl as OpenPaymentsIncomingPaymentWithConnectionUrl } from '@interledger/open-payments' import base64url from 'base64url' From 6253f50a7bd19c7bdf7e2e0dfe00ab12924f4838 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Oct 2023 12:28:12 -0400 Subject: [PATCH 15/39] refactor(backend): update to new open payments type --- packages/backend/src/open_payments/receiver/service.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/open_payments/receiver/service.test.ts b/packages/backend/src/open_payments/receiver/service.test.ts index 35ab5cdec3..ae61a312fa 100644 --- a/packages/backend/src/open_payments/receiver/service.test.ts +++ b/packages/backend/src/open_payments/receiver/service.test.ts @@ -5,7 +5,7 @@ import { AuthenticatedClient, AccessType, AccessAction, - IncomingPayment as OpenPaymentsIncomingPayment, + IncomingPaymentWithPaymentMethods as OpenPaymentsIncomingPaymentWithPaymentMethods, WalletAddress as OpenPaymentsWalletAddress, mockWalletAddress, Grant as OpenPaymentsGrant, @@ -118,7 +118,7 @@ describe('Receiver Service', (): void => { ${true} | ${'existing grant'} `('remote ($description)', ({ existingGrant }): void => { let walletAddress: OpenPaymentsWalletAddress - let incomingPayment: OpenPaymentsIncomingPayment + let incomingPayment: OpenPaymentsIncomingPaymentWithPaymentMethods const authServer = faker.internet.url({ appendSlash: false }) const INCOMING_PAYMENT_PATH = 'incoming-payments' const grantOptions = { From 63c1425215848876894f82e5becbc411c50a93e2 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:18:04 -0400 Subject: [PATCH 16/39] chore(backend): format --- .../backend/src/open_payments/payment/incoming/model.test.ts | 4 +--- .../src/open_payments/payment/incoming_remote/service.ts | 1 - .../src/open_payments/payment/outgoing/service.test.ts | 3 --- packages/backend/src/open_payments/receiver/service.test.ts | 1 - packages/backend/src/tests/outgoingPayment.ts | 1 - 5 files changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/backend/src/open_payments/payment/incoming/model.test.ts b/packages/backend/src/open_payments/payment/incoming/model.test.ts index 14a48d223e..ca1f27b371 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.test.ts @@ -1,6 +1,6 @@ import { IocContract } from '@adonisjs/fold' import { createTestApp, TestContainer } from '../../../tests/app' -import { Config, IAppConfig } from '../../../config/app' +import { Config } from '../../../config/app' import { initIocContainer } from '../../..' import { AppServices } from '../../../app' import { createIncomingPayment } from '../../../tests/incomingPayment' @@ -14,12 +14,10 @@ import { IncomingPayment } from './model' describe('Incoming Payment Model', (): void => { let deps: IocContract let appContainer: TestContainer - let config: IAppConfig beforeAll(async (): Promise => { deps = initIocContainer(Config) appContainer = await createTestApp(deps) - config = await deps.use('config') }) afterEach(async (): Promise => { diff --git a/packages/backend/src/open_payments/payment/incoming_remote/service.ts b/packages/backend/src/open_payments/payment/incoming_remote/service.ts index f5e46896e5..2ccb348951 100644 --- a/packages/backend/src/open_payments/payment/incoming_remote/service.ts +++ b/packages/backend/src/open_payments/payment/incoming_remote/service.ts @@ -1,6 +1,5 @@ import { AuthenticatedClient, - IncomingPayment as OpenPaymentsIncomingPayment, IncomingPaymentWithPaymentMethods as OpenPaymentsIncomingPaymentWithPaymentMethods, isPendingGrant, AccessAction, diff --git a/packages/backend/src/open_payments/payment/outgoing/service.test.ts b/packages/backend/src/open_payments/payment/outgoing/service.test.ts index 4d5c7770bf..fe89625592 100644 --- a/packages/backend/src/open_payments/payment/outgoing/service.test.ts +++ b/packages/backend/src/open_payments/payment/outgoing/service.test.ts @@ -43,7 +43,6 @@ import { isTransferError, TransferError } from '../../../accounting/errors' import { AccountingService, TransferOptions } from '../../../accounting/service' import { AssetOptions } from '../../../asset/service' import { Amount } from '../../amount' -import { ConnectionService } from '../../connection/service' import { getTests } from '../../wallet_address/model.test' import { Quote } from '../../quote/model' import { WalletAddress } from '../../wallet_address/model' @@ -53,7 +52,6 @@ describe('OutgoingPaymentService', (): void => { let appContainer: TestContainer let outgoingPaymentService: OutgoingPaymentService let accountingService: AccountingService - let connectionService: ConnectionService let knex: Knex let walletAddressId: string let incomingPayment: IncomingPayment @@ -241,7 +239,6 @@ describe('OutgoingPaymentService', (): void => { appContainer = await createTestApp(deps) outgoingPaymentService = await deps.use('outgoingPaymentService') accountingService = await deps.use('accountingService') - connectionService = await deps.use('connectionService') knex = appContainer.knex }) diff --git a/packages/backend/src/open_payments/receiver/service.test.ts b/packages/backend/src/open_payments/receiver/service.test.ts index ae61a312fa..d185735857 100644 --- a/packages/backend/src/open_payments/receiver/service.test.ts +++ b/packages/backend/src/open_payments/receiver/service.test.ts @@ -12,7 +12,6 @@ import { GrantRequest, mockIncomingPaymentWithPaymentMethods } from '@interledger/open-payments' -import { URL } from 'url' import { v4 as uuid } from 'uuid' import { ReceiverService } from './service' diff --git a/packages/backend/src/tests/outgoingPayment.ts b/packages/backend/src/tests/outgoingPayment.ts index 58b6172a83..4125b943e1 100644 --- a/packages/backend/src/tests/outgoingPayment.ts +++ b/packages/backend/src/tests/outgoingPayment.ts @@ -1,4 +1,3 @@ -import base64url from 'base64url' import { IocContract } from '@adonisjs/fold' import { createQuote, CreateTestQuoteOptions } from './quote' From 8512740ec929a68e7da5b568c895accd00fc9182 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:22:17 -0400 Subject: [PATCH 17/39] fix(backend): re-rename paymentPointer -> walletAddress --- .../src/open_payments/payment/incoming/model.ts | 2 +- .../open_payments/payment/outgoing/service.test.ts | 8 +++----- packages/backend/src/tests/outgoingPayment.ts | 12 ++++++------ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index c3f4abeeb5..7b54b6653d 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -218,7 +218,7 @@ export class IncomingPayment ilpStreamConnection: Connection ): OpenPaymentsIncomingPaymentWithPaymentMethod public toOpenPaymentsType( - paymentPointer: WalletAddress, + walletAddress: WalletAddress, ilpStreamConnection?: Connection ): OpenPaymentsIncomingPayment | OpenPaymentsIncomingPaymentWithPaymentMethod diff --git a/packages/backend/src/open_payments/payment/outgoing/service.test.ts b/packages/backend/src/open_payments/payment/outgoing/service.test.ts index fe89625592..3777ffc68b 100644 --- a/packages/backend/src/open_payments/payment/outgoing/service.test.ts +++ b/packages/backend/src/open_payments/payment/outgoing/service.test.ts @@ -1077,11 +1077,9 @@ describe('OutgoingPaymentService', (): void => { code: asset.code, scale: asset.scale + 1 }) - await OutgoingPayment.relatedQuery('paymentPointer') - .for(paymentId) - .patch({ - assetId - }) + await OutgoingPayment.relatedQuery('walletAddress').for(paymentId).patch({ + assetId + }) await processNext( paymentId, diff --git a/packages/backend/src/tests/outgoingPayment.ts b/packages/backend/src/tests/outgoingPayment.ts index 4125b943e1..8c59ed1196 100644 --- a/packages/backend/src/tests/outgoingPayment.ts +++ b/packages/backend/src/tests/outgoingPayment.ts @@ -29,22 +29,22 @@ export async function createOutgoingPayment( const outgoingPaymentService = await deps.use('outgoingPaymentService') const receiverService = await deps.use('receiverService') if (options.validDestination === false) { - const paymentPointerService = await deps.use('paymentPointerService') + const walletAddressService = await deps.use('walletAddressService') const streamServer = await deps.use('streamServer') const connection = streamServer.generateCredentials() const incomingPayment = await createIncomingPayment(deps, { - paymentPointerId: options.paymentPointerId + walletAddressId: options.walletAddressId }) - const paymentPointer = await paymentPointerService.get( - options.paymentPointerId + const walletAddress = await walletAddressService.get( + options.walletAddressId ) - assert(paymentPointer) + assert(walletAddress) jest .spyOn(receiverService, 'get') .mockResolvedValueOnce( new Receiver( - incomingPayment.toOpenPaymentsType(paymentPointer, connection) + incomingPayment.toOpenPaymentsType(walletAddress, connection) ) ) } From 9e2ccc1ad208c25e636d1bb0ae21a59819932e35 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:24:05 -0400 Subject: [PATCH 18/39] fix(backend): use receiver constructor --- packages/backend/src/tests/receiver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/tests/receiver.ts b/packages/backend/src/tests/receiver.ts index 1d0941491d..ac3100ed4f 100644 --- a/packages/backend/src/tests/receiver.ts +++ b/packages/backend/src/tests/receiver.ts @@ -18,7 +18,7 @@ export async function createReceiver( const connectionService = await deps.use('connectionService') - return Receiver.fromIncomingPayment( + return new Receiver( incomingPayment.toOpenPaymentsType( walletAddress, connectionService.get(incomingPayment)! From 43e2d5c35ed3507db4834b8ed88651b8c809021b Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Oct 2023 15:01:55 -0400 Subject: [PATCH 19/39] chore(backend): update open-payments packages --- packages/backend/package.json | 4 ++-- pnpm-lock.yaml | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index a88d18e432..bdb857e61c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -53,8 +53,8 @@ "@graphql-tools/load": "^8.0.0", "@graphql-tools/schema": "^10.0.0", "@interledger/http-signature-utils": "1.1.0", - "@interledger/open-payments": "5.0.0", - "@interledger/openapi": "1.0.3", + "@interledger/open-payments": "5.2.1", + "@interledger/openapi": "1.1.0", "@interledger/pay": "0.4.0-alpha.9", "@interledger/stream-receiver": "^0.3.3-alpha.3", "@koa/router": "^12.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2336f0247..789747c773 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -306,8 +306,8 @@ importers: specifier: 5.2.1 version: 5.2.1 '@interledger/openapi': - specifier: 1.0.3 - version: 1.0.3 + specifier: 1.1.0 + version: 1.1.0 '@interledger/pay': specifier: 0.4.0-alpha.9 version: 0.4.0-alpha.9 @@ -4415,6 +4415,21 @@ packages: - supports-color dev: false + /@interledger/open-payments@5.2.1: + resolution: {integrity: sha512-JzMOhu5eZRTaan4WFXclZo7eh0Ez+dk4ct7CpZC9nVu3N5rQK2FCG08HpCm55xBp5rKncmUQb6v0f+Bz/B7SHw==} + dependencies: + '@interledger/http-signature-utils': 1.1.0 + '@interledger/openapi': 1.2.0 + axios: 1.5.1 + base64url: 3.0.1 + http-message-signatures: 0.1.2 + pino: 8.16.0 + uuid: 9.0.1 + transitivePeerDependencies: + - debug + - supports-color + dev: false + /@interledger/openapi@1.0.3: resolution: {integrity: sha512-Xx/Me2tj8DhHpQR9cVjioX/DAUmQnaXWjt8hEVPIru4pDSMHCGG0RmpU2dV+aIiVmkDM2OvJUfKoLGsPVStqrA==} dependencies: @@ -4431,6 +4446,22 @@ packages: - supports-color dev: false + /@interledger/openapi@1.1.0: + resolution: {integrity: sha512-tCs2IBbSFdbquOkfO35RYMZ8IN161TupbtXniwvyEOu0yQHI5qiI0EWK6wNE4f8sx85+aiuGcOX61f0sjDM6rQ==} + dependencies: + '@apidevtools/json-schema-ref-parser': 10.1.0 + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + koa: 2.14.2 + openapi-default-setter: 12.1.3 + openapi-request-coercer: 12.1.3 + openapi-request-validator: 12.1.3 + openapi-response-validator: 12.1.3 + openapi-types: 12.1.3 + transitivePeerDependencies: + - supports-color + dev: false + /@interledger/openapi@1.2.0: resolution: {integrity: sha512-1+EglSaJfQd/p2a/odIs94xwaOmotWDAZjBT4J0TxXORT2gez83xpUg4mo36t4lD3sjVbHbARqTKiSg2P2jLnw==} dependencies: From 04051d144787eb0bee75283416ccd6002f07d3f2 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Oct 2023 20:27:40 -0400 Subject: [PATCH 20/39] fix(backend): quote open payments type --- packages/backend/src/open_payments/quote/model.ts | 10 ++++++++-- .../backend/src/open_payments/quote/routes.test.ts | 9 ++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/open_payments/quote/model.ts b/packages/backend/src/open_payments/quote/model.ts index ec27ced151..7c391e14ba 100644 --- a/packages/backend/src/open_payments/quote/model.ts +++ b/packages/backend/src/open_payments/quote/model.ts @@ -20,7 +20,8 @@ export class Quote extends WalletAddressSubresource { 'receiveAmount', 'minExchangeRate', 'lowEstimatedExchangeRate', - 'highEstimatedExchangeRate' + 'highEstimatedExchangeRate', + 'method' ] } @@ -152,6 +153,10 @@ export class Quote extends WalletAddressSubresource { this.highEstimatedExchangeRateDenominator = value.b.value } + public get method(): 'ilp' { + return 'ilp' + } + $formatJson(json: Pojo): Pojo { json = super.$formatJson(json) return { @@ -179,7 +184,8 @@ export class Quote extends WalletAddressSubresource { debitAmount: serializeAmount(this.debitAmount), receiver: this.receiver, expiresAt: this.expiresAt.toISOString(), - createdAt: this.createdAt.toISOString() + createdAt: this.createdAt.toISOString(), + method: this.method } } } diff --git a/packages/backend/src/open_payments/quote/routes.test.ts b/packages/backend/src/open_payments/quote/routes.test.ts index bff434f234..c152ba2685 100644 --- a/packages/backend/src/open_payments/quote/routes.test.ts +++ b/packages/backend/src/open_payments/quote/routes.test.ts @@ -104,7 +104,8 @@ describe('Quote Routes', (): void => { debitAmount: serializeAmount(quote.debitAmount), receiveAmount: serializeAmount(quote.receiveAmount), createdAt: quote.createdAt.toISOString(), - expiresAt: quote.expiresAt.toISOString() + expiresAt: quote.expiresAt.toISOString(), + method: quote.method } }, urlPath: Quote.urlPath @@ -229,7 +230,8 @@ describe('Quote Routes', (): void => { value: quote.receiveAmount.value.toString() }, createdAt: quote.createdAt.toISOString(), - expiresAt: quote.expiresAt.toISOString() + expiresAt: quote.expiresAt.toISOString(), + method: 'ilp' }) } ) @@ -276,7 +278,8 @@ describe('Quote Routes', (): void => { value: quote.receiveAmount.value.toString() }, createdAt: quote.createdAt.toISOString(), - expiresAt: quote.expiresAt.toISOString() + expiresAt: quote.expiresAt.toISOString(), + method: 'ilp' }) }) }) From f00958a9a9cedcc7a30e7649b5cb73eae0bd38cd Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Oct 2023 21:03:11 -0400 Subject: [PATCH 21/39] fix(backend): receiver service tests --- .../open_payments/receiver/service.test.ts | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/open_payments/receiver/service.test.ts b/packages/backend/src/open_payments/receiver/service.test.ts index d185735857..b11016aeea 100644 --- a/packages/backend/src/open_payments/receiver/service.test.ts +++ b/packages/backend/src/open_payments/receiver/service.test.ts @@ -106,7 +106,14 @@ describe('Receiver Service', (): void => { metadata: incomingPayment.metadata || undefined, expiresAt: incomingPayment.expiresAt, updatedAt: new Date(incomingPayment.updatedAt), - createdAt: new Date(incomingPayment.createdAt) + createdAt: new Date(incomingPayment.createdAt), + methods: [ + { + type: 'ilp', + ilpAddress: expect.any(String), + sharedSecret: expect.any(String) + } + ] } }) }) @@ -237,7 +244,14 @@ describe('Receiver Service', (): void => { incomingAmount: incomingPayment.incomingAmount && parseAmount(incomingPayment.incomingAmount), - expiresAt: incomingPayment.expiresAt + expiresAt: incomingPayment.expiresAt, + methods: [ + { + type: 'ilp', + ilpAddress: expect.any(String), + sharedSecret: expect.any(String) + } + ] } }) expect(clientGetWalletAddressSpy).toHaveBeenCalledWith({ @@ -449,7 +463,15 @@ describe('Receiver Service', (): void => { updatedAt: new Date(incomingPayment.updatedAt), createdAt: new Date(incomingPayment.createdAt), expiresAt: - incomingPayment.expiresAt && new Date(incomingPayment.expiresAt) + incomingPayment.expiresAt && + new Date(incomingPayment.expiresAt), + methods: [ + { + type: 'ilp', + ilpAddress: incomingPayment.methods[0].ilpAddress, + sharedSecret: expect.any(String) + } + ] } }) @@ -535,7 +557,14 @@ describe('Receiver Service', (): void => { metadata: receiver.incomingPayment?.metadata || undefined, updatedAt: receiver.incomingPayment?.updatedAt, createdAt: receiver.incomingPayment?.createdAt, - expiresAt: receiver.incomingPayment?.expiresAt + expiresAt: receiver.incomingPayment?.expiresAt, + methods: [ + { + type: 'ilp', + ilpAddress: receiver.ilpAddress, + sharedSecret: expect.any(String) + } + ] } }) From f1e47bcd90ce7e2c89e9d108b8a31dce22e549c9 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:09:22 -0400 Subject: [PATCH 22/39] fix: re-rename paymentPointer -> walletAddress --- postman/collections/Interledger.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/postman/collections/Interledger.json b/postman/collections/Interledger.json index 95ad6048ea..fd2e3f884a 100644 --- a/postman/collections/Interledger.json +++ b/postman/collections/Interledger.json @@ -1650,9 +1650,9 @@ } ], "url": { - "raw": "{{pfryPaymentPointer}}/incoming-payments/{{incomingPaymentId}}", + "raw": "{{pfryWalletAddress}}/incoming-payments/{{incomingPaymentId}}", "host": [ - "{{pfryPaymentPointer}}" + "{{pfryWalletAddress}}" ], "path": [ "incoming-payments", From bd9e4a6a1dd60b16779924264458251bab188070 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:45:23 -0400 Subject: [PATCH 23/39] refactor(postman): rm connection, add method --- postman/collections/Interledger.json | 42 +++------------------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/postman/collections/Interledger.json b/postman/collections/Interledger.json index fd2e3f884a..17859b3e05 100644 --- a/postman/collections/Interledger.json +++ b/postman/collections/Interledger.json @@ -1443,39 +1443,6 @@ ], "id": "eb2dba4c-bb3b-4e31-89d1-17e24a158638" }, - { - "name": "STREAM connection", - "item": [ - { - "name": "Get STREAM credentials", - "id": "ee878ea2-ae30-4d12-b617-d28a92c98d86", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [ - { - "key": "Host", - "value": "happy-life-bank-backend" - } - ], - "url": { - "raw": "{{PeerOpenPaymentsHost}}/connections/{{connectionId}}", - "host": [ - "{{PeerOpenPaymentsHost}}" - ], - "path": [ - "connections", - "{{connectionId}}" - ] - } - }, - "response": [] - } - ], - "id": "710e5513-49bf-45ab-b15b-efee78a76462" - }, { "name": "Incoming Payments", "item": [ @@ -1505,7 +1472,6 @@ "", "const body = pm.response.json();", "pm.collectionVariables.set(\"incomingPaymentId\", body.id.split(\"/\").pop());", - "pm.collectionVariables.set(\"connectionId\", body.ilpStreamConnection.id.split(\"/\").pop());", "" ], "type": "text/javascript" @@ -1707,9 +1673,9 @@ } ], "url": { - "raw": "{{pfryPaymentPointer}}/incoming-payments/{{incomingPaymentId}}", + "raw": "{{pfryWalletAddress}}/incoming-payments/{{incomingPaymentId}}", "host": [ - "{{pfryPaymentPointer}}" + "{{pfryWalletAddress}}" ], "path": [ "incoming-payments", @@ -1883,7 +1849,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"receiver\": \"https://happy-life-bank-backend/accounts/pfry/incoming-payments/{{incomingPaymentId}}\",\n \"debitAmount\": {\n \"value\": \"1000\",\n \"assetCode\": \"USD\",\n \"assetScale\": 2\n }\n}", + "raw": "{\n \"receiver\": \"https://happy-life-bank-backend/accounts/pfry/incoming-payments/{{incomingPaymentId}}\",\n \"debitAmount\": {\n \"value\": \"1000\",\n \"assetCode\": \"USD\",\n \"assetScale\": 2\n },\n \"method\": \"ilp\"\n}", "options": { "raw": { "language": "json" @@ -2611,7 +2577,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"receiver\": \"https://happy-life-bank-backend/accounts/pfry/incoming-payments/{{incomingPaymentId}}\"\n}", + "raw": "{\n \"receiver\": \"https://happy-life-bank-backend/accounts/pfry/incoming-payments/{{incomingPaymentId}}\",\n \"method\": \"ilp\"\n}", "options": { "raw": { "language": "json" From 49ddb99843a3d5f4f749759b83a7b1ba388c2f8d Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:37:04 -0400 Subject: [PATCH 24/39] refactor(backend): remove IncomingPayment.connectionId --- ..._drop_connections_from_incoming_payment.js | 19 ++++++++++ .../src/open_payments/connection/service.ts | 11 ++++-- .../open_payments/payment/incoming/model.ts | 21 +---------- .../payment/incoming/service.test.ts | 36 +++++++------------ 4 files changed, 41 insertions(+), 46 deletions(-) create mode 100644 packages/backend/migrations/20231019142543_drop_connections_from_incoming_payment.js diff --git a/packages/backend/migrations/20231019142543_drop_connections_from_incoming_payment.js b/packages/backend/migrations/20231019142543_drop_connections_from_incoming_payment.js new file mode 100644 index 0000000000..87f9d05aa4 --- /dev/null +++ b/packages/backend/migrations/20231019142543_drop_connections_from_incoming_payment.js @@ -0,0 +1,19 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function (knex) { + return knex.schema.alterTable('incomingPayments', function (table) { + table.dropColumn('connectionId') + }) +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) { + return knex.schema.alterTable('incomingPayments', function (table) { + table.uuid('connectionId').nullable() + }) +} diff --git a/packages/backend/src/open_payments/connection/service.ts b/packages/backend/src/open_payments/connection/service.ts index 47ca9d26d3..4a55354cc2 100644 --- a/packages/backend/src/open_payments/connection/service.ts +++ b/packages/backend/src/open_payments/connection/service.ts @@ -1,6 +1,9 @@ import { StreamServer } from '@interledger/stream-receiver' import { BaseService } from '../../shared/baseService' -import { IncomingPayment } from '../payment/incoming/model' +import { + IncomingPayment, + IncomingPaymentState +} from '../payment/incoming/model' import { IlpAddress } from 'ilp-packet' export interface Connection { @@ -36,7 +39,11 @@ function getConnection( deps: ServiceDependencies, payment: IncomingPayment ): Connection | undefined { - if (!payment.connectionId) { + if ( + [IncomingPaymentState.Completed, IncomingPaymentState.Expired].includes( + payment.state + ) + ) { return undefined } const credentials = deps.streamServer.generateCredentials({ diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index 7b54b6653d..6bae157a88 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -1,5 +1,4 @@ -import { Model, ModelOptions, QueryContext } from 'objection' -import { v4 as uuid } from 'uuid' +import { Model } from 'objection' import { Amount, AmountJSON, serializeAmount } from '../../amount' import { Connection } from '../../connection/service' @@ -87,8 +86,6 @@ export class IncomingPayment public expiresAt!: Date public state!: IncomingPaymentState public metadata?: Record - // The "| null" is necessary so that `$beforeUpdate` can modify a patch to remove the connectionId. If `$beforeUpdate` set `error = undefined`, the patch would ignore the modification. - public connectionId?: string | null public processAt!: Date | null @@ -194,22 +191,6 @@ export class IncomingPayment return data } - public $beforeInsert(context: QueryContext): void { - super.$beforeInsert(context) - this.connectionId = this.connectionId || uuid() - } - - public $beforeUpdate(opts: ModelOptions, queryContext: QueryContext): void { - super.$beforeUpdate(opts, queryContext) - if ( - [IncomingPaymentState.Completed, IncomingPaymentState.Expired].includes( - this.state - ) - ) { - this.connectionId = null - } - } - public toOpenPaymentsType( walletAddress: WalletAddress ): OpenPaymentsIncomingPayment diff --git a/packages/backend/src/open_payments/payment/incoming/service.test.ts b/packages/backend/src/open_payments/payment/incoming/service.test.ts index 3c69334e28..6a2b39fbb1 100644 --- a/packages/backend/src/open_payments/payment/incoming/service.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/service.test.ts @@ -321,8 +321,7 @@ describe('Incoming Payment Service', (): void => { ).resolves.toMatchObject({ id: incomingPayment.id, state: IncomingPaymentState.Completed, - processAt: new Date(now.getTime() + 30_000), - connectionId: null + processAt: new Date(now.getTime() + 30_000) }) await expect( incomingPaymentService.get({ @@ -330,8 +329,7 @@ describe('Incoming Payment Service', (): void => { }) ).resolves.toMatchObject({ state: IncomingPaymentState.Completed, - processAt: new Date(now.getTime() + 30_000), - connectionId: null + processAt: new Date(now.getTime() + 30_000) }) }) }) @@ -400,8 +398,7 @@ describe('Incoming Payment Service', (): void => { }) ).resolves.toMatchObject({ state: IncomingPaymentState.Expired, - processAt: new Date(now.getTime() + 30_000), - connectionId: null + processAt: new Date(now.getTime() + 30_000) }) }) @@ -482,8 +479,7 @@ describe('Incoming Payment Service', (): void => { eventType === IncomingPaymentEventType.IncomingPaymentExpired ? IncomingPaymentState.Expired : IncomingPaymentState.Completed, - processAt: expect.any(Date), - connectionId: null + processAt: expect.any(Date) }) await expect( accountingService.getTotalReceived(incomingPayment.id) @@ -550,8 +546,7 @@ describe('Incoming Payment Service', (): void => { ).resolves.toMatchObject({ id: incomingPayment.id, state: IncomingPaymentState.Completed, - processAt: new Date(now.getTime() + 30_000), - connectionId: null + processAt: new Date(now.getTime() + 30_000) }) await expect( incomingPaymentService.get({ @@ -559,8 +554,7 @@ describe('Incoming Payment Service', (): void => { }) ).resolves.toMatchObject({ state: IncomingPaymentState.Completed, - processAt: new Date(now.getTime() + 30_000), - connectionId: null + processAt: new Date(now.getTime() + 30_000) }) }) @@ -586,8 +580,7 @@ describe('Incoming Payment Service', (): void => { ).resolves.toMatchObject({ id: incomingPayment.id, state: IncomingPaymentState.Completed, - processAt: new Date(incomingPayment.expiresAt.getTime()), - connectionId: null + processAt: new Date(incomingPayment.expiresAt.getTime()) }) await expect( incomingPaymentService.get({ @@ -595,8 +588,7 @@ describe('Incoming Payment Service', (): void => { }) ).resolves.toMatchObject({ state: IncomingPaymentState.Completed, - processAt: new Date(incomingPayment.expiresAt.getTime()), - connectionId: null + processAt: new Date(incomingPayment.expiresAt.getTime()) }) }) @@ -619,8 +611,7 @@ describe('Incoming Payment Service', (): void => { id: incomingPayment.id }) ).resolves.toMatchObject({ - state: IncomingPaymentState.Expired, - connectionId: null + state: IncomingPaymentState.Expired }) await expect( incomingPaymentService.complete(incomingPayment.id) @@ -630,8 +621,7 @@ describe('Incoming Payment Service', (): void => { id: incomingPayment.id }) ).resolves.toMatchObject({ - state: IncomingPaymentState.Expired, - connectionId: null + state: IncomingPaymentState.Expired }) }) @@ -644,8 +634,7 @@ describe('Incoming Payment Service', (): void => { id: incomingPayment.id }) ).resolves.toMatchObject({ - state: IncomingPaymentState.Completed, - connectionId: null + state: IncomingPaymentState.Completed }) await expect( incomingPaymentService.complete(incomingPayment.id) @@ -655,8 +644,7 @@ describe('Incoming Payment Service', (): void => { id: incomingPayment.id }) ).resolves.toMatchObject({ - state: IncomingPaymentState.Completed, - connectionId: null + state: IncomingPaymentState.Completed }) }) }) From 05bcfcf2219debce50bbaa7ee6ac5d9eb324152c Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:37:19 -0400 Subject: [PATCH 25/39] fix(backend): bad receiver model test setup --- .../backend/src/open_payments/receiver/model.test.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/open_payments/receiver/model.test.ts b/packages/backend/src/open_payments/receiver/model.test.ts index e6e705f2a4..ce94c866c0 100644 --- a/packages/backend/src/open_payments/receiver/model.test.ts +++ b/packages/backend/src/open_payments/receiver/model.test.ts @@ -6,11 +6,12 @@ import { AppServices } from '../../app' import { createIncomingPayment } from '../../tests/incomingPayment' import { createWalletAddress } from '../../tests/walletAddress' import { truncateTables } from '../../tests/tableManager' -import { ConnectionService } from '../connection/service' +import { Connection, ConnectionService } from '../connection/service' import { Receiver } from './model' import { IncomingPaymentState } from '../payment/incoming/model' import assert from 'assert' import base64url from 'base64url' +import { IlpAddress } from 'ilp-packet' describe('Receiver Model', (): void => { let deps: IocContract @@ -78,8 +79,10 @@ describe('Receiver Model', (): void => { }) incomingPayment.state = IncomingPaymentState.Completed - const connection = connectionService.get(incomingPayment) - assert(connection) + const connection: Connection = { + ilpAddress: 'test.ilp' as IlpAddress, + sharedSecret: Buffer.from('') + } const openPaymentsIncomingPayment = incomingPayment.toOpenPaymentsType( walletAddress, @@ -99,7 +102,6 @@ describe('Receiver Model', (): void => { incomingPayment.expiresAt = new Date(Date.now() - 1) const connection = connectionService.get(incomingPayment) - assert(connection) const openPaymentsIncomingPayment = incomingPayment.toOpenPaymentsType( walletAddress, From 318af8e73675bd4d91eb15b36405f5047c6ec18b Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Thu, 19 Oct 2023 12:06:18 -0400 Subject: [PATCH 26/39] fix(backend): remove todo --- packages/backend/src/open_payments/receiver/model.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/backend/src/open_payments/receiver/model.ts b/packages/backend/src/open_payments/receiver/model.ts index 814e64b8ec..9de55d5ceb 100644 --- a/packages/backend/src/open_payments/receiver/model.ts +++ b/packages/backend/src/open_payments/receiver/model.ts @@ -65,7 +65,6 @@ export class Receiver { this.ilpAddress = ilpMethod.ilpAddress this.sharedSecret = base64url.toBuffer(ilpMethod.sharedSecret) - // TODO: ensure these are the right assetCode/Scales this.assetCode = incomingPayment.receivedAmount.assetCode this.assetScale = incomingPayment.receivedAmount.assetScale From 8d50c52199da20bed8b82bc1c19b92249bf48562 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:09:15 -0400 Subject: [PATCH 27/39] refactor(backend): rename connectionService to streamCredentialService --- packages/backend/src/index.ts | 10 ++--- .../open_payments/connection/service.test.ts | 18 ++++----- .../src/open_payments/connection/service.ts | 28 ++++++------- .../payment/incoming/model.test.ts | 10 ++--- .../open_payments/payment/incoming/model.ts | 14 +++---- .../open_payments/payment/incoming/routes.ts | 22 ++++++----- .../src/open_payments/receiver/model.test.ts | 39 ++++++++++--------- .../open_payments/receiver/service.test.ts | 16 +++++--- .../src/open_payments/receiver/service.ts | 26 ++++++++----- .../ilp/auto-peering/service.test.ts | 4 +- packages/backend/src/tests/outgoingPayment.ts | 4 +- packages/backend/src/tests/receiver.ts | 4 +- 12 files changed, 103 insertions(+), 92 deletions(-) diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 7e71eb1b51..188de6d7dd 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -37,7 +37,7 @@ import { createWebhookService } from './webhook/service' import { createConnectorService } from './payment-method/ilp/connector' import { createOpenAPI } from '@interledger/openapi' import { createAuthenticatedClient as createOpenPaymentsClient } from '@interledger/open-payments' -import { createConnectionService } from './open_payments/connection/service' +import { createStreamCredentialsService } from './open_payments/connection/service' import { createWalletAddressKeyService } from './open_payments/wallet_address/key/service' import { createReceiverService } from './open_payments/receiver/service' import { createRemoteIncomingPaymentService } from './open_payments/payment/incoming_remote/service' @@ -267,7 +267,7 @@ export function initIocContainer( config: await deps.use('config'), logger: await deps.use('logger'), incomingPaymentService: await deps.use('incomingPaymentService'), - connectionService: await deps.use('connectionService') + streamCredentialsService: await deps.use('streamCredentialsService') }) }) container.singleton('walletAddressRoutes', async (deps) => { @@ -283,9 +283,9 @@ export function initIocContainer( walletAddressService: await deps.use('walletAddressService') }) }) - container.singleton('connectionService', async (deps) => { + container.singleton('streamCredentialsService', async (deps) => { const config = await deps.use('config') - return await createConnectionService({ + return await createStreamCredentialsService({ logger: await deps.use('logger'), openPaymentsUrl: config.openPaymentsUrl, streamServer: await deps.use('streamServer') @@ -295,7 +295,7 @@ export function initIocContainer( const config = await deps.use('config') return await createReceiverService({ logger: await deps.use('logger'), - connectionService: await deps.use('connectionService'), + streamCredentialsService: await deps.use('streamCredentialsService'), grantService: await deps.use('grantService'), incomingPaymentService: await deps.use('incomingPaymentService'), openPaymentsUrl: config.openPaymentsUrl, diff --git a/packages/backend/src/open_payments/connection/service.test.ts b/packages/backend/src/open_payments/connection/service.test.ts index 77a754d4a7..2a732c35ba 100644 --- a/packages/backend/src/open_payments/connection/service.test.ts +++ b/packages/backend/src/open_payments/connection/service.test.ts @@ -1,6 +1,6 @@ import { Knex } from 'knex' import { createTestApp, TestContainer } from '../../tests/app' -import { ConnectionService } from './service' +import { StreamCredentialsService } from './service' import { IncomingPayment, IncomingPaymentState @@ -14,17 +14,17 @@ import { createWalletAddress } from '../../tests/walletAddress' import { truncateTables } from '../../tests/tableManager' import assert from 'assert' -describe('Connection Service', (): void => { +describe('Stream Credentials Service', (): void => { let deps: IocContract let appContainer: TestContainer - let connectionService: ConnectionService + let streamCredentialsService: StreamCredentialsService let knex: Knex let incomingPayment: IncomingPayment beforeAll(async (): Promise => { deps = await initIocContainer(Config) appContainer = await createTestApp(deps) - connectionService = await deps.use('connectionService') + streamCredentialsService = await deps.use('streamCredentialsService') knex = appContainer.knex }) @@ -44,10 +44,10 @@ describe('Connection Service', (): void => { }) describe('get', (): void => { - test('returns connection for incoming payment', (): void => { - const connection = connectionService.get(incomingPayment) - assert.ok(connection) - expect(connection).toMatchObject({ + test('returns stream credentials for incoming payment', (): void => { + const credentials = streamCredentialsService.get(incomingPayment) + assert.ok(credentials) + expect(credentials).toMatchObject({ ilpAddress: expect.stringMatching(/^test\.rafiki\.[a-zA-Z0-9_-]{95}$/), sharedSecret: expect.any(Buffer) }) @@ -65,7 +65,7 @@ describe('Connection Service', (): void => { expiresAt: state === IncomingPaymentState.Expired ? new Date() : undefined }) - expect(connectionService.get(incomingPayment)).toBeUndefined() + expect(streamCredentialsService.get(incomingPayment)).toBeUndefined() } ) }) diff --git a/packages/backend/src/open_payments/connection/service.ts b/packages/backend/src/open_payments/connection/service.ts index 4a55354cc2..05dbec8270 100644 --- a/packages/backend/src/open_payments/connection/service.ts +++ b/packages/backend/src/open_payments/connection/service.ts @@ -4,15 +4,12 @@ import { IncomingPayment, IncomingPaymentState } from '../payment/incoming/model' -import { IlpAddress } from 'ilp-packet' +import { StreamCredentials as IlpStreamCredentials } from '@interledger/stream-receiver' -export interface Connection { - ilpAddress: IlpAddress - sharedSecret: Buffer -} +export { IlpStreamCredentials } -export interface ConnectionService { - get(payment: IncomingPayment): Connection | undefined +export interface StreamCredentialsService { + get(payment: IncomingPayment): IlpStreamCredentials | undefined } export interface ServiceDependencies extends BaseService { @@ -20,25 +17,25 @@ export interface ServiceDependencies extends BaseService { streamServer: StreamServer } -export async function createConnectionService( +export async function createStreamCredentialsService( deps_: ServiceDependencies -): Promise { +): Promise { const log = deps_.logger.child({ - service: 'ConnectionService' + service: 'StreamCredentialsService' }) const deps: ServiceDependencies = { ...deps_, logger: log } return { - get: (payment) => getConnection(deps, payment) + get: (payment) => getStreamCredentials(deps, payment) } } -function getConnection( +function getStreamCredentials( deps: ServiceDependencies, payment: IncomingPayment -): Connection | undefined { +): IlpStreamCredentials | undefined { if ( [IncomingPaymentState.Completed, IncomingPaymentState.Expired].includes( payment.state @@ -53,8 +50,5 @@ function getConnection( scale: payment.asset.scale } }) - return { - ilpAddress: credentials.ilpAddress, - sharedSecret: credentials.sharedSecret - } + return credentials } diff --git a/packages/backend/src/open_payments/payment/incoming/model.test.ts b/packages/backend/src/open_payments/payment/incoming/model.test.ts index ca1f27b371..2a2d043a78 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.test.ts @@ -6,7 +6,7 @@ import { AppServices } from '../../../app' import { createIncomingPayment } from '../../../tests/incomingPayment' import { createWalletAddress } from '../../../tests/walletAddress' import { truncateTables } from '../../../tests/tableManager' -import { Connection } from '../../connection/service' +import { IlpStreamCredentials } from '../../connection/service' import { serializeAmount } from '../../amount' import { IlpAddress } from 'ilp-packet' import { IncomingPayment } from './model' @@ -30,7 +30,7 @@ describe('Incoming Payment Model', (): void => { }) describe('toOpenPaymentsType', () => { - test('returns incoming payment without connection provided', async () => { + test('returns incoming payment without stream credentials provided', async () => { const walletAddress = await createWalletAddress(deps) const incomingPayment = await createIncomingPayment(deps, { walletAddressId: walletAddress.id, @@ -52,20 +52,20 @@ describe('Incoming Payment Model', (): void => { }) }) - test('returns incoming payment with connection as object', async () => { + test('returns incoming payment with stream credentials as object', async () => { const walletAddress = await createWalletAddress(deps) const incomingPayment = await createIncomingPayment(deps, { walletAddressId: walletAddress.id, metadata: { description: 'my payment' } }) - const connection: Connection = { + const streamCredentials: IlpStreamCredentials = { ilpAddress: 'test.ilp' as IlpAddress, sharedSecret: Buffer.from('') } expect( - incomingPayment.toOpenPaymentsType(walletAddress, connection) + incomingPayment.toOpenPaymentsType(walletAddress, streamCredentials) ).toEqual({ id: `${walletAddress.url}${IncomingPayment.urlPath}/${incomingPayment.id}`, walletAddress: walletAddress.url, diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index 6bae157a88..8c75adb3fe 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -1,7 +1,7 @@ import { Model } from 'objection' import { Amount, AmountJSON, serializeAmount } from '../../amount' -import { Connection } from '../../connection/service' +import { IlpStreamCredentials } from '../../connection/service' import { WalletAddress, WalletAddressSubresource @@ -196,16 +196,16 @@ export class IncomingPayment ): OpenPaymentsIncomingPayment public toOpenPaymentsType( walletAddress: WalletAddress, - ilpStreamConnection: Connection + ilpStreamCredentials: IlpStreamCredentials ): OpenPaymentsIncomingPaymentWithPaymentMethod public toOpenPaymentsType( walletAddress: WalletAddress, - ilpStreamConnection?: Connection + ilpStreamCredentials?: IlpStreamCredentials ): OpenPaymentsIncomingPayment | OpenPaymentsIncomingPaymentWithPaymentMethod public toOpenPaymentsType( walletAddress: WalletAddress, - ilpStreamConnection?: Connection + ilpStreamCredentials?: IlpStreamCredentials ): | OpenPaymentsIncomingPayment | OpenPaymentsIncomingPaymentWithPaymentMethod { @@ -223,14 +223,14 @@ export class IncomingPayment expiresAt: this.expiresAt.toISOString() } - if (ilpStreamConnection) { + if (ilpStreamCredentials) { return { ...baseIncomingPayment, methods: [ { type: 'ilp', - ilpAddress: ilpStreamConnection.ilpAddress, - sharedSecret: base64url(ilpStreamConnection.sharedSecret) + ilpAddress: ilpStreamCredentials.ilpAddress, + sharedSecret: base64url(ilpStreamCredentials.sharedSecret) } ] } diff --git a/packages/backend/src/open_payments/payment/incoming/routes.ts b/packages/backend/src/open_payments/payment/incoming/routes.ts index a5bd0eda8e..f632e10413 100644 --- a/packages/backend/src/open_payments/payment/incoming/routes.ts +++ b/packages/backend/src/open_payments/payment/incoming/routes.ts @@ -18,8 +18,10 @@ import { } from './errors' import { AmountJSON, parseAmount } from '../../amount' import { listSubresource } from '../../wallet_address/routes' -import { Connection } from '../../connection/service' -import { ConnectionService } from '../../connection/service' +import { + IlpStreamCredentials, + StreamCredentialsService +} from '../../connection/service' import { AccessAction, IncomingPayment as OpenPaymentsIncomingPayment, @@ -31,7 +33,7 @@ interface ServiceDependencies { config: IAppConfig logger: Logger incomingPaymentService: IncomingPaymentService - connectionService: ConnectionService + streamCredentialsService: StreamCredentialsService } export type ReadContextWithAuthenticatedStatus = ReadContext & @@ -105,11 +107,11 @@ async function getIncomingPaymentPrivate( ctx.throw(500, 'Error trying to get incoming payment') } if (!incomingPayment) return ctx.throw(404) - const connection = deps.connectionService.get(incomingPayment) + const streamCredentials = deps.streamCredentialsService.get(incomingPayment) ctx.body = incomingPaymentToBody( ctx.walletAddress, incomingPayment, - connection + streamCredentials ) } @@ -146,11 +148,13 @@ async function createIncomingPayment( } ctx.status = 201 - const connection = deps.connectionService.get(incomingPaymentOrError) + const streamCredentials = deps.streamCredentialsService.get( + incomingPaymentOrError + ) ctx.body = incomingPaymentToBody( ctx.walletAddress, incomingPaymentOrError, - connection + streamCredentials ) } @@ -196,7 +200,7 @@ async function listIncomingPayments( function incomingPaymentToBody( walletAddress: WalletAddress, incomingPayment: IncomingPayment, - ilpStreamConnection?: Connection + ilpStreamCredentials?: IlpStreamCredentials ): OpenPaymentsIncomingPayment | IncomingPaymentWithPaymentMethods { - return incomingPayment.toOpenPaymentsType(walletAddress, ilpStreamConnection) + return incomingPayment.toOpenPaymentsType(walletAddress, ilpStreamCredentials) } diff --git a/packages/backend/src/open_payments/receiver/model.test.ts b/packages/backend/src/open_payments/receiver/model.test.ts index ce94c866c0..5a2d4a319e 100644 --- a/packages/backend/src/open_payments/receiver/model.test.ts +++ b/packages/backend/src/open_payments/receiver/model.test.ts @@ -6,7 +6,10 @@ import { AppServices } from '../../app' import { createIncomingPayment } from '../../tests/incomingPayment' import { createWalletAddress } from '../../tests/walletAddress' import { truncateTables } from '../../tests/tableManager' -import { Connection, ConnectionService } from '../connection/service' +import { + IlpStreamCredentials, + StreamCredentialsService +} from '../connection/service' import { Receiver } from './model' import { IncomingPaymentState } from '../payment/incoming/model' import assert from 'assert' @@ -16,12 +19,12 @@ import { IlpAddress } from 'ilp-packet' describe('Receiver Model', (): void => { let deps: IocContract let appContainer: TestContainer - let connectionService: ConnectionService + let streamCredentialsService: StreamCredentialsService beforeAll(async (): Promise => { deps = initIocContainer(Config) appContainer = await createTestApp(deps) - connectionService = await deps.use('connectionService') + streamCredentialsService = await deps.use('streamCredentialsService') }) afterEach(async (): Promise => { @@ -40,11 +43,11 @@ describe('Receiver Model', (): void => { walletAddressId: walletAddress.id }) - const connection = connectionService.get(incomingPayment) - assert(connection) + const streamCredentials = streamCredentialsService.get(incomingPayment) + assert(streamCredentials) const receiver = new Receiver( - incomingPayment.toOpenPaymentsType(walletAddress, connection) + incomingPayment.toOpenPaymentsType(walletAddress, streamCredentials) ) expect(receiver).toEqual({ @@ -64,8 +67,8 @@ describe('Receiver Model', (): void => { methods: [ { type: 'ilp', - ilpAddress: connection.ilpAddress, - sharedSecret: base64url(connection.sharedSecret) + ilpAddress: streamCredentials.ilpAddress, + sharedSecret: base64url(streamCredentials.sharedSecret) } ] } @@ -79,14 +82,14 @@ describe('Receiver Model', (): void => { }) incomingPayment.state = IncomingPaymentState.Completed - const connection: Connection = { + const streamCredentials: IlpStreamCredentials = { ilpAddress: 'test.ilp' as IlpAddress, sharedSecret: Buffer.from('') } const openPaymentsIncomingPayment = incomingPayment.toOpenPaymentsType( walletAddress, - connection + streamCredentials ) expect(() => new Receiver(openPaymentsIncomingPayment)).toThrow( @@ -101,11 +104,11 @@ describe('Receiver Model', (): void => { }) incomingPayment.expiresAt = new Date(Date.now() - 1) - const connection = connectionService.get(incomingPayment) - assert(connection) + const streamCredentials = streamCredentialsService.get(incomingPayment) + assert(streamCredentials) const openPaymentsIncomingPayment = incomingPayment.toOpenPaymentsType( walletAddress, - connection + streamCredentials ) expect(() => new Receiver(openPaymentsIncomingPayment)).toThrow( @@ -113,19 +116,19 @@ describe('Receiver Model', (): void => { ) }) - test('throws if stream connection has invalid ILP address', async () => { + test('throws if stream credentials has invalid ILP address', async () => { const walletAddress = await createWalletAddress(deps) const incomingPayment = await createIncomingPayment(deps, { walletAddressId: walletAddress.id }) - const connection = connectionService.get(incomingPayment) - assert(connection) - ;(connection.ilpAddress as string) = 'not base 64 encoded' + const streamCredentials = streamCredentialsService.get(incomingPayment) + assert(streamCredentials) + ;(streamCredentials.ilpAddress as string) = 'not base 64 encoded' const openPaymentsIncomingPayment = incomingPayment.toOpenPaymentsType( walletAddress, - connection + streamCredentials ) expect(() => new Receiver(openPaymentsIncomingPayment)).toThrow( diff --git a/packages/backend/src/open_payments/receiver/service.test.ts b/packages/backend/src/open_payments/receiver/service.test.ts index b11016aeea..fd382e6d9d 100644 --- a/packages/backend/src/open_payments/receiver/service.test.ts +++ b/packages/backend/src/open_payments/receiver/service.test.ts @@ -25,7 +25,7 @@ import { MockWalletAddress } from '../../tests/walletAddress' import { truncateTables } from '../../tests/tableManager' -import { ConnectionService } from '../connection/service' +import { StreamCredentialsService } from '../connection/service' import { GrantService } from '../grant/service' import { WalletAddressService } from '../wallet_address/service' import { Amount, parseAmount } from '../amount' @@ -46,7 +46,7 @@ describe('Receiver Service', (): void => { let incomingPaymentService: IncomingPaymentService let openPaymentsClient: AuthenticatedClient let knex: Knex - let connectionService: ConnectionService + let streamCredentialsService: StreamCredentialsService let walletAddressService: WalletAddressService let grantService: GrantService let remoteIncomingPaymentService: RemoteIncomingPaymentService @@ -57,7 +57,7 @@ describe('Receiver Service', (): void => { receiverService = await deps.use('receiverService') incomingPaymentService = await deps.use('incomingPaymentService') openPaymentsClient = await deps.use('openPaymentsClient') - connectionService = await deps.use('connectionService') + streamCredentialsService = await deps.use('streamCredentialsService') walletAddressService = await deps.use('walletAddressService') grantService = await deps.use('grantService') remoteIncomingPaymentService = await deps.use( @@ -590,14 +590,18 @@ describe('Receiver Service', (): void => { ).resolves.toEqual(ReceiverError.InvalidAmount) }) - test('throws if error when getting connection for local incoming payment', async (): Promise => { - jest.spyOn(connectionService, 'get').mockReturnValueOnce(undefined) + test('throws if error when getting stream credentials for local incoming payment', async (): Promise => { + jest + .spyOn(streamCredentialsService, 'get') + .mockReturnValueOnce(undefined) await expect( receiverService.create({ walletAddressUrl: walletAddress.url }) - ).rejects.toThrow('Could not get connection for local incoming payment') + ).rejects.toThrow( + 'Could not get stream credentials for local incoming payment' + ) }) }) }) diff --git a/packages/backend/src/open_payments/receiver/service.ts b/packages/backend/src/open_payments/receiver/service.ts index a637462062..a1e6e879b7 100644 --- a/packages/backend/src/open_payments/receiver/service.ts +++ b/packages/backend/src/open_payments/receiver/service.ts @@ -5,7 +5,7 @@ import { AccessType, AccessAction } from '@interledger/open-payments' -import { ConnectionService } from '../connection/service' +import { StreamCredentialsService } from '../connection/service' import { Grant } from '../grant/model' import { GrantService } from '../grant/service' import { WalletAddressService } from '../wallet_address/service' @@ -29,14 +29,14 @@ interface CreateReceiverArgs { metadata?: Record } -// A receiver is resolved from an incoming payment or a connection +// A receiver is resolved from an incoming payment export interface ReceiverService { get(url: string): Promise create(args: CreateReceiverArgs): Promise } interface ServiceDependencies extends BaseService { - connectionService: ConnectionService + streamCredentialsService: StreamCredentialsService grantService: GrantService incomingPaymentService: IncomingPaymentService openPaymentsUrl: string @@ -123,16 +123,22 @@ async function createLocalIncomingPayment( return incomingPaymentOrError } - const connection = deps.connectionService.get(incomingPaymentOrError) + const streamCredentials = deps.streamCredentialsService.get( + incomingPaymentOrError + ) - if (!connection) { - const errorMessage = 'Could not get connection for local incoming payment' + if (!streamCredentials) { + const errorMessage = + 'Could not get stream credentials for local incoming payment' deps.logger.error({ incomingPaymentOrError }, errorMessage) throw new Error(errorMessage) } - return incomingPaymentOrError.toOpenPaymentsType(walletAddress, connection) + return incomingPaymentOrError.toOpenPaymentsType( + walletAddress, + streamCredentials + ) } async function getReceiver( @@ -219,13 +225,13 @@ async function getLocalIncomingPayment({ return undefined } - const connection = deps.connectionService.get(incomingPayment) + const streamCredentials = deps.streamCredentialsService.get(incomingPayment) - if (!connection) { + if (!streamCredentials) { return undefined } - return incomingPayment.toOpenPaymentsType(walletAddress, connection) + return incomingPayment.toOpenPaymentsType(walletAddress, streamCredentials) } async function getIncomingPaymentGrant( diff --git a/packages/backend/src/payment-method/ilp/auto-peering/service.test.ts b/packages/backend/src/payment-method/ilp/auto-peering/service.test.ts index cafc9fa1b3..32d6597234 100644 --- a/packages/backend/src/payment-method/ilp/auto-peering/service.test.ts +++ b/packages/backend/src/payment-method/ilp/auto-peering/service.test.ts @@ -45,7 +45,7 @@ describe('Auto Peering Service', (): void => { }) describe('acceptPeeringRequest', () => { - test('creates peer and resolves connection details', async (): Promise => { + test('creates peer and resolves stream credential details', async (): Promise => { const asset = await createAsset(deps) const args: PeeringRequestArgs = { @@ -83,7 +83,7 @@ describe('Auto Peering Service', (): void => { }) }) - test('updates connection details if duplicate peer request', async (): Promise => { + test('updates stream credentials details if duplicate peer request', async (): Promise => { const asset = await createAsset(deps) const args: PeeringRequestArgs = { diff --git a/packages/backend/src/tests/outgoingPayment.ts b/packages/backend/src/tests/outgoingPayment.ts index 8c59ed1196..b517ba1349 100644 --- a/packages/backend/src/tests/outgoingPayment.ts +++ b/packages/backend/src/tests/outgoingPayment.ts @@ -31,7 +31,7 @@ export async function createOutgoingPayment( if (options.validDestination === false) { const walletAddressService = await deps.use('walletAddressService') const streamServer = await deps.use('streamServer') - const connection = streamServer.generateCredentials() + const streamCredentials = streamServer.generateCredentials() const incomingPayment = await createIncomingPayment(deps, { walletAddressId: options.walletAddressId @@ -44,7 +44,7 @@ export async function createOutgoingPayment( .spyOn(receiverService, 'get') .mockResolvedValueOnce( new Receiver( - incomingPayment.toOpenPaymentsType(walletAddress, connection) + incomingPayment.toOpenPaymentsType(walletAddress, streamCredentials) ) ) } diff --git a/packages/backend/src/tests/receiver.ts b/packages/backend/src/tests/receiver.ts index ac3100ed4f..ed04051974 100644 --- a/packages/backend/src/tests/receiver.ts +++ b/packages/backend/src/tests/receiver.ts @@ -16,12 +16,12 @@ export async function createReceiver( walletAddressId: walletAddress.id }) - const connectionService = await deps.use('connectionService') + const streamCredentialsService = await deps.use('streamCredentialsService') return new Receiver( incomingPayment.toOpenPaymentsType( walletAddress, - connectionService.get(incomingPayment)! + streamCredentialsService.get(incomingPayment)! ) ) } From 8e2519f3fb9cdbd78be82d0222394488ac111cfd Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:31:54 -0400 Subject: [PATCH 28/39] refactor(backend): rename connection file and move to ilp --- packages/backend/src/index.ts | 2 +- .../open_payments/payment/incoming/model.test.ts | 2 +- .../src/open_payments/payment/incoming/model.ts | 2 +- .../src/open_payments/payment/incoming/routes.ts | 2 +- .../src/open_payments/receiver/model.test.ts | 2 +- .../src/open_payments/receiver/service.test.ts | 2 +- .../src/open_payments/receiver/service.ts | 2 +- .../ilp/stream-credentials}/service.test.ts | 16 ++++++++-------- .../ilp/stream-credentials}/service.ts | 4 ++-- 9 files changed, 17 insertions(+), 17 deletions(-) rename packages/backend/src/{open_payments/connection => payment-method/ilp/stream-credentials}/service.test.ts (80%) rename packages/backend/src/{open_payments/connection => payment-method/ilp/stream-credentials}/service.ts (91%) diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 188de6d7dd..fc6b911264 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -37,7 +37,7 @@ import { createWebhookService } from './webhook/service' import { createConnectorService } from './payment-method/ilp/connector' import { createOpenAPI } from '@interledger/openapi' import { createAuthenticatedClient as createOpenPaymentsClient } from '@interledger/open-payments' -import { createStreamCredentialsService } from './open_payments/connection/service' +import { createStreamCredentialsService } from './payment-method/ilp/stream-credentials/service' import { createWalletAddressKeyService } from './open_payments/wallet_address/key/service' import { createReceiverService } from './open_payments/receiver/service' import { createRemoteIncomingPaymentService } from './open_payments/payment/incoming_remote/service' diff --git a/packages/backend/src/open_payments/payment/incoming/model.test.ts b/packages/backend/src/open_payments/payment/incoming/model.test.ts index 2a2d043a78..56f237fd72 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.test.ts @@ -6,7 +6,7 @@ import { AppServices } from '../../../app' import { createIncomingPayment } from '../../../tests/incomingPayment' import { createWalletAddress } from '../../../tests/walletAddress' import { truncateTables } from '../../../tests/tableManager' -import { IlpStreamCredentials } from '../../connection/service' +import { IlpStreamCredentials } from '../../../payment-method/ilp/stream-credentials/service' import { serializeAmount } from '../../amount' import { IlpAddress } from 'ilp-packet' import { IncomingPayment } from './model' diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index 8c75adb3fe..98e4c56d77 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -1,7 +1,7 @@ import { Model } from 'objection' import { Amount, AmountJSON, serializeAmount } from '../../amount' -import { IlpStreamCredentials } from '../../connection/service' +import { IlpStreamCredentials } from '../../../payment-method/ilp/stream-credentials/service' import { WalletAddress, WalletAddressSubresource diff --git a/packages/backend/src/open_payments/payment/incoming/routes.ts b/packages/backend/src/open_payments/payment/incoming/routes.ts index f632e10413..3a2318672e 100644 --- a/packages/backend/src/open_payments/payment/incoming/routes.ts +++ b/packages/backend/src/open_payments/payment/incoming/routes.ts @@ -21,7 +21,7 @@ import { listSubresource } from '../../wallet_address/routes' import { IlpStreamCredentials, StreamCredentialsService -} from '../../connection/service' +} from '../../../payment-method/ilp/stream-credentials/service' import { AccessAction, IncomingPayment as OpenPaymentsIncomingPayment, diff --git a/packages/backend/src/open_payments/receiver/model.test.ts b/packages/backend/src/open_payments/receiver/model.test.ts index 5a2d4a319e..fd0d6dc2a9 100644 --- a/packages/backend/src/open_payments/receiver/model.test.ts +++ b/packages/backend/src/open_payments/receiver/model.test.ts @@ -9,7 +9,7 @@ import { truncateTables } from '../../tests/tableManager' import { IlpStreamCredentials, StreamCredentialsService -} from '../connection/service' +} from '../../payment-method/ilp/stream-credentials/service' import { Receiver } from './model' import { IncomingPaymentState } from '../payment/incoming/model' import assert from 'assert' diff --git a/packages/backend/src/open_payments/receiver/service.test.ts b/packages/backend/src/open_payments/receiver/service.test.ts index fd382e6d9d..717122abe1 100644 --- a/packages/backend/src/open_payments/receiver/service.test.ts +++ b/packages/backend/src/open_payments/receiver/service.test.ts @@ -25,7 +25,7 @@ import { MockWalletAddress } from '../../tests/walletAddress' import { truncateTables } from '../../tests/tableManager' -import { StreamCredentialsService } from '../connection/service' +import { StreamCredentialsService } from '../../payment-method/ilp/stream-credentials/service' import { GrantService } from '../grant/service' import { WalletAddressService } from '../wallet_address/service' import { Amount, parseAmount } from '../amount' diff --git a/packages/backend/src/open_payments/receiver/service.ts b/packages/backend/src/open_payments/receiver/service.ts index a1e6e879b7..e43de89cdc 100644 --- a/packages/backend/src/open_payments/receiver/service.ts +++ b/packages/backend/src/open_payments/receiver/service.ts @@ -5,7 +5,7 @@ import { AccessType, AccessAction } from '@interledger/open-payments' -import { StreamCredentialsService } from '../connection/service' +import { StreamCredentialsService } from '../../payment-method/ilp/stream-credentials/service' import { Grant } from '../grant/model' import { GrantService } from '../grant/service' import { WalletAddressService } from '../wallet_address/service' diff --git a/packages/backend/src/open_payments/connection/service.test.ts b/packages/backend/src/payment-method/ilp/stream-credentials/service.test.ts similarity index 80% rename from packages/backend/src/open_payments/connection/service.test.ts rename to packages/backend/src/payment-method/ilp/stream-credentials/service.test.ts index 2a732c35ba..41cba5b0cf 100644 --- a/packages/backend/src/open_payments/connection/service.test.ts +++ b/packages/backend/src/payment-method/ilp/stream-credentials/service.test.ts @@ -1,17 +1,17 @@ import { Knex } from 'knex' -import { createTestApp, TestContainer } from '../../tests/app' +import { createTestApp, TestContainer } from '../../../tests/app' import { StreamCredentialsService } from './service' import { IncomingPayment, IncomingPaymentState -} from '../payment/incoming/model' -import { Config } from '../../config/app' +} from '../../../open_payments/payment/incoming/model' +import { Config } from '../../../config/app' import { IocContract } from '@adonisjs/fold' -import { initIocContainer } from '../..' -import { AppServices } from '../../app' -import { createIncomingPayment } from '../../tests/incomingPayment' -import { createWalletAddress } from '../../tests/walletAddress' -import { truncateTables } from '../../tests/tableManager' +import { initIocContainer } from '../../..' +import { AppServices } from '../../../app' +import { createIncomingPayment } from '../../../tests/incomingPayment' +import { createWalletAddress } from '../../../tests/walletAddress' +import { truncateTables } from '../../../tests/tableManager' import assert from 'assert' describe('Stream Credentials Service', (): void => { diff --git a/packages/backend/src/open_payments/connection/service.ts b/packages/backend/src/payment-method/ilp/stream-credentials/service.ts similarity index 91% rename from packages/backend/src/open_payments/connection/service.ts rename to packages/backend/src/payment-method/ilp/stream-credentials/service.ts index 05dbec8270..2c025bdd46 100644 --- a/packages/backend/src/open_payments/connection/service.ts +++ b/packages/backend/src/payment-method/ilp/stream-credentials/service.ts @@ -1,9 +1,9 @@ import { StreamServer } from '@interledger/stream-receiver' -import { BaseService } from '../../shared/baseService' +import { BaseService } from '../../../shared/baseService' import { IncomingPayment, IncomingPaymentState -} from '../payment/incoming/model' +} from '../../../open_payments/payment/incoming/model' import { StreamCredentials as IlpStreamCredentials } from '@interledger/stream-receiver' export { IlpStreamCredentials } From 8396970b59a2483728d210d8623d1f2e977b34bb Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Thu, 19 Oct 2023 22:33:16 -0400 Subject: [PATCH 29/39] refactor(backend): streamCredentialService.get to not return undefined --- ..._drop_connections_from_incoming_payment.js | 38 +++++++++--------- .../open_payments/payment/incoming/model.ts | 7 ++++ .../open_payments/receiver/service.test.ts | 40 ++++++++++++------- .../src/open_payments/receiver/service.ts | 16 +++----- .../ilp/stream-credentials/service.test.ts | 21 +--------- .../ilp/stream-credentials/service.ts | 16 ++------ 6 files changed, 61 insertions(+), 77 deletions(-) diff --git a/packages/backend/migrations/20231019142543_drop_connections_from_incoming_payment.js b/packages/backend/migrations/20231019142543_drop_connections_from_incoming_payment.js index 87f9d05aa4..a9adddd51e 100644 --- a/packages/backend/migrations/20231019142543_drop_connections_from_incoming_payment.js +++ b/packages/backend/migrations/20231019142543_drop_connections_from_incoming_payment.js @@ -1,19 +1,19 @@ -/** - * @param { import("knex").Knex } knex - * @returns { Promise } - */ -exports.up = function (knex) { - return knex.schema.alterTable('incomingPayments', function (table) { - table.dropColumn('connectionId') - }) -} - -/** - * @param { import("knex").Knex } knex - * @returns { Promise } - */ -exports.down = function (knex) { - return knex.schema.alterTable('incomingPayments', function (table) { - table.uuid('connectionId').nullable() - }) -} +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function (knex) { + return knex.schema.alterTable('incomingPayments', function (table) { + table.dropColumn('connectionId') + }) +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) { + return knex.schema.alterTable('incomingPayments', function (table) { + table.uuid('connectionId').nullable() + }) +} diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index 98e4c56d77..e11566ebc4 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -161,6 +161,13 @@ export class IncomingPayment return this } + public isExpiredOrComplete(): boolean { + return ( + this.state === IncomingPaymentState.Expired || + this.state === IncomingPaymentState.Completed + ) + } + public toData(amountReceived: bigint): IncomingPaymentData { const data: IncomingPaymentData = { incomingPayment: { diff --git a/packages/backend/src/open_payments/receiver/service.test.ts b/packages/backend/src/open_payments/receiver/service.test.ts index 717122abe1..e7a1344e8b 100644 --- a/packages/backend/src/open_payments/receiver/service.test.ts +++ b/packages/backend/src/open_payments/receiver/service.test.ts @@ -25,7 +25,6 @@ import { MockWalletAddress } from '../../tests/walletAddress' import { truncateTables } from '../../tests/tableManager' -import { StreamCredentialsService } from '../../payment-method/ilp/stream-credentials/service' import { GrantService } from '../grant/service' import { WalletAddressService } from '../wallet_address/service' import { Amount, parseAmount } from '../amount' @@ -38,6 +37,7 @@ import { RemoteIncomingPaymentError } from '../payment/incoming_remote/errors' import assert from 'assert' import { Receiver } from './model' import { Grant } from '../grant/model' +import { IncomingPaymentState } from '../payment/incoming/model' describe('Receiver Service', (): void => { let deps: IocContract @@ -46,7 +46,6 @@ describe('Receiver Service', (): void => { let incomingPaymentService: IncomingPaymentService let openPaymentsClient: AuthenticatedClient let knex: Knex - let streamCredentialsService: StreamCredentialsService let walletAddressService: WalletAddressService let grantService: GrantService let remoteIncomingPaymentService: RemoteIncomingPaymentService @@ -57,7 +56,6 @@ describe('Receiver Service', (): void => { receiverService = await deps.use('receiverService') incomingPaymentService = await deps.use('incomingPaymentService') openPaymentsClient = await deps.use('openPaymentsClient') - streamCredentialsService = await deps.use('streamCredentialsService') walletAddressService = await deps.use('walletAddressService') grantService = await deps.use('grantService') remoteIncomingPaymentService = await deps.use( @@ -590,19 +588,31 @@ describe('Receiver Service', (): void => { ).resolves.toEqual(ReceiverError.InvalidAmount) }) - test('throws if error when getting stream credentials for local incoming payment', async (): Promise => { - jest - .spyOn(streamCredentialsService, 'get') - .mockReturnValueOnce(undefined) - - await expect( - receiverService.create({ - walletAddressUrl: walletAddress.url + test.each([IncomingPaymentState.Completed, IncomingPaymentState.Expired])( + 'throws if local incoming payment state is %s', + async (): Promise => { + const incomingPayment = await createIncomingPayment(deps, { + walletAddressId: walletAddress.id, + incomingAmount: { + value: BigInt(5), + assetCode: walletAddress.asset.code, + assetScale: walletAddress.asset.scale + } }) - ).rejects.toThrow( - 'Could not get stream credentials for local incoming payment' - ) - }) + incomingPayment.state = IncomingPaymentState.Completed + jest + .spyOn(incomingPaymentService, 'create') + .mockResolvedValueOnce(incomingPayment) + + await expect( + receiverService.create({ + walletAddressUrl: walletAddress.url + }) + ).rejects.toThrow( + 'Could not get stream credentials for local incoming payment' + ) + } + ) }) }) }) diff --git a/packages/backend/src/open_payments/receiver/service.ts b/packages/backend/src/open_payments/receiver/service.ts index e43de89cdc..47e2f049d0 100644 --- a/packages/backend/src/open_payments/receiver/service.ts +++ b/packages/backend/src/open_payments/receiver/service.ts @@ -123,11 +123,7 @@ async function createLocalIncomingPayment( return incomingPaymentOrError } - const streamCredentials = deps.streamCredentialsService.get( - incomingPaymentOrError - ) - - if (!streamCredentials) { + if (incomingPaymentOrError.isExpiredOrComplete()) { const errorMessage = 'Could not get stream credentials for local incoming payment' deps.logger.error({ incomingPaymentOrError }, errorMessage) @@ -135,6 +131,10 @@ async function createLocalIncomingPayment( throw new Error(errorMessage) } + const streamCredentials = deps.streamCredentialsService.get( + incomingPaymentOrError + ) + return incomingPaymentOrError.toOpenPaymentsType( walletAddress, streamCredentials @@ -221,16 +221,12 @@ async function getLocalIncomingPayment({ walletAddressId: walletAddress.id }) - if (!incomingPayment) { + if (!incomingPayment || incomingPayment.isExpiredOrComplete()) { return undefined } const streamCredentials = deps.streamCredentialsService.get(incomingPayment) - if (!streamCredentials) { - return undefined - } - return incomingPayment.toOpenPaymentsType(walletAddress, streamCredentials) } diff --git a/packages/backend/src/payment-method/ilp/stream-credentials/service.test.ts b/packages/backend/src/payment-method/ilp/stream-credentials/service.test.ts index 41cba5b0cf..58d63a8f1b 100644 --- a/packages/backend/src/payment-method/ilp/stream-credentials/service.test.ts +++ b/packages/backend/src/payment-method/ilp/stream-credentials/service.test.ts @@ -1,10 +1,7 @@ import { Knex } from 'knex' import { createTestApp, TestContainer } from '../../../tests/app' import { StreamCredentialsService } from './service' -import { - IncomingPayment, - IncomingPaymentState -} from '../../../open_payments/payment/incoming/model' +import { IncomingPayment } from '../../../open_payments/payment/incoming/model' import { Config } from '../../../config/app' import { IocContract } from '@adonisjs/fold' import { initIocContainer } from '../../..' @@ -52,21 +49,5 @@ describe('Stream Credentials Service', (): void => { sharedSecret: expect.any(Buffer) }) }) - - test.each` - state - ${IncomingPaymentState.Completed} - ${IncomingPaymentState.Expired} - `( - `returns undefined for $state incoming payment`, - async ({ state }): Promise => { - await incomingPayment.$query(knex).patch({ - state, - expiresAt: - state === IncomingPaymentState.Expired ? new Date() : undefined - }) - expect(streamCredentialsService.get(incomingPayment)).toBeUndefined() - } - ) }) }) diff --git a/packages/backend/src/payment-method/ilp/stream-credentials/service.ts b/packages/backend/src/payment-method/ilp/stream-credentials/service.ts index 2c025bdd46..e9c3505297 100644 --- a/packages/backend/src/payment-method/ilp/stream-credentials/service.ts +++ b/packages/backend/src/payment-method/ilp/stream-credentials/service.ts @@ -1,15 +1,12 @@ import { StreamServer } from '@interledger/stream-receiver' import { BaseService } from '../../../shared/baseService' -import { - IncomingPayment, - IncomingPaymentState -} from '../../../open_payments/payment/incoming/model' +import { IncomingPayment } from '../../../open_payments/payment/incoming/model' import { StreamCredentials as IlpStreamCredentials } from '@interledger/stream-receiver' export { IlpStreamCredentials } export interface StreamCredentialsService { - get(payment: IncomingPayment): IlpStreamCredentials | undefined + get(payment: IncomingPayment): IlpStreamCredentials } export interface ServiceDependencies extends BaseService { @@ -35,14 +32,7 @@ export async function createStreamCredentialsService( function getStreamCredentials( deps: ServiceDependencies, payment: IncomingPayment -): IlpStreamCredentials | undefined { - if ( - [IncomingPaymentState.Completed, IncomingPaymentState.Expired].includes( - payment.state - ) - ) { - return undefined - } +): IlpStreamCredentials { const credentials = deps.streamServer.generateCredentials({ paymentTag: payment.id, asset: { From 6f3e16e40f2fc75344288b073288aa18aa36f4c9 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 20 Oct 2023 09:30:08 -0400 Subject: [PATCH 30/39] feat(backend): add method to quote create paths --- .../src/graphql/resolvers/quote.test.ts | 6 ++- .../backend/src/graphql/resolvers/quote.ts | 3 +- .../payment/outgoing/service.test.ts | 54 ++++++++++++------- .../src/open_payments/quote/routes.test.ts | 16 ++++-- .../backend/src/open_payments/quote/routes.ts | 4 +- .../src/open_payments/quote/service.test.ts | 27 ++++++---- .../src/open_payments/quote/service.ts | 1 + .../backend/src/shared/pagination.test.ts | 3 +- packages/backend/src/tests/outgoingPayment.ts | 5 +- packages/backend/src/tests/quote.ts | 2 + 10 files changed, 82 insertions(+), 39 deletions(-) diff --git a/packages/backend/src/graphql/resolvers/quote.test.ts b/packages/backend/src/graphql/resolvers/quote.test.ts index 28458c97d1..7e1b08b301 100644 --- a/packages/backend/src/graphql/resolvers/quote.test.ts +++ b/packages/backend/src/graphql/resolvers/quote.test.ts @@ -58,6 +58,7 @@ describe('Quote Resolvers', (): void => { assetCode: asset.code, assetScale: asset.scale }, + method: 'ilp', validDestination: false }) } @@ -217,7 +218,8 @@ describe('Quote Resolvers', (): void => { }) .then((query): QuoteResponse => query.data?.createQuote) - expect(createSpy).toHaveBeenCalledWith(input) + console.log(createSpy.mock.calls[0]) + expect(createSpy).toHaveBeenCalledWith({ ...input, method: 'ilp' }) expect(query.code).toBe('200') expect(query.success).toBe(true) expect(query.quote?.id).toBe(quote?.id) @@ -271,7 +273,7 @@ describe('Quote Resolvers', (): void => { variables: { input } }) .then((query): QuoteResponse => query.data?.createQuote) - expect(createSpy).toHaveBeenCalledWith(input) + expect(createSpy).toHaveBeenCalledWith({ ...input, method: 'ilp' }) expect(query.code).toBe('500') expect(query.success).toBe(false) expect(query.message).toBe('Error trying to create quote') diff --git a/packages/backend/src/graphql/resolvers/quote.ts b/packages/backend/src/graphql/resolvers/quote.ts index 8c713615d1..07e2e2cf59 100644 --- a/packages/backend/src/graphql/resolvers/quote.ts +++ b/packages/backend/src/graphql/resolvers/quote.ts @@ -35,7 +35,8 @@ export const createQuote: MutationResolvers['createQuote'] = const quoteService = await ctx.container.use('quoteService') const options: CreateQuoteOptions = { walletAddressId: args.input.walletAddressId, - receiver: args.input.receiver + receiver: args.input.receiver, + method: 'ilp' } if (args.input.debitAmount) options.debitAmount = args.input.debitAmount if (args.input.receiveAmount) diff --git a/packages/backend/src/open_payments/payment/outgoing/service.test.ts b/packages/backend/src/open_payments/payment/outgoing/service.test.ts index 3777ffc68b..c6092a7509 100644 --- a/packages/backend/src/open_payments/payment/outgoing/service.test.ts +++ b/packages/backend/src/open_payments/payment/outgoing/service.test.ts @@ -337,7 +337,8 @@ describe('OutgoingPaymentService', (): void => { const quote = await createQuote(deps, { walletAddressId, receiver, - debitAmount + debitAmount, + method: 'ilp' }) const options = { walletAddressId, @@ -400,7 +401,8 @@ describe('OutgoingPaymentService', (): void => { walletAddressId, receiver, debitAmount, - validDestination: false + validDestination: false, + method: 'ilp' }) await expect( outgoingPaymentService.create({ @@ -438,7 +440,8 @@ describe('OutgoingPaymentService', (): void => { walletAddressId, receiver, debitAmount, - validDestination: false + validDestination: false, + method: 'ilp' }) await expect( outgoingPaymentService.create({ @@ -453,7 +456,8 @@ describe('OutgoingPaymentService', (): void => { walletAddressId, receiver, debitAmount, - validDestination: false + validDestination: false, + method: 'ilp' }) await quote.$query(knex).patch({ expiresAt: new Date() @@ -475,7 +479,8 @@ describe('OutgoingPaymentService', (): void => { const quote = await createQuote(deps, { walletAddressId, receiver, - debitAmount + debitAmount, + method: 'ilp' }) await incomingPayment.$query(knex).patch({ state, @@ -496,7 +501,8 @@ describe('OutgoingPaymentService', (): void => { walletAddressId, receiver, debitAmount, - validDestination: false + validDestination: false, + method: 'ilp' }) const walletAddress = await createWalletAddress(deps) const walletAddressUpdated = await WalletAddress.query( @@ -523,7 +529,8 @@ describe('OutgoingPaymentService', (): void => { return await createQuote(deps, { walletAddressId, receiver, - debitAmount + debitAmount, + method: 'ilp' }) }) ) @@ -571,7 +578,8 @@ describe('OutgoingPaymentService', (): void => { quote = await createQuote(deps, { walletAddressId, receiver, - debitAmount + debitAmount, + method: 'ilp' }) options = { walletAddressId, @@ -831,7 +839,8 @@ describe('OutgoingPaymentService', (): void => { const paymentId = await setup({ receiver, debitAmount, - receiveAmount + receiveAmount, + method: 'ilp' }) const payment = await processNext( @@ -860,7 +869,8 @@ describe('OutgoingPaymentService', (): void => { assert.ok(incomingPayment.walletAddress) const paymentId = await setup({ receiver: incomingPayment.getUrl(incomingPayment.walletAddress), - receiveAmount + receiveAmount, + method: 'ilp' }) const payment = await processNext( @@ -881,7 +891,8 @@ describe('OutgoingPaymentService', (): void => { const paymentId = await setup( { receiver, - receiveAmount + receiveAmount, + method: 'ilp' }, receiveAmount ) @@ -924,7 +935,8 @@ describe('OutgoingPaymentService', (): void => { const paymentId = await setup({ receiver, - debitAmount + debitAmount, + method: 'ilp' }) for (let i = 0; i < 4; i++) { @@ -966,7 +978,8 @@ describe('OutgoingPaymentService', (): void => { ) const paymentId = await setup({ receiver, - debitAmount + debitAmount, + method: 'ilp' }) const payment = await processNext( @@ -992,7 +1005,8 @@ describe('OutgoingPaymentService', (): void => { ) const paymentId = await setup({ receiver, - receiveAmount + receiveAmount, + method: 'ilp' }) const payment = await processNext(paymentId, OutgoingPaymentState.Sending) @@ -1022,7 +1036,8 @@ describe('OutgoingPaymentService', (): void => { const paymentId = await setup( { receiver, - receiveAmount + receiveAmount, + method: 'ilp' }, receiveAmount ) @@ -1048,7 +1063,8 @@ describe('OutgoingPaymentService', (): void => { const paymentId = await setup( { receiver, - receiveAmount + receiveAmount, + method: 'ilp' }, receiveAmount ) @@ -1071,7 +1087,8 @@ describe('OutgoingPaymentService', (): void => { it('FAILED (source asset changed)', async (): Promise => { const paymentId = await setup({ receiver, - debitAmount + debitAmount, + method: 'ilp' }) const { id: assetId } = await createAsset(deps, { code: asset.code, @@ -1091,7 +1108,8 @@ describe('OutgoingPaymentService', (): void => { it('FAILED (destination asset changed)', async (): Promise => { const paymentId = await setup({ receiver, - debitAmount + debitAmount, + method: 'ilp' }) // Pretend that the destination asset was initially different. await OutgoingPayment.relatedQuery('quote') diff --git a/packages/backend/src/open_payments/quote/routes.test.ts b/packages/backend/src/open_payments/quote/routes.test.ts index c152ba2685..3993ba1b1c 100644 --- a/packages/backend/src/open_payments/quote/routes.test.ts +++ b/packages/backend/src/open_payments/quote/routes.test.ts @@ -53,6 +53,7 @@ describe('Quote Routes', (): void => { assetCode: asset.code, assetScale: asset.scale }, + method: 'ilp', client, validDestination: false }) @@ -137,7 +138,8 @@ describe('Quote Routes', (): void => { ...debitAmount, value: debitAmount.value.toString(), assetScale: debitAmount.assetScale + 1 - } + }, + method: 'ilp' } const ctx = setup({}) await expect(quoteRoutes.create(ctx)).rejects.toMatchObject({ @@ -170,7 +172,8 @@ describe('Quote Routes', (): void => { '$description', async ({ debitAmount, receiveAmount }): Promise => { options = { - receiver + receiver, + method: 'ilp' } if (debitAmount) options.debitAmount = { @@ -208,7 +211,8 @@ describe('Quote Routes', (): void => { ...options.receiveAmount, value: BigInt(options.receiveAmount.value) }, - client + client, + method: 'ilp' }) expect(ctx.response).toSatisfyApiSpec() const quoteId = ( @@ -238,7 +242,8 @@ describe('Quote Routes', (): void => { test('receiver.incomingAmount', async (): Promise => { options = { - receiver + receiver, + method: 'ilp' } const ctx = setup({ client }) let quote: Quote | undefined @@ -256,7 +261,8 @@ describe('Quote Routes', (): void => { expect(quoteSpy).toHaveBeenCalledWith({ walletAddressId: walletAddress.id, receiver, - client + client, + method: 'ilp' }) expect(ctx.response).toSatisfyApiSpec() const quoteId = ( diff --git a/packages/backend/src/open_payments/quote/routes.ts b/packages/backend/src/open_payments/quote/routes.ts index 816b415a72..b1bba83322 100644 --- a/packages/backend/src/open_payments/quote/routes.ts +++ b/packages/backend/src/open_payments/quote/routes.ts @@ -46,6 +46,7 @@ async function getQuote( interface CreateBodyBase { receiver: string + method: 'ilp' } interface CreateBodyWithDebitAmount extends CreateBodyBase { @@ -68,7 +69,8 @@ async function createQuote( const options: CreateQuoteOptions = { walletAddressId: ctx.walletAddress.id, receiver: body.receiver, - client: ctx.client + client: ctx.client, + method: body.method } if (body.debitAmount) options.debitAmount = parseAmount(body.debitAmount) if (body.receiveAmount) diff --git a/packages/backend/src/open_payments/quote/service.test.ts b/packages/backend/src/open_payments/quote/service.test.ts index d85d47a155..8ed609696a 100644 --- a/packages/backend/src/open_payments/quote/service.test.ts +++ b/packages/backend/src/open_payments/quote/service.test.ts @@ -113,7 +113,8 @@ describe('QuoteService', (): void => { }, client, validDestination: false, - withFee: true + withFee: true, + method: 'ilp' }), get: (options) => quoteService.get(options), list: (options) => quoteService.getWalletAddressPage(options) @@ -148,7 +149,8 @@ describe('QuoteService', (): void => { }) options = { walletAddressId: sendingWalletAddress.id, - receiver: incomingPayment.getUrl(receivingWalletAddress) + receiver: incomingPayment.getUrl(receivingWalletAddress), + method: 'ilp' } if (debitAmount) options.debitAmount = debitAmount if (receiveAmount) options.receiveAmount = receiveAmount @@ -342,7 +344,8 @@ describe('QuoteService', (): void => { const options: CreateQuoteOptions = { walletAddressId: sendingWalletAddress.id, receiver: incomingPayment.getUrl(receivingWalletAddress), - receiveAmount + receiveAmount, + method: 'ilp' } const mockedQuote = mockQuote({ @@ -383,7 +386,8 @@ describe('QuoteService', (): void => { quoteService.create({ walletAddressId: uuid(), receiver: `${receivingWalletAddress.url}/incoming-payments/${uuid()}`, - debitAmount + debitAmount, + method: 'ilp' }) ).resolves.toEqual(QuoteError.UnknownWalletAddress) }) @@ -398,7 +402,8 @@ describe('QuoteService', (): void => { quoteService.create({ walletAddressId: walletAddress.id, receiver: `${receivingWalletAddress.url}/incoming-payments/${uuid()}`, - debitAmount + debitAmount, + method: 'ilp' }) ).resolves.toEqual(QuoteError.InactiveWalletAddress) }) @@ -408,7 +413,8 @@ describe('QuoteService', (): void => { quoteService.create({ walletAddressId: sendingWalletAddress.id, receiver: `${receivingWalletAddress.url}/incoming-payments/${uuid()}`, - debitAmount + debitAmount, + method: 'ilp' }) ).resolves.toEqual(QuoteError.InvalidReceiver) }) @@ -430,7 +436,8 @@ describe('QuoteService', (): void => { }) const options: CreateQuoteOptions = { walletAddressId: sendingWalletAddress.id, - receiver: incomingPayment.getUrl(receivingWalletAddress) + receiver: incomingPayment.getUrl(receivingWalletAddress), + method: 'ilp' } if (debitAmount) options.debitAmount = debitAmount if (receiveAmount) options.receiveAmount = receiveAmount @@ -499,7 +506,8 @@ describe('QuoteService', (): void => { const quote = await quoteService.create({ walletAddressId: sendingWalletAddress.id, - receiver: receiver.incomingPayment!.id + receiver: receiver.incomingPayment!.id, + method: 'ilp' }) assert.ok(!isQuoteError(quote)) @@ -576,7 +584,8 @@ describe('QuoteService', (): void => { value: debitAmountValue, assetCode: sendAsset.code, assetScale: sendAsset.scale - } + }, + method: 'ilp' }) assert.ok(!isQuoteError(quote)) diff --git a/packages/backend/src/open_payments/quote/service.ts b/packages/backend/src/open_payments/quote/service.ts index 4b2e254477..5ece11b0c7 100644 --- a/packages/backend/src/open_payments/quote/service.ts +++ b/packages/backend/src/open_payments/quote/service.ts @@ -56,6 +56,7 @@ async function getQuote( interface QuoteOptionsBase { walletAddressId: string receiver: string + method: 'ilp' client?: string } diff --git a/packages/backend/src/shared/pagination.test.ts b/packages/backend/src/shared/pagination.test.ts index e99d3f03c1..c12f9c74a3 100644 --- a/packages/backend/src/shared/pagination.test.ts +++ b/packages/backend/src/shared/pagination.test.ts @@ -212,7 +212,8 @@ describe('Pagination', (): void => { walletAddressId: defaultWalletAddress.id, receiver: secondaryWalletAddress.url, debitAmount, - validDestination: false + validDestination: false, + method: 'ilp' }) quoteIds.push(quote.id) } diff --git a/packages/backend/src/tests/outgoingPayment.ts b/packages/backend/src/tests/outgoingPayment.ts index b517ba1349..4da04fe8cf 100644 --- a/packages/backend/src/tests/outgoingPayment.ts +++ b/packages/backend/src/tests/outgoingPayment.ts @@ -14,14 +14,15 @@ export async function createOutgoingPayment( deps: IocContract, options: Omit< CreateOutgoingPaymentOptions & CreateTestQuoteOptions, - 'quoteId' + 'quoteId' | 'method' > ): Promise { const quoteOptions: CreateTestQuoteOptions = { walletAddressId: options.walletAddressId, client: options.client, receiver: options.receiver, - validDestination: options.validDestination + validDestination: options.validDestination, + method: 'ilp' } if (options.debitAmount) quoteOptions.debitAmount = options.debitAmount if (options.receiveAmount) quoteOptions.receiveAmount = options.receiveAmount diff --git a/packages/backend/src/tests/quote.ts b/packages/backend/src/tests/quote.ts index 10df3b8771..340d56b26b 100644 --- a/packages/backend/src/tests/quote.ts +++ b/packages/backend/src/tests/quote.ts @@ -65,6 +65,8 @@ export async function createQuote( debitAmount, receiveAmount, client, + /* eslint-disable @typescript-eslint/no-unused-vars */ + method, validDestination = true, withFee = false }: CreateTestQuoteOptions From 9fd8b16111ab824e03f316751c2c60866c5c90a8 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 20 Oct 2023 09:50:03 -0400 Subject: [PATCH 31/39] fix(backend): rm debug log --- packages/backend/src/graphql/resolvers/quote.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/backend/src/graphql/resolvers/quote.test.ts b/packages/backend/src/graphql/resolvers/quote.test.ts index 7e1b08b301..1f39f81952 100644 --- a/packages/backend/src/graphql/resolvers/quote.test.ts +++ b/packages/backend/src/graphql/resolvers/quote.test.ts @@ -218,7 +218,6 @@ describe('Quote Resolvers', (): void => { }) .then((query): QuoteResponse => query.data?.createQuote) - console.log(createSpy.mock.calls[0]) expect(createSpy).toHaveBeenCalledWith({ ...input, method: 'ilp' }) expect(query.code).toBe('200') expect(query.success).toBe(true) From e68b48ec5a4ffbc8c367c8cf3b5040ae8fafa2e7 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 20 Oct 2023 11:27:20 -0400 Subject: [PATCH 32/39] fix(postman): rm extra port from url --- .postman/api_84fc90ca-3153-4865-8b49-b91218e5d574 | 1 + postman/collections/Interledger.json | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.postman/api_84fc90ca-3153-4865-8b49-b91218e5d574 b/.postman/api_84fc90ca-3153-4865-8b49-b91218e5d574 index 74aa12fe24..66ff287f22 100644 --- a/.postman/api_84fc90ca-3153-4865-8b49-b91218e5d574 +++ b/.postman/api_84fc90ca-3153-4865-8b49-b91218e5d574 @@ -17,3 +17,4 @@ files[] = {"id":"23674746-92761441-6d0c-4fc6-aa04-73eccf6afd4c","path":"Interled rootDirectory = postman/schemas [config.relations.apiDefinition.metaData] +type = openapi:3 diff --git a/postman/collections/Interledger.json b/postman/collections/Interledger.json index 4b4351f1e6..498bd4d9b7 100644 --- a/postman/collections/Interledger.json +++ b/postman/collections/Interledger.json @@ -1624,7 +1624,6 @@ "host": [ "{{pfryWalletAddress}}" ], - "port": "4000", "path": [ "incoming-payments", "{{incomingPaymentId}}" From 718d962cd135cd150495398efde4e0c72271c4a9 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 20 Oct 2023 13:54:13 -0400 Subject: [PATCH 33/39] fix(backend): return empty methods for complete or expired incomingPayment --- .../payment/incoming/model.test.ts | 50 +++++++++++++++++-- .../open_payments/payment/incoming/model.ts | 48 +++++++----------- .../payment/incoming/routes.test.ts | 32 +++++++++++- .../open_payments/payment/incoming/routes.ts | 32 +++--------- .../src/open_payments/receiver/model.test.ts | 32 +++++++----- .../src/open_payments/receiver/model.ts | 8 +-- .../src/open_payments/receiver/service.ts | 7 ++- packages/backend/src/tests/outgoingPayment.ts | 5 +- packages/backend/src/tests/receiver.ts | 2 +- 9 files changed, 135 insertions(+), 81 deletions(-) diff --git a/packages/backend/src/open_payments/payment/incoming/model.test.ts b/packages/backend/src/open_payments/payment/incoming/model.test.ts index 56f237fd72..036803207e 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.test.ts @@ -9,7 +9,7 @@ import { truncateTables } from '../../../tests/tableManager' import { IlpStreamCredentials } from '../../../payment-method/ilp/stream-credentials/service' import { serializeAmount } from '../../amount' import { IlpAddress } from 'ilp-packet' -import { IncomingPayment } from './model' +import { IncomingPayment, IncomingPaymentState } from './model' describe('Incoming Payment Model', (): void => { let deps: IocContract @@ -30,7 +30,7 @@ describe('Incoming Payment Model', (): void => { }) describe('toOpenPaymentsType', () => { - test('returns incoming payment without stream credentials provided', async () => { + test('returns incoming payment', async () => { const walletAddress = await createWalletAddress(deps) const incomingPayment = await createIncomingPayment(deps, { walletAddressId: walletAddress.id, @@ -51,8 +51,10 @@ describe('Incoming Payment Model', (): void => { createdAt: incomingPayment.createdAt.toISOString() }) }) + }) - test('returns incoming payment with stream credentials as object', async () => { + describe('toOpenPaymentsTypeWithMethods', () => { + test('returns incoming payment with payment methods', async () => { const walletAddress = await createWalletAddress(deps) const incomingPayment = await createIncomingPayment(deps, { walletAddressId: walletAddress.id, @@ -65,7 +67,10 @@ describe('Incoming Payment Model', (): void => { } expect( - incomingPayment.toOpenPaymentsType(walletAddress, streamCredentials) + incomingPayment.toOpenPaymentsTypeWithMethods( + walletAddress, + streamCredentials + ) ).toEqual({ id: `${walletAddress.url}${IncomingPayment.urlPath}/${incomingPayment.id}`, walletAddress: walletAddress.url, @@ -87,5 +92,42 @@ describe('Incoming Payment Model', (): void => { ] }) }) + + test.each([IncomingPaymentState.Completed, IncomingPaymentState.Expired])( + 'returns incoming payment with empty methods if payment state is %s', + async (paymentState): Promise => { + const walletAddress = await createWalletAddress(deps) + const incomingPayment = await createIncomingPayment(deps, { + walletAddressId: walletAddress.id, + metadata: { description: 'my payment' } + }) + incomingPayment.state = paymentState + + const streamCredentials: IlpStreamCredentials = { + ilpAddress: 'test.ilp' as IlpAddress, + sharedSecret: Buffer.from('') + } + + expect( + incomingPayment.toOpenPaymentsTypeWithMethods( + walletAddress, + streamCredentials + ) + ).toEqual({ + id: `${walletAddress.url}${IncomingPayment.urlPath}/${incomingPayment.id}`, + walletAddress: walletAddress.url, + completed: incomingPayment.completed, + receivedAmount: serializeAmount(incomingPayment.receivedAmount), + incomingAmount: incomingPayment.incomingAmount + ? serializeAmount(incomingPayment.incomingAmount) + : undefined, + expiresAt: incomingPayment.expiresAt.toISOString(), + metadata: incomingPayment.metadata ?? undefined, + updatedAt: incomingPayment.updatedAt.toISOString(), + createdAt: incomingPayment.createdAt.toISOString(), + methods: [] + }) + } + ) }) }) diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index e11566ebc4..6fb0fcede0 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -200,23 +200,8 @@ export class IncomingPayment public toOpenPaymentsType( walletAddress: WalletAddress - ): OpenPaymentsIncomingPayment - public toOpenPaymentsType( - walletAddress: WalletAddress, - ilpStreamCredentials: IlpStreamCredentials - ): OpenPaymentsIncomingPaymentWithPaymentMethod - public toOpenPaymentsType( - walletAddress: WalletAddress, - ilpStreamCredentials?: IlpStreamCredentials - ): OpenPaymentsIncomingPayment | OpenPaymentsIncomingPaymentWithPaymentMethod - - public toOpenPaymentsType( - walletAddress: WalletAddress, - ilpStreamCredentials?: IlpStreamCredentials - ): - | OpenPaymentsIncomingPayment - | OpenPaymentsIncomingPaymentWithPaymentMethod { - const baseIncomingPayment: OpenPaymentsIncomingPayment = { + ): OpenPaymentsIncomingPayment { + return { id: this.getUrl(walletAddress), walletAddress: walletAddress.url, incomingAmount: this.incomingAmount @@ -229,21 +214,24 @@ export class IncomingPayment updatedAt: this.updatedAt.toISOString(), expiresAt: this.expiresAt.toISOString() } + } - if (ilpStreamCredentials) { - return { - ...baseIncomingPayment, - methods: [ - { - type: 'ilp', - ilpAddress: ilpStreamCredentials.ilpAddress, - sharedSecret: base64url(ilpStreamCredentials.sharedSecret) - } - ] - } + public toOpenPaymentsTypeWithMethods( + walletAddress: WalletAddress, + ilpStreamCredentials: IlpStreamCredentials + ): OpenPaymentsIncomingPaymentWithPaymentMethod { + return { + ...this.toOpenPaymentsType(walletAddress), + methods: this.isExpiredOrComplete() + ? [] + : [ + { + type: 'ilp', + ilpAddress: ilpStreamCredentials.ilpAddress, + sharedSecret: base64url(ilpStreamCredentials.sharedSecret) + } + ] } - - return baseIncomingPayment } public toPublicOpenPaymentsType(): Pick< diff --git a/packages/backend/src/open_payments/payment/incoming/routes.test.ts b/packages/backend/src/open_payments/payment/incoming/routes.test.ts index 0fc8e07513..61f9d6fa25 100644 --- a/packages/backend/src/open_payments/payment/incoming/routes.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/routes.test.ts @@ -15,7 +15,7 @@ import { ListContext } from '../../../app' import { truncateTables } from '../../../tests/tableManager' -import { IncomingPayment } from './model' +import { IncomingPayment, IncomingPaymentState } from './model' import { IncomingPaymentRoutes, CreateBody, @@ -143,6 +143,36 @@ describe('Incoming Payment Routes', (): void => { }) }) + describe('get', (): void => { + test.each([IncomingPaymentState.Completed, IncomingPaymentState.Expired])( + 'returns incoming payment with empty methods if payment state is %s', + async (paymentState): Promise => { + const walletAddress = await createWalletAddress(deps) + const incomingPayment = await createIncomingPayment(deps, { + walletAddressId: walletAddress.id + }) + await incomingPayment.$query().update({ state: paymentState }) + + const ctx = setup({ + reqOpts: { + headers: { Accept: 'application/json' }, + method: 'GET', + url: `/incoming-payments/${incomingPayment.id}` + }, + params: { + id: incomingPayment.id + }, + walletAddress + }) + + await expect(incomingPaymentRoutes.get(ctx)).resolves.toBeUndefined() + + expect(ctx.response).toSatisfyApiSpec() + expect(ctx.body).toMatchObject({ methods: [] }) + } + ) + }) + describe('create', (): void => { let amount: AmountJSON diff --git a/packages/backend/src/open_payments/payment/incoming/routes.ts b/packages/backend/src/open_payments/payment/incoming/routes.ts index 0028b473c3..82bd19ebb3 100644 --- a/packages/backend/src/open_payments/payment/incoming/routes.ts +++ b/packages/backend/src/open_payments/payment/incoming/routes.ts @@ -18,16 +18,8 @@ import { } from './errors' import { AmountJSON, parseAmount } from '../../amount' import { listSubresource } from '../../wallet_address/routes' -import { - IlpStreamCredentials, - StreamCredentialsService -} from '../../../payment-method/ilp/stream-credentials/service' -import { - AccessAction, - IncomingPayment as OpenPaymentsIncomingPayment, - IncomingPaymentWithPaymentMethods -} from '@interledger/open-payments' -import { WalletAddress } from '../../wallet_address/model' +import { StreamCredentialsService } from '../../../payment-method/ilp/stream-credentials/service' +import { AccessAction } from '@interledger/open-payments' interface ServiceDependencies { config: IAppConfig @@ -110,9 +102,8 @@ async function getIncomingPaymentPrivate( const streamCredentials = deps.streamCredentialsService.get(incomingPayment) - ctx.body = incomingPaymentToBody( + ctx.body = incomingPayment.toOpenPaymentsTypeWithMethods( incomingPayment.walletAddress, - incomingPayment, streamCredentials ) } @@ -158,9 +149,8 @@ async function createIncomingPayment( const streamCredentials = deps.streamCredentialsService.get( incomingPaymentOrError ) - ctx.body = incomingPaymentToBody( + ctx.body = incomingPaymentOrError.toOpenPaymentsTypeWithMethods( incomingPaymentOrError.walletAddress, - incomingPaymentOrError, streamCredentials ) } @@ -189,9 +179,8 @@ async function completeIncomingPayment( ctx.throw(404) } - ctx.body = incomingPaymentToBody( - incomingPaymentOrError.walletAddress, - incomingPaymentOrError + ctx.body = incomingPaymentOrError.toOpenPaymentsType( + incomingPaymentOrError.walletAddress ) } @@ -203,7 +192,7 @@ async function listIncomingPayments( await listSubresource({ ctx, getWalletAddressPage: deps.incomingPaymentService.getWalletAddressPage, - toBody: (payment) => incomingPaymentToBody(ctx.walletAddress, payment) + toBody: (payment) => payment.toOpenPaymentsType(ctx.walletAddress) }) } catch (err) { if (err instanceof Koa.HttpError) { @@ -212,10 +201,3 @@ async function listIncomingPayments( ctx.throw(500, 'Error trying to list incoming payments') } } -function incomingPaymentToBody( - walletAddress: WalletAddress, - incomingPayment: IncomingPayment, - ilpStreamCredentials?: IlpStreamCredentials -): OpenPaymentsIncomingPayment | IncomingPaymentWithPaymentMethods { - return incomingPayment.toOpenPaymentsType(walletAddress, ilpStreamCredentials) -} diff --git a/packages/backend/src/open_payments/receiver/model.test.ts b/packages/backend/src/open_payments/receiver/model.test.ts index fd0d6dc2a9..c062e1370c 100644 --- a/packages/backend/src/open_payments/receiver/model.test.ts +++ b/packages/backend/src/open_payments/receiver/model.test.ts @@ -47,7 +47,10 @@ describe('Receiver Model', (): void => { assert(streamCredentials) const receiver = new Receiver( - incomingPayment.toOpenPaymentsType(walletAddress, streamCredentials) + incomingPayment.toOpenPaymentsTypeWithMethods( + walletAddress, + streamCredentials + ) ) expect(receiver).toEqual({ @@ -87,10 +90,11 @@ describe('Receiver Model', (): void => { sharedSecret: Buffer.from('') } - const openPaymentsIncomingPayment = incomingPayment.toOpenPaymentsType( - walletAddress, - streamCredentials - ) + const openPaymentsIncomingPayment = + incomingPayment.toOpenPaymentsTypeWithMethods( + walletAddress, + streamCredentials + ) expect(() => new Receiver(openPaymentsIncomingPayment)).toThrow( 'Cannot create receiver from completed incoming payment' @@ -106,10 +110,11 @@ describe('Receiver Model', (): void => { incomingPayment.expiresAt = new Date(Date.now() - 1) const streamCredentials = streamCredentialsService.get(incomingPayment) assert(streamCredentials) - const openPaymentsIncomingPayment = incomingPayment.toOpenPaymentsType( - walletAddress, - streamCredentials - ) + const openPaymentsIncomingPayment = + incomingPayment.toOpenPaymentsTypeWithMethods( + walletAddress, + streamCredentials + ) expect(() => new Receiver(openPaymentsIncomingPayment)).toThrow( 'Cannot create receiver from expired incoming payment' @@ -126,10 +131,11 @@ describe('Receiver Model', (): void => { assert(streamCredentials) ;(streamCredentials.ilpAddress as string) = 'not base 64 encoded' - const openPaymentsIncomingPayment = incomingPayment.toOpenPaymentsType( - walletAddress, - streamCredentials - ) + const openPaymentsIncomingPayment = + incomingPayment.toOpenPaymentsTypeWithMethods( + walletAddress, + streamCredentials + ) expect(() => new Receiver(openPaymentsIncomingPayment)).toThrow( 'Invalid ILP address on ilp payment method' diff --git a/packages/backend/src/open_payments/receiver/model.ts b/packages/backend/src/open_payments/receiver/model.ts index 9de55d5ceb..bc694ffc7e 100644 --- a/packages/backend/src/open_payments/receiver/model.ts +++ b/packages/backend/src/open_payments/receiver/model.ts @@ -31,14 +31,14 @@ export class Receiver { public readonly incomingPayment: ReceiverIncomingPayment constructor(incomingPayment: OpenPaymentsIncomingPaymentWithPaymentMethod) { - if (!incomingPayment.methods.length) { - throw new Error('Missing payment method(s) on incoming payment') - } - if (incomingPayment.completed) { throw new Error('Cannot create receiver from completed incoming payment') } + if (!incomingPayment.methods.length) { + throw new Error('Missing payment method(s) on incoming payment') + } + const expiresAt = incomingPayment.expiresAt ? new Date(incomingPayment.expiresAt) : undefined diff --git a/packages/backend/src/open_payments/receiver/service.ts b/packages/backend/src/open_payments/receiver/service.ts index 47e2f049d0..e461fd6ae1 100644 --- a/packages/backend/src/open_payments/receiver/service.ts +++ b/packages/backend/src/open_payments/receiver/service.ts @@ -135,7 +135,7 @@ async function createLocalIncomingPayment( incomingPaymentOrError ) - return incomingPaymentOrError.toOpenPaymentsType( + return incomingPaymentOrError.toOpenPaymentsTypeWithMethods( walletAddress, streamCredentials ) @@ -227,7 +227,10 @@ async function getLocalIncomingPayment({ const streamCredentials = deps.streamCredentialsService.get(incomingPayment) - return incomingPayment.toOpenPaymentsType(walletAddress, streamCredentials) + return incomingPayment.toOpenPaymentsTypeWithMethods( + walletAddress, + streamCredentials + ) } async function getIncomingPaymentGrant( diff --git a/packages/backend/src/tests/outgoingPayment.ts b/packages/backend/src/tests/outgoingPayment.ts index 4da04fe8cf..3a84d23e67 100644 --- a/packages/backend/src/tests/outgoingPayment.ts +++ b/packages/backend/src/tests/outgoingPayment.ts @@ -45,7 +45,10 @@ export async function createOutgoingPayment( .spyOn(receiverService, 'get') .mockResolvedValueOnce( new Receiver( - incomingPayment.toOpenPaymentsType(walletAddress, streamCredentials) + incomingPayment.toOpenPaymentsTypeWithMethods( + walletAddress, + streamCredentials + ) ) ) } diff --git a/packages/backend/src/tests/receiver.ts b/packages/backend/src/tests/receiver.ts index ed04051974..3cd6b7a8c8 100644 --- a/packages/backend/src/tests/receiver.ts +++ b/packages/backend/src/tests/receiver.ts @@ -19,7 +19,7 @@ export async function createReceiver( const streamCredentialsService = await deps.use('streamCredentialsService') return new Receiver( - incomingPayment.toOpenPaymentsType( + incomingPayment.toOpenPaymentsTypeWithMethods( walletAddress, streamCredentialsService.get(incomingPayment)! ) From 5a4399ad9b45081c216796069e2a9552a284b678 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:16:06 -0400 Subject: [PATCH 34/39] fix(backend): toOpenPaymentsTypeWithMethods --- .../payment/incoming/model.test.ts | 24 +++++++++++++++++++ .../open_payments/payment/incoming/model.ts | 21 ++++++++-------- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/open_payments/payment/incoming/model.test.ts b/packages/backend/src/open_payments/payment/incoming/model.test.ts index 036803207e..f436a4a097 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.test.ts @@ -93,6 +93,30 @@ describe('Incoming Payment Model', (): void => { }) }) + test('returns incoming payment with empty methods when stream credentials are undefined', async () => { + const walletAddress = await createWalletAddress(deps) + const incomingPayment = await createIncomingPayment(deps, { + walletAddressId: walletAddress.id, + metadata: { description: 'my payment' } + }) + expect( + incomingPayment.toOpenPaymentsTypeWithMethods(walletAddress) + ).toEqual({ + id: `${walletAddress.url}${IncomingPayment.urlPath}/${incomingPayment.id}`, + walletAddress: walletAddress.url, + completed: incomingPayment.completed, + receivedAmount: serializeAmount(incomingPayment.receivedAmount), + incomingAmount: incomingPayment.incomingAmount + ? serializeAmount(incomingPayment.incomingAmount) + : undefined, + expiresAt: incomingPayment.expiresAt.toISOString(), + metadata: incomingPayment.metadata ?? undefined, + updatedAt: incomingPayment.updatedAt.toISOString(), + createdAt: incomingPayment.createdAt.toISOString(), + methods: [] + }) + }) + test.each([IncomingPaymentState.Completed, IncomingPaymentState.Expired])( 'returns incoming payment with empty methods if payment state is %s', async (paymentState): Promise => { diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index 6fb0fcede0..537442cd44 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -218,19 +218,20 @@ export class IncomingPayment public toOpenPaymentsTypeWithMethods( walletAddress: WalletAddress, - ilpStreamCredentials: IlpStreamCredentials + ilpStreamCredentials?: IlpStreamCredentials ): OpenPaymentsIncomingPaymentWithPaymentMethod { return { ...this.toOpenPaymentsType(walletAddress), - methods: this.isExpiredOrComplete() - ? [] - : [ - { - type: 'ilp', - ilpAddress: ilpStreamCredentials.ilpAddress, - sharedSecret: base64url(ilpStreamCredentials.sharedSecret) - } - ] + methods: + this.isExpiredOrComplete() || !ilpStreamCredentials + ? [] + : [ + { + type: 'ilp', + ilpAddress: ilpStreamCredentials.ilpAddress, + sharedSecret: base64url(ilpStreamCredentials.sharedSecret) + } + ] } } From 80b6f1f4e6dcb9539aebf3578a731e088d6fab65 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:16:39 -0400 Subject: [PATCH 35/39] refactor(backend): dont return stream credentials for complete or expired payments --- .../src/open_payments/receiver/service.test.ts | 4 ++-- .../src/open_payments/receiver/service.ts | 16 ++++++++++------ .../ilp/stream-credentials/service.ts | 7 +++++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/open_payments/receiver/service.test.ts b/packages/backend/src/open_payments/receiver/service.test.ts index e7a1344e8b..12c3b6e090 100644 --- a/packages/backend/src/open_payments/receiver/service.test.ts +++ b/packages/backend/src/open_payments/receiver/service.test.ts @@ -590,7 +590,7 @@ describe('Receiver Service', (): void => { test.each([IncomingPaymentState.Completed, IncomingPaymentState.Expired])( 'throws if local incoming payment state is %s', - async (): Promise => { + async (paymentState): Promise => { const incomingPayment = await createIncomingPayment(deps, { walletAddressId: walletAddress.id, incomingAmount: { @@ -599,7 +599,7 @@ describe('Receiver Service', (): void => { assetScale: walletAddress.asset.scale } }) - incomingPayment.state = IncomingPaymentState.Completed + incomingPayment.state = paymentState jest .spyOn(incomingPaymentService, 'create') .mockResolvedValueOnce(incomingPayment) diff --git a/packages/backend/src/open_payments/receiver/service.ts b/packages/backend/src/open_payments/receiver/service.ts index e461fd6ae1..36ec83b015 100644 --- a/packages/backend/src/open_payments/receiver/service.ts +++ b/packages/backend/src/open_payments/receiver/service.ts @@ -123,7 +123,11 @@ async function createLocalIncomingPayment( return incomingPaymentOrError } - if (incomingPaymentOrError.isExpiredOrComplete()) { + const streamCredentials = deps.streamCredentialsService.get( + incomingPaymentOrError + ) + + if (!streamCredentials) { const errorMessage = 'Could not get stream credentials for local incoming payment' deps.logger.error({ incomingPaymentOrError }, errorMessage) @@ -131,10 +135,6 @@ async function createLocalIncomingPayment( throw new Error(errorMessage) } - const streamCredentials = deps.streamCredentialsService.get( - incomingPaymentOrError - ) - return incomingPaymentOrError.toOpenPaymentsTypeWithMethods( walletAddress, streamCredentials @@ -221,12 +221,16 @@ async function getLocalIncomingPayment({ walletAddressId: walletAddress.id }) - if (!incomingPayment || incomingPayment.isExpiredOrComplete()) { + if (!incomingPayment) { return undefined } const streamCredentials = deps.streamCredentialsService.get(incomingPayment) + if (!streamCredentials) { + return undefined + } + return incomingPayment.toOpenPaymentsTypeWithMethods( walletAddress, streamCredentials diff --git a/packages/backend/src/payment-method/ilp/stream-credentials/service.ts b/packages/backend/src/payment-method/ilp/stream-credentials/service.ts index e9c3505297..d57b164d13 100644 --- a/packages/backend/src/payment-method/ilp/stream-credentials/service.ts +++ b/packages/backend/src/payment-method/ilp/stream-credentials/service.ts @@ -6,7 +6,7 @@ import { StreamCredentials as IlpStreamCredentials } from '@interledger/stream-r export { IlpStreamCredentials } export interface StreamCredentialsService { - get(payment: IncomingPayment): IlpStreamCredentials + get(payment: IncomingPayment): IlpStreamCredentials | undefined } export interface ServiceDependencies extends BaseService { @@ -32,7 +32,10 @@ export async function createStreamCredentialsService( function getStreamCredentials( deps: ServiceDependencies, payment: IncomingPayment -): IlpStreamCredentials { +): IlpStreamCredentials | undefined { + if (payment.isExpiredOrComplete()) { + return undefined + } const credentials = deps.streamServer.generateCredentials({ paymentTag: payment.id, asset: { From 4f1f58376522f0dc2a3d0bfbc8f328f2803e921c Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:45:07 -0400 Subject: [PATCH 36/39] fix(backend): use fixed open-payments paginationArgs --- packages/backend/package.json | 2 +- packages/backend/src/shared/pagination.ts | 4 +--- pnpm-lock.yaml | 8 ++++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index bdb857e61c..4ae132c1a0 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -53,7 +53,7 @@ "@graphql-tools/load": "^8.0.0", "@graphql-tools/schema": "^10.0.0", "@interledger/http-signature-utils": "1.1.0", - "@interledger/open-payments": "5.2.1", + "@interledger/open-payments": "5.2.2", "@interledger/openapi": "1.1.0", "@interledger/pay": "0.4.0-alpha.9", "@interledger/stream-receiver": "^0.3.3-alpha.3", diff --git a/packages/backend/src/shared/pagination.ts b/packages/backend/src/shared/pagination.ts index 205afb523f..8af77ea58e 100644 --- a/packages/backend/src/shared/pagination.ts +++ b/packages/backend/src/shared/pagination.ts @@ -1,9 +1,7 @@ -import { PaginationArgs as OpenPaymentsPaginationArgs } from '@interledger/open-payments' +import { PaginationArgs } from '@interledger/open-payments' import { BaseModel, PageInfo, Pagination } from './baseModel' -type PaginationArgs = Omit - export function parsePaginationQueryParameters({ first, last, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 789747c773..604bd143f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -303,8 +303,8 @@ importers: specifier: 1.1.0 version: 1.1.0 '@interledger/open-payments': - specifier: 5.2.1 - version: 5.2.1 + specifier: 5.2.2 + version: 5.2.2 '@interledger/openapi': specifier: 1.1.0 version: 1.1.0 @@ -4415,8 +4415,8 @@ packages: - supports-color dev: false - /@interledger/open-payments@5.2.1: - resolution: {integrity: sha512-JzMOhu5eZRTaan4WFXclZo7eh0Ez+dk4ct7CpZC9nVu3N5rQK2FCG08HpCm55xBp5rKncmUQb6v0f+Bz/B7SHw==} + /@interledger/open-payments@5.2.2: + resolution: {integrity: sha512-Tp65PDdgwX/Uc47zbuYpOoFAs9k4wV99LBKhEu804zBWeAmWy5ULRsJwDabSgqgmdnjkRhFJEmO/HOxVqb9rTg==} dependencies: '@interledger/http-signature-utils': 1.1.0 '@interledger/openapi': 1.2.0 From b1b0941076fa060b1b0f7221ede82cbc241e9976 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:21:55 -0400 Subject: [PATCH 37/39] fix(backend): add stream credential test cases --- .../ilp/stream-credentials/service.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/backend/src/payment-method/ilp/stream-credentials/service.test.ts b/packages/backend/src/payment-method/ilp/stream-credentials/service.test.ts index 58d63a8f1b..22c4f4842b 100644 --- a/packages/backend/src/payment-method/ilp/stream-credentials/service.test.ts +++ b/packages/backend/src/payment-method/ilp/stream-credentials/service.test.ts @@ -10,6 +10,7 @@ import { createIncomingPayment } from '../../../tests/incomingPayment' import { createWalletAddress } from '../../../tests/walletAddress' import { truncateTables } from '../../../tests/tableManager' import assert from 'assert' +import { IncomingPaymentState } from '../../../graphql/generated/graphql' describe('Stream Credentials Service', (): void => { let deps: IocContract @@ -49,5 +50,21 @@ describe('Stream Credentials Service', (): void => { sharedSecret: expect.any(Buffer) }) }) + + test.each` + state + ${IncomingPaymentState.Completed} + ${IncomingPaymentState.Expired} + `( + `returns undefined for $state incoming payment`, + async ({ state }): Promise => { + await incomingPayment.$query(knex).patch({ + state, + expiresAt: + state === IncomingPaymentState.Expired ? new Date() : undefined + }) + expect(streamCredentialsService.get(incomingPayment)).toBeUndefined() + } + ) }) }) From d01ea18b709526a3e2396a769f8f6d478770a58b Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:36:13 -0400 Subject: [PATCH 38/39] refactor(backend): implement openapi request changes --- .../payment/incoming_remote/service.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/open_payments/payment/incoming_remote/service.test.ts b/packages/backend/src/open_payments/payment/incoming_remote/service.test.ts index 946c190e29..41f26e9087 100644 --- a/packages/backend/src/open_payments/payment/incoming_remote/service.test.ts +++ b/packages/backend/src/open_payments/payment/incoming_remote/service.test.ts @@ -149,11 +149,12 @@ describe('Remote Incoming Payment Service', (): void => { expect(incomingPayment).toStrictEqual(mockedIncomingPayment) expect(clientCreateIncomingPaymentSpy).toHaveBeenCalledWith( { - walletAddress: walletAddress.id, + url: walletAddress.id, accessToken: grant.accessToken }, { ...args, + walletAddress: walletAddress.id, expiresAt: args.expiresAt ? args.expiresAt.toISOString() : undefined, @@ -230,13 +231,14 @@ describe('Remote Incoming Payment Service', (): void => { expect(incomingPayment).toStrictEqual(mockedIncomingPayment) expect(clientCreateIncomingPaymentSpy).toHaveBeenCalledWith( { - walletAddress: walletAddress.id, + url: walletAddress.id, accessToken: grant.expired ? newToken.access_token.value : grant.accessToken }, { ...args, + walletAddress: walletAddress.id, expiresAt: args.expiresAt ? args.expiresAt.toISOString() : undefined, @@ -316,11 +318,12 @@ describe('Remote Incoming Payment Service', (): void => { }) expect(clientCreateIncomingPaymentSpy).toHaveBeenCalledWith( { - walletAddress: walletAddress.id, + url: walletAddress.id, accessToken: grant.access_token.value }, { ...args, + walletAddress: walletAddress.id, expiresAt: args.expiresAt ? args.expiresAt.toISOString() : undefined, From 65fbef058bc9c29f30a6d31821c760db2a7de20b Mon Sep 17 00:00:00 2001 From: Nathan Lie Date: Fri, 20 Oct 2023 15:00:38 -0700 Subject: [PATCH 39/39] docs: fix typos in Postman --- postman/collections/Interledger.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/postman/collections/Interledger.json b/postman/collections/Interledger.json index 498bd4d9b7..37458ce749 100644 --- a/postman/collections/Interledger.json +++ b/postman/collections/Interledger.json @@ -1620,10 +1620,12 @@ } ], "url": { - "raw": "{{pfryWalletAddress}}/incoming-payments/{{incomingPaymentId}}", + "raw": "http://localhost:4000/incoming-payments/{{incomingPaymentId}}", + "protocol": "http", "host": [ - "{{pfryWalletAddress}}" + "localhost" ], + "port": "4000", "path": [ "incoming-payments", "{{incomingPaymentId}}" @@ -1813,7 +1815,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"walletAddress\": {{gfranklinWalletAddress}},\n \"receiver\": \"https://happy-life-bank-backend/accounts/pfry/incoming-payments/{{incomingPaymentId}}\",\n \"debitAmount\": {\n \"value\": \"1000\",\n \"assetCode\": \"USD\",\n \"assetScale\": 2\n }\n \"method\": \"ilp\"\n}", + "raw": "{\n \"walletAddress\": {{gfranklinWalletAddress}},\n \"receiver\": \"https://happy-life-bank-backend/accounts/pfry/incoming-payments/{{incomingPaymentId}}\",\n \"debitAmount\": {\n \"value\": \"1000\",\n \"assetCode\": \"USD\",\n \"assetScale\": 2\n },\n \"method\": \"ilp\"\n}", "options": { "raw": { "language": "json"