Skip to content

Commit

Permalink
feat(backend): change outgoing payment create interface
Browse files Browse the repository at this point in the history
- can now take args to create from either
a quote or incoming payment
- includes some WIP stuff in route test and
service that needs cleanup
  • Loading branch information
BlairCurrey committed Apr 3, 2024
1 parent 88ae1d4 commit b69b430
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 10 deletions.
61 changes: 56 additions & 5 deletions packages/backend/src/open_payments/payment/outgoing/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ import {
import { truncateTables } from '../../../tests/tableManager'
import { createAsset } from '../../../tests/asset'
import { errorToCode, errorToMessage, OutgoingPaymentError } from './errors'
import { CreateOutgoingPaymentOptions, OutgoingPaymentService } from './service'
import {
CreateFromIncomingPayment,
CreateFromQuote,
OutgoingPaymentService
} from './service'
import { OutgoingPayment, OutgoingPaymentState } from './model'
import { OutgoingPaymentRoutes, CreateBody } from './routes'
import { serializeAmount } from '../../amount'
import { Amount, serializeAmount } from '../../amount'
import { Grant } from '../../auth/middleware'
import { WalletAddress } from '../../wallet_address/model'
import {
Expand All @@ -28,6 +32,7 @@ import {
} from '../../wallet_address/model.test'
import { createOutgoingPayment } from '../../../tests/outgoingPayment'
import { createWalletAddress } from '../../../tests/walletAddress'
import { createIncomingPayment } from '../../../tests/incomingPayment'

describe('Outgoing Payment Routes', (): void => {
let deps: IocContract<AppServices>
Expand All @@ -41,7 +46,7 @@ describe('Outgoing Payment Routes', (): void => {

const receivingWalletAddress = `https://wallet.example/${uuid()}`

const createPayment = async (options: {
const createPayment = async (options?: {
client?: string
grant?: Grant
metadata?: Record<string, unknown>
Expand Down Expand Up @@ -163,7 +168,9 @@ describe('Outgoing Payment Routes', (): void => {

describe('create', (): void => {
const setup = (
options: Omit<CreateOutgoingPaymentOptions, 'walletAddressId'>
options:
| Omit<CreateFromQuote, 'walletAddressId'>
| Omit<CreateFromIncomingPayment, 'walletAddressId'>
): CreateContext<CreateBody> =>
setupContext<CreateContext<CreateBody>>({
reqOpts: {
Expand All @@ -184,7 +191,7 @@ describe('Outgoing Payment Routes', (): void => {
grant | client | description
${{ id: uuid() }} | ${faker.internet.url({ appendSlash: false })} | ${'grant'}
${undefined} | ${undefined} | ${'no grant'}
`('create ($description)', ({ grant, client }): void => {
`('create from quote ($description)', ({ grant, client }): void => {
test('returns the outgoing payment on success (metadata)', async (): Promise<void> => {
const metadata = {
description: 'rent',
Expand Down Expand Up @@ -245,6 +252,50 @@ describe('Outgoing Payment Routes', (): void => {
})
})

test('create from incoming payment', async (): Promise<void> => {
const asset = await createAsset(deps)
const receiverWalletAddress = await createWalletAddress(deps, {
assetId: asset.id
})
const debitAmount: Amount = {
value: BigInt(56),
assetCode: walletAddress.asset.code,
assetScale: walletAddress.asset.scale
}
const incomingPayment = await createIncomingPayment(deps, {
walletAddressId: receiverWalletAddress.id,
incomingAmount: {
value: BigInt(56),
assetCode: asset.code,
assetScale: asset.scale
}
})
console.log({ incomingPayment })
const ctx = setup({
incomingPaymentId: incomingPayment.id,
debitAmount
})
// Was created to test the create interface (via ctx setup above and (maybe) create(ctx) below).
// TODO: remove this or fully implement it
throw new Error('TODO: implement fully or remove this test')
// TODO: remove this comment. createSpy can break other tests. ran it without underlying creat
// method ever being called. this caused future tests to use the mock (i think) and fail
// const createSpy = jest
// .spyOn(outgoingPaymentService, 'create')
// .mockResolvedValueOnce({} as OutgoingPayment)
// TODO: spy on quoteService.create
// TODO: assert quoteService create called with correct args
await expect(outgoingPaymentRoutes.create(ctx)).resolves.toBeUndefined()
// TODO: assert create outgoing payment spy called with correct args
// expect(createSpy).toHaveBeenCalledWith({
// walletAddressId: walletAddress.id,
// quoteId: payment.quote.id,
// metadata,
// client,
// grant
// })
})

test.each(Object.values(OutgoingPaymentError).map((err) => [err]))(
'returns error on %s',
async (error): Promise<void> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import {
OutgoingPaymentError,
isOutgoingPaymentError
} from './errors'
import { CreateOutgoingPaymentOptions, OutgoingPaymentService } from './service'
import {
CreateFromIncomingPayment,
CreateFromQuote,
OutgoingPaymentService
} from './service'
import { createTestApp, TestContainer } from '../../../tests/app'
import { Config } from '../../../config/app'
import { Grant } from '../../auth/middleware'
Expand Down Expand Up @@ -650,7 +654,9 @@ describe('OutgoingPaymentService', (): void => {

describe('validateGrant', (): void => {
let quote: Quote
let options: Omit<CreateOutgoingPaymentOptions, 'grant'>
let options:
| Omit<CreateFromQuote, 'grant'>
| Omit<CreateFromIncomingPayment, 'grant'>
let interval: string
beforeEach(async (): Promise<void> => {
quote = await createQuote(deps, {
Expand Down
35 changes: 32 additions & 3 deletions packages/backend/src/open_payments/payment/outgoing/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { knex } from 'knex'
import { AccountAlreadyExistsError } from '../../../accounting/errors'
import { PaymentMethodHandlerService } from '../../../payment-method/handler/service'
import { TelemetryService } from '../../../telemetry/service'
import { Amount } from '../../amount'

export interface OutgoingPaymentService
extends WalletAddressSubresourceService<OutgoingPayment> {
Expand Down Expand Up @@ -88,20 +89,48 @@ async function getOutgoingPayment(
}
}

export interface CreateOutgoingPaymentOptions {
interface CreateBase {
walletAddressId: string
quoteId: string
client?: string
grant?: Grant
metadata?: Record<string, unknown>
callback?: (f: unknown) => NodeJS.Timeout
grantLockTimeoutMs?: number
}

export interface CreateFromQuote extends CreateBase {
quoteId: string
}

export interface CreateFromIncomingPayment extends CreateBase {
incomingPaymentId: string
debitAmount: Amount
}

export type CreateOutgoingPaymentOptions =
| CreateFromQuote
| CreateFromIncomingPayment

function isCreateFromQuote(
options: CreateOutgoingPaymentOptions
): options is CreateFromQuote {
return 'quoteId' in options
}

function isCreateFromIncomingPayment(
options: CreateOutgoingPaymentOptions
): options is CreateFromIncomingPayment {
return 'incomingPaymentId' in options && 'debitAmount' in options
}

async function createOutgoingPayment(
deps: ServiceDependencies,
options: CreateOutgoingPaymentOptions
options: CreateFromQuote | CreateFromIncomingPayment
): Promise<OutgoingPayment | OutgoingPaymentError> {
if (isCreateFromIncomingPayment(options)) {
throw new Error('Create from Incoming Payment not implemented')
}

const grantId = options.grant?.id
const walletAddressId = options.walletAddressId
try {
Expand Down

0 comments on commit b69b430

Please sign in to comment.