Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

662/mk/use open payments types #1024

Merged
merged 33 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c3ccd88
feat(open-payments): export the different IncomingPayment types
mkurapov Jan 24, 2023
3415f5f
feat(backend): use open-payment types for IncomingPayment
mkurapov Jan 24, 2023
4693a79
feat(backend): use open-payment types for Connection
mkurapov Jan 24, 2023
c633b30
feat(backend): use open-payment types for OutgoingPayment
mkurapov Jan 24, 2023
909462a
feat(backend): use open-payment types for Quote
mkurapov Jan 24, 2023
8feaacb
chore(backend): fix IncomingPayments.toOpenPaymentsType
mkurapov Jan 24, 2023
5300073
chore(open-payments): update mocks
mkurapov Jan 24, 2023
01a0ba0
chore(backend): formatting
mkurapov Jan 24, 2023
36d8b8e
chore(backend): add better typing for incomingPayment.toOpenPaymentsType
mkurapov Jan 25, 2023
d04f994
chore(backend): remove QuoteJSON
mkurapov Jan 25, 2023
379aee8
Merge branch 'main' into 662/mk/use-open-payments-types
mkurapov Jan 25, 2023
11fa542
chore(open-payments): fix mocks
mkurapov Jan 25, 2023
27cfdde
chore(backend): fix IncomingPayment types
mkurapov Jan 25, 2023
4efddb3
chore(open-payments): fix incoming payment types
mkurapov Jan 25, 2023
dc051ec
chore(backend): fix incoming payment method
mkurapov Jan 25, 2023
0fddf9e
chore(backend): make toOpenPaymentsType async
mkurapov Jan 28, 2023
ad57048
chore(backend): getUrl for OutgoingPayment
mkurapov Jan 28, 2023
d48dc0a
chore(backend): fix tests with async toOpenPaymentsType
mkurapov Jan 28, 2023
9302bad
chore(backend): return undefined instead of null
mkurapov Jan 28, 2023
5e5d4e8
chore(backend): add awaits where necessary
mkurapov Jan 28, 2023
25972d3
chore(open-payments): make getBody async
mkurapov Jan 28, 2023
4800c80
chore(backend): fix outgoingPayment model method
mkurapov Jan 28, 2023
cf95410
chore(backend): fix list tests
mkurapov Jan 28, 2023
9a967f1
chore(backend): update receiver tests
mkurapov Jan 28, 2023
e5f3c03
Merge branch 'main' into 662/mk/use-open-payments-types
mkurapov Feb 1, 2023
5733224
chore(backend): make toOpenPaymentType sync (#1056)
mkurapov Feb 1, 2023
28a34df
chore(backend): remove $formatJson where applicable
mkurapov Feb 2, 2023
5e95486
chore(backend): remove $formatJson where applicable
mkurapov Feb 2, 2023
b6d25c6
chore(backend): add test for incomingPayment model
mkurapov Feb 2, 2023
09a0c10
chore(backend): fix incomingPayment model test
mkurapov Feb 2, 2023
c079942
chore(open-payments): use suggestion
mkurapov Feb 2, 2023
c55e586
chore(backend): fix test
mkurapov Feb 2, 2023
fc311e2
chore(backend): use suggestion
mkurapov Feb 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/backend/src/graphql/resolvers/receiver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import { AppServices } from '../../app'
import { initIocContainer } from '../..'
import { Config } from '../../config/app'
import { Amount, serializeAmount } from '../../open_payments/amount'
import { mockIncomingPayment, mockPaymentPointer } from 'open-payments'
import {
mockIncomingPaymentWithConnection,
mockPaymentPointer
} from 'open-payments'
import { CreateReceiverResponse } from '../generated/graphql'
import { ReceiverService } from '../../open_payments/receiver/service'
import { Receiver } from '../../open_payments/receiver/model'
Expand Down Expand Up @@ -49,7 +52,7 @@ describe('Receiver Resolver', (): void => {
incomingAmount
}): Promise<void> => {
const receiver = Receiver.fromIncomingPayment(
mockIncomingPayment({
mockIncomingPaymentWithConnection({
id: `${paymentPointer.id}/incoming-payments/${uuid()}`,
paymentPointer: paymentPointer.id,
description,
Expand Down
18 changes: 0 additions & 18 deletions packages/backend/src/open_payments/connection/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@ import { IlpAddress } from 'ilp-packet'
import { ILPStreamConnection } from 'open-payments'
import { IncomingPayment } from '../payment/incoming/model'

export type ConnectionJSON = {
id: string
ilpAddress: IlpAddress
sharedSecret: string
assetCode: string
assetScale: number
}

export abstract class ConnectionBase {
protected constructor(
public readonly ilpAddress: IlpAddress,
Expand Down Expand Up @@ -55,16 +47,6 @@ export class Connection extends ConnectionBase {
return `${this.openPaymentsUrl}/connections/${this.id}`
}

public toJSON(): ConnectionJSON {
return {
id: this.url,
ilpAddress: this.ilpAddress,
sharedSecret: base64url(this.sharedSecret),
assetCode: this.assetCode,
assetScale: this.assetScale
}
}

public toOpenPaymentsType(): ILPStreamConnection {
return {
id: this.url,
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/open_payments/connection/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ async function getConnection(

const connection = deps.connectionService.get(incomingPayment)
if (!connection) return ctx.throw(404)
ctx.body = connection.toJSON()
ctx.body = connection.toOpenPaymentsType()
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('Connection Service', (): void => {
expect(connection.url).toEqual(
`${Config.openPaymentsUrl}/connections/${incomingPayment.connectionId}`
)
expect(connection.toJSON()).toEqual({
expect(connection.toOpenPaymentsType()).toEqual({
id: connection.url,
ilpAddress: connection.ilpAddress,
sharedSecret: base64url(connection.sharedSecret || ''),
Expand Down
79 changes: 50 additions & 29 deletions packages/backend/src/open_payments/payment/incoming/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Model, ModelOptions, Pojo, QueryContext } from 'objection'
import { v4 as uuid } from 'uuid'

import { Amount, AmountJSON, serializeAmount } from '../../amount'
import { Connection, ConnectionJSON } from '../../connection/model'
import { Connection } from '../../connection/model'
import {
PaymentPointer,
PaymentPointerSubresource
Expand All @@ -11,7 +11,11 @@ import { Asset } from '../../../asset/model'
import { LiquidityAccount, OnCreditOptions } from '../../../accounting/service'
import { ConnectorAccount } from '../../../connector/core/rafiki'
import { WebhookEvent } from '../../../webhook/model'
import { IncomingPayment as OpenPaymentsIncomingPayment } from 'open-payments'
import {
IncomingPayment as OpenPaymentsIncomingPayment,
IncomingPaymentWithConnection as OpenPaymentsIncomingPaymentWithConnection,
IncomingPaymentWithConnectionUrl as OpenPaymentsIncomingPaymentWithConnectionUrl
} from 'open-payments'

export enum IncomingPaymentEventType {
IncomingPaymentExpired = 'incoming_payment.expired',
Expand Down Expand Up @@ -80,7 +84,6 @@ export class IncomingPayment
}
}

public paymentPointer!: PaymentPointer
Copy link
Contributor Author

@mkurapov mkurapov Jan 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no non-null assertion since we can start removing withGraphFetched('paymentPointer') from the DB calls

public description?: string
public expiresAt!: Date
public state!: IncomingPaymentState
Expand Down Expand Up @@ -127,8 +130,15 @@ export class IncomingPayment
this.receivedAmountValue = amount.value
}

public get url(): string {
return `${this.paymentPointer.url}${IncomingPayment.urlPath}/${this.id}`
public async getUrl(fetchedPaymentPointer?: PaymentPointer): Promise<string> {
Copy link
Contributor Author

@mkurapov mkurapov Jan 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding fetchedPaymentPointer in-case you already have the paymentPointer and don't want to grab it again, like in toOpenPaymentsType

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if callers just assigned incomingPayment.paymentPointer = fetchedPaymentPointer, so that getUrl+getPaymentPointer only dealt with whether this.paymentPointer is defined or not?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking a step back, if/when we change subresource urls to not include the payment pointer, we'll no longer need an async getUrl.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hesitant to do that, since
a) we either have to change the type definitions of getUrl/toOpenPaymentsType to possibly return undefined, or throw inside the method if paymentPointer isn't defined
b) it's not entirely clear that the caller has to do that in order to make these methods work.

Maybe we can

  1. have toOpenPaymentsType take in a paymentPointer (this would also make it sync), it would require passing in ctx.paymentPointer in most places but I think that's ok
  2. A bit more complicated, but we make a separate <subresource>WithPaymentPointer class that constructs a new model with the paymentPointer defined, although I'm not sure the additional boilerplate is worth it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, I'm only suggesting changing getUrl to

Suggested change
public async getUrl(fetchedPaymentPointer?: PaymentPointer): Promise<string> {
public async getUrl(): Promise<string> {

and callers can optionally define incomingPayment.paymentPointer to avoid $relatedQuery in getPaymentPointer.

const paymentPointer =
fetchedPaymentPointer || (await this.getPaymentPointer())

return `${paymentPointer.url}${IncomingPayment.urlPath}/${this.id}`
}

public async getPaymentPointer(): Promise<PaymentPointer> {
return this.paymentPointer ?? (await this.$relatedQuery('paymentPointer'))
}

public async onCredit({
Expand Down Expand Up @@ -239,39 +249,50 @@ export class IncomingPayment
return payment
}

public toOpenPaymentsType({
ilpStreamConnection
}: {
public async toOpenPaymentsType(): Promise<OpenPaymentsIncomingPayment>
public async toOpenPaymentsType(
ilpStreamConnection: Connection
}): OpenPaymentsIncomingPayment {
return {
id: this.url,
paymentPointer: this.paymentPointer.url,
): Promise<OpenPaymentsIncomingPaymentWithConnection>
public async toOpenPaymentsType(
ilpStreamConnection: string
): Promise<OpenPaymentsIncomingPaymentWithConnectionUrl>
public async toOpenPaymentsType(
ilpStreamConnection?: Connection | string
): Promise<
| OpenPaymentsIncomingPayment
| OpenPaymentsIncomingPaymentWithConnection
| OpenPaymentsIncomingPaymentWithConnectionUrl
> {
const paymentPointer = await this.getPaymentPointer()
const baseIncomingPayment: OpenPaymentsIncomingPayment = {
id: await this.getUrl(paymentPointer),
paymentPointer: paymentPointer.url,
incomingAmount: this.incomingAmount
? serializeAmount(this.incomingAmount)
: undefined,
receivedAmount: serializeAmount(this.receivedAmount),
completed: this.completed,
description: this.description,
externalRef: this.externalRef,
createdAt: this.createdAt.toISOString(),
updatedAt: this.updatedAt.toISOString(),
expiresAt: this.expiresAt.toISOString(),
expiresAt: this.expiresAt.toISOString()
}

if (!ilpStreamConnection) {
return baseIncomingPayment
}

if (typeof ilpStreamConnection === 'string') {
return {
...baseIncomingPayment,
ilpStreamConnection
}
}

return {
...baseIncomingPayment,
ilpStreamConnection: ilpStreamConnection.toOpenPaymentsType()
}
}
}

// TODO: disallow undefined
// https://github.com/interledger/rafiki/issues/594
export type IncomingPaymentJSON = {
id: string
paymentPointer: string
incomingAmount?: AmountJSON
receivedAmount: AmountJSON
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wilsonianb Keeping AmountJSON over OpenPaymentsAmount makes sense right? The Amount interface is a more generic thing we use across the code, not necessarily being an "open payments" concept

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I'm good with keeping amount values as strings in open-payments and leaving it to callers to (de)serialize.

completed: boolean
description?: string
externalRef?: string
createdAt: string
updatedAt: string
expiresAt?: string
ilpStreamConnection?: ConnectionJSON | string
}
54 changes: 28 additions & 26 deletions packages/backend/src/open_payments/payment/incoming/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,31 +82,33 @@ describe('Incoming Payment Routes', (): void => {
externalRef
}),
get: (ctx) => incomingPaymentRoutes.get(ctx),
getBody: (incomingPayment, list) => ({
id: incomingPayment.url,
paymentPointer: paymentPointer.url,
completed: false,
incomingAmount:
incomingPayment.incomingAmount &&
serializeAmount(incomingPayment.incomingAmount),
description: incomingPayment.description,
expiresAt: incomingPayment.expiresAt.toISOString(),
createdAt: incomingPayment.createdAt.toISOString(),
updatedAt: incomingPayment.updatedAt.toISOString(),
receivedAmount: serializeAmount(incomingPayment.receivedAmount),
externalRef: '#123',
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
}
}),
getBody: async (incomingPayment, list) => {
return {
id: await incomingPayment.getUrl(),
paymentPointer: paymentPointer.url,
completed: false,
incomingAmount:
incomingPayment.incomingAmount &&
serializeAmount(incomingPayment.incomingAmount),
description: incomingPayment.description,
expiresAt: incomingPayment.expiresAt.toISOString(),
createdAt: incomingPayment.createdAt.toISOString(),
updatedAt: incomingPayment.updatedAt.toISOString(),
receivedAmount: serializeAmount(incomingPayment.receivedAmount),
externalRef: '#123',
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
}
}
},
list: (ctx) => incomingPaymentRoutes.list(ctx),
urlPath: IncomingPayment.urlPath
})
Expand Down Expand Up @@ -263,7 +265,7 @@ describe('Incoming Payment Routes', (): void => {
await expect(incomingPaymentRoutes.complete(ctx)).resolves.toBeUndefined()
expect(ctx.response).toSatisfyApiSpec()
expect(ctx.body).toEqual({
id: incomingPayment.url,
id: await incomingPayment.getUrl(),
paymentPointer: paymentPointer.url,
incomingAmount: {
value: '123',
Expand Down
44 changes: 17 additions & 27 deletions packages/backend/src/open_payments/payment/incoming/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '../../../app'
import { IAppConfig } from '../../../config/app'
import { IncomingPaymentService } from './service'
import { IncomingPayment, IncomingPaymentJSON } from './model'
import { IncomingPayment } from './model'
import {
errorToCode,
errorToMessage,
Expand All @@ -17,8 +17,13 @@ import {
} from './errors'
import { AmountJSON, parseAmount } from '../../amount'
import { listSubresource } from '../../payment_pointer/routes'
import { ConnectionJSON } from '../../connection/model'
import { Connection } from '../../connection/model'
import { ConnectionService } from '../../connection/service'
import {
IncomingPayment as OpenPaymentsIncomingPayment,
IncomingPaymentWithConnection as OpenPaymentsIncomingPaymentWithConnection,
IncomingPaymentWithConnectionUrl as OpenPaymentsIncomingPaymentWithConnectionUrl
} from 'open-payments'

// Don't allow creating an incoming payment too far out. Incoming payments with no payments before they expire are cleaned up, since incoming payments creation is unauthenticated.
// TODO what is a good default value for this?
Expand Down Expand Up @@ -70,7 +75,7 @@ async function getIncomingPayment(
}
if (!incomingPayment) return ctx.throw(404)
const connection = deps.connectionService.get(incomingPayment)
ctx.body = incomingPaymentToBody(deps, incomingPayment, connection?.toJSON())
ctx.body = incomingPaymentToBody(incomingPayment, connection)
}

export type CreateBody = {
Expand Down Expand Up @@ -111,11 +116,7 @@ async function createIncomingPayment(

ctx.status = 201
const connection = deps.connectionService.get(incomingPaymentOrError)
ctx.body = incomingPaymentToBody(
deps,
incomingPaymentOrError,
connection?.toJSON()
)
ctx.body = incomingPaymentToBody(incomingPaymentOrError, connection)
}

async function completeIncomingPayment(
Expand All @@ -137,7 +138,7 @@ async function completeIncomingPayment(
errorToMessage[incomingPaymentOrError]
)
}
ctx.body = incomingPaymentToBody(deps, incomingPaymentOrError)
ctx.body = incomingPaymentToBody(incomingPaymentOrError)
}

async function listIncomingPayments(
Expand All @@ -149,29 +150,18 @@ async function listIncomingPayments(
ctx,
getPaymentPointerPage: deps.incomingPaymentService.getPaymentPointerPage,
toBody: (payment) =>
incomingPaymentToBody(
deps,
payment,
deps.connectionService.getUrl(payment)
)
incomingPaymentToBody(payment, deps.connectionService.getUrl(payment))
})
} catch (_) {
ctx.throw(500, 'Error trying to list incoming payments')
}
}

function incomingPaymentToBody(
deps: ServiceDependencies,
incomingPayment: IncomingPayment,
ilpStreamConnection?: ConnectionJSON | string
): IncomingPaymentJSON {
const body = {
...incomingPayment.toJSON(),
id: incomingPayment.url,
paymentPointer: incomingPayment.paymentPointer.url
} as unknown as IncomingPaymentJSON
if (ilpStreamConnection) {
body.ilpStreamConnection = ilpStreamConnection
}
return body
ilpStreamConnection?: Connection | string
):
| OpenPaymentsIncomingPayment
| OpenPaymentsIncomingPaymentWithConnection
| OpenPaymentsIncomingPaymentWithConnectionUrl {
return incomingPayment.toOpenPaymentsType(ilpStreamConnection)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import {
AuthenticatedClient as OpenPaymentsClient,
AccessAction,
AccessType,
mockIncomingPayment,
mockInteractiveGrant,
mockNonInteractiveGrant,
mockPaymentPointer
mockPaymentPointer,
mockIncomingPaymentWithConnection
} from 'open-payments'
import { GrantService } from '../../grant/service'
import { RemoteIncomingPaymentError } from './errors'
Expand Down Expand Up @@ -90,7 +90,7 @@ describe('Remote Incoming Payment Service', (): void => {
${undefined} | ${undefined} | ${undefined} | ${undefined}
${amount} | ${new Date(Date.now() + 30_000)} | ${'Test incoming payment'} | ${'#123'}
`('creates remote incoming payment ($#)', async (args): Promise<void> => {
const mockedIncomingPayment = mockIncomingPayment({
const mockedIncomingPayment = mockIncomingPaymentWithConnection({
...args,
paymentPointerUrl: paymentPointer.id
})
Expand Down Expand Up @@ -178,7 +178,7 @@ describe('Remote Incoming Payment Service', (): void => {
${undefined} | ${undefined} | ${undefined} | ${undefined}
${amount} | ${new Date(Date.now() + 30_000)} | ${'Test incoming payment'} | ${'#123'}
`('creates remote incoming payment ($#)', async (args): Promise<void> => {
const mockedIncomingPayment = mockIncomingPayment({
const mockedIncomingPayment = mockIncomingPaymentWithConnection({
...args,
paymentPointerUrl: paymentPointer.id
})
Expand Down
Loading