-
Notifications
You must be signed in to change notification settings - Fork 89
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(backend): add Open Payments client service (#595)
* fix(backend): return correct ilpStreamConnection type Update ilp-packet version to match types. * chore(backend): add configurable DEV_ACCESS_TOKEN to bypass introspection * feat(backend): add Open Payments client service Replace calls to Pay.setupPayment Downgrade axios to v0.26.1 * chore(backend): rename QuoteError.InvalidDestination to InvalidReceiver * chore(backend): delete completed/expired incoming payment connectionId * fix(backend): pass correct host to ConnectionService Remove publicHost from OutgoingPaymentService.
- Loading branch information
1 parent
68115bb
commit e96d44e
Showing
30 changed files
with
793 additions
and
280 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
packages/backend/src/open_payments/client/service.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import { IocContract } from '@adonisjs/fold' | ||
import { faker } from '@faker-js/faker' | ||
import { Knex } from 'knex' | ||
import nock from 'nock' | ||
import { URL } from 'url' | ||
import { v4 as uuid } from 'uuid' | ||
|
||
import { OpenPaymentsClientService } from './service' | ||
import { createTestApp, TestContainer } from '../../tests/app' | ||
import { Config } from '../../config/app' | ||
import { initIocContainer } from '../..' | ||
import { AppServices } from '../../app' | ||
import { createIncomingPayment } from '../../tests/incomingPayment' | ||
import { createPaymentPointer } from '../../tests/paymentPointer' | ||
import { truncateTables } from '../../tests/tableManager' | ||
|
||
describe('Open Payments Client Service', (): void => { | ||
let deps: IocContract<AppServices> | ||
let appContainer: TestContainer | ||
let clientService: OpenPaymentsClientService | ||
let knex: Knex | ||
|
||
beforeAll(async (): Promise<void> => { | ||
deps = await initIocContainer(Config) | ||
appContainer = await createTestApp(deps) | ||
clientService = await deps.use('openPaymentsClientService') | ||
knex = await deps.use('knex') | ||
}) | ||
|
||
afterEach(async (): Promise<void> => { | ||
jest.useRealTimers() | ||
await truncateTables(knex) | ||
}) | ||
|
||
afterAll(async (): Promise<void> => { | ||
await appContainer.shutdown() | ||
}) | ||
describe('incomingPayment.get', (): void => { | ||
test.each` | ||
incomingAmount | description | externalRef | ||
${undefined} | ${undefined} | ${undefined} | ||
${BigInt(123)} | ${'Test'} | ${'#123'} | ||
`( | ||
'resolves incoming payment from Open Payments server', | ||
async ({ incomingAmount, description, externalRef }): Promise<void> => { | ||
const paymentPointer = await createPaymentPointer(deps, { | ||
mockServerPort: appContainer.openPaymentsPort | ||
}) | ||
const incomingPayment = await createIncomingPayment(deps, { | ||
paymentPointerId: paymentPointer.id, | ||
description, | ||
incomingAmount: incomingAmount && { | ||
value: incomingAmount, | ||
assetCode: paymentPointer.asset.code, | ||
assetScale: paymentPointer.asset.scale | ||
}, | ||
externalRef | ||
}) | ||
const resolvedPayment = await clientService.incomingPayment.get( | ||
incomingPayment.url | ||
) | ||
paymentPointer.scope.isDone() | ||
expect(resolvedPayment).toEqual({ | ||
...incomingPayment.toJSON(), | ||
id: incomingPayment.url, | ||
paymentPointer: incomingPayment.paymentPointer.url, | ||
ilpStreamConnection: { | ||
id: `${Config.openPaymentsUrl}/connections/${incomingPayment.connectionId}`, | ||
ilpAddress: expect.any(String), | ||
sharedSecret: expect.any(String) | ||
} | ||
}) | ||
} | ||
) | ||
test.each` | ||
statusCode | ||
${404} | ||
${500} | ||
`( | ||
'returns undefined for unsuccessful request ($statusCode)', | ||
async ({ statusCode }): Promise<void> => { | ||
const incomingPaymentUrl = new URL( | ||
`${faker.internet.url()}/incoming-payments/${uuid()}` | ||
) | ||
const scope = nock(incomingPaymentUrl.host) | ||
.get(incomingPaymentUrl.pathname) | ||
.reply(statusCode) | ||
scope.isDone() | ||
await expect( | ||
clientService.incomingPayment.get(incomingPaymentUrl.href) | ||
).resolves.toBeUndefined() | ||
} | ||
) | ||
test('returns undefined for invalid incoming payment response', async (): Promise<void> => { | ||
const incomingPaymentUrl = new URL( | ||
`${faker.internet.url()}/incoming-payments/${uuid()}` | ||
) | ||
const scope = nock(incomingPaymentUrl.host) | ||
.get(incomingPaymentUrl.pathname) | ||
.reply(200, () => ({ | ||
validPayment: 0 | ||
})) | ||
scope.isDone() | ||
await expect( | ||
clientService.incomingPayment.get(incomingPaymentUrl.href) | ||
).resolves.toBeUndefined() | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import axios from 'axios' | ||
import { OpenAPI, HttpMethod, ValidateFunction } from 'openapi' | ||
|
||
import { BaseService } from '../../shared/baseService' | ||
import { IncomingPaymentJSON } from '../payment/incoming/model' | ||
|
||
const REQUEST_TIMEOUT = 5_000 // millseconds | ||
|
||
export interface OpenPaymentsClientService { | ||
incomingPayment: { | ||
get(url: string): Promise<IncomingPaymentJSON | undefined> | ||
} | ||
} | ||
|
||
export interface ServiceDependencies extends BaseService { | ||
accessToken: string | ||
openApi: OpenAPI | ||
validateResponse: ValidateFunction<IncomingPaymentJSON> | ||
} | ||
|
||
export async function createOpenPaymentsClientService( | ||
deps_: Omit<ServiceDependencies, 'validateResponse'> | ||
): Promise<OpenPaymentsClientService> { | ||
const log = deps_.logger.child({ | ||
service: 'OpenPaymentsClientService' | ||
}) | ||
const validateResponse = | ||
deps_.openApi.createResponseValidator<IncomingPaymentJSON>({ | ||
path: '/incoming-payments/{incomingPaymentId}', | ||
method: HttpMethod.GET | ||
}) | ||
const deps: ServiceDependencies = { | ||
...deps_, | ||
logger: log, | ||
validateResponse | ||
} | ||
return { | ||
incomingPayment: { | ||
get: (url) => getIncomingPayment(deps, url) | ||
} | ||
} | ||
} | ||
|
||
export async function getIncomingPayment( | ||
deps: ServiceDependencies, | ||
url: string | ||
): Promise<IncomingPaymentJSON | undefined> { | ||
const requestHeaders = { | ||
Authorization: `GNAP ${deps.accessToken}`, | ||
'Content-Type': 'application/json' | ||
} | ||
try { | ||
const { status, data } = await axios.get(url, { | ||
headers: requestHeaders, | ||
timeout: REQUEST_TIMEOUT, | ||
validateStatus: (status) => status === 200 | ||
}) | ||
if ( | ||
!deps.validateResponse({ | ||
status, | ||
body: data | ||
}) | ||
) { | ||
throw new Error('unreachable') | ||
} | ||
return data | ||
} catch (_) { | ||
return undefined | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.