Skip to content

Commit

Permalink
Merge branch 'mk/219/methods' into integration
Browse files Browse the repository at this point in the history
  • Loading branch information
mkurapov committed Sep 27, 2023
2 parents 597ab28 + 3b1049e commit 12ec9e9
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 40 deletions.
14 changes: 9 additions & 5 deletions openapi/resource-server.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: Open Payments
version: '1.3'
version: '1.4'
license:
name: Apache 2.0
identifier: Apache-2.0
Expand Down Expand Up @@ -947,10 +947,10 @@ components:
description: An **incoming payment** resource with public details.
type: object
examples:
- receivedAmount: null
value: '0'
assetCode: USD
assetScale: 2
- receivedAmount:
value: '0'
assetCode: USD
assetScale: 2
properties:
receiveAmount:
$ref: ./schemas.yaml#/components/schemas/amount
Expand Down Expand Up @@ -1220,6 +1220,10 @@ components:
type: string
pattern: '^[a-zA-Z0-9-_]+$'
description: The base64 url-encoded shared secret to use when establishing a STREAM connection.
required:
- type
- ilpAddress
- sharedSecret
examples:
- type: string
ilpAddress: string
Expand Down
19 changes: 10 additions & 9 deletions packages/open-payments/src/client/incoming-payment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
defaultAxiosInstance,
mockIncomingPayment,
mockIncomingPaymentPaginationResult,
mockIncomingPaymentWithPaymentMethods,
mockOpenApiResponseValidators,
silentLogger
} from '../test/helpers'
Expand Down Expand Up @@ -48,7 +49,7 @@ describe('incoming-payment', (): void => {

describe('getIncomingPayment', (): void => {
test('returns incoming payment if passes validation', async (): Promise<void> => {
const incomingPayment = mockIncomingPayment()
const incomingPayment = mockIncomingPaymentWithPaymentMethods()

nock(walletAddress)
.get('/incoming-payments/1')
Expand All @@ -66,7 +67,7 @@ describe('incoming-payment', (): void => {
})

test('throws if incoming payment does not pass validation', async (): Promise<void> => {
const incomingPayment = mockIncomingPayment({
const incomingPayment = mockIncomingPaymentWithPaymentMethods({
incomingAmount: {
assetCode: 'USD',
assetScale: 2,
Expand Down Expand Up @@ -99,7 +100,7 @@ describe('incoming-payment', (): void => {
})

test('throws if incoming payment does not pass open api validation', async (): Promise<void> => {
const incomingPayment = mockIncomingPayment()
const incomingPayment = mockIncomingPaymentWithPaymentMethods()

nock(walletAddress)
.get('/incoming-payments/1')
Expand Down Expand Up @@ -129,7 +130,7 @@ describe('incoming-payment', (): void => {
`(
'returns the incoming payment on success',
async ({ incomingAmount, expiresAt, metadata }): Promise<void> => {
const incomingPayment = mockIncomingPayment({
const incomingPayment = mockIncomingPaymentWithPaymentMethods({
incomingAmount,
expiresAt,
metadata
Expand Down Expand Up @@ -162,7 +163,7 @@ describe('incoming-payment', (): void => {
value: '10'
}

const incomingPayment = mockIncomingPayment({
const incomingPayment = mockIncomingPaymentWithPaymentMethods({
incomingAmount: amount,
receivedAmount: amount,
completed: false
Expand All @@ -184,7 +185,7 @@ describe('incoming-payment', (): void => {
})

test('throws if the created incoming payment does not pass open api validation', async (): Promise<void> => {
const incomingPayment = mockIncomingPayment()
const incomingPayment = mockIncomingPaymentWithPaymentMethods()

const scope = nock(walletAddress)
.post('/incoming-payments')
Expand Down Expand Up @@ -522,7 +523,7 @@ describe('incoming-payment', (): void => {

describe('validateCreatedIncomingPayment', (): void => {
test('returns the created incoming payment if it passes validation', async (): Promise<void> => {
const incomingPayment = mockIncomingPayment({
const incomingPayment = mockIncomingPaymentWithPaymentMethods({
incomingAmount: {
assetCode: 'USD',
assetScale: 2,
Expand All @@ -541,7 +542,7 @@ describe('incoming-payment', (): void => {
})

test('throws if received amount is a non-zero value for a newly created incoming payment', async (): Promise<void> => {
const incomingPayment = mockIncomingPayment({
const incomingPayment = mockIncomingPaymentWithPaymentMethods({
receivedAmount: {
assetCode: 'USD',
assetScale: 2,
Expand All @@ -555,7 +556,7 @@ describe('incoming-payment', (): void => {
})

test('throws if the created incoming payment is completed', async (): Promise<void> => {
const incomingPayment = mockIncomingPayment({
const incomingPayment = mockIncomingPaymentWithPaymentMethods({
completed: true
})

Expand Down
27 changes: 15 additions & 12 deletions packages/open-payments/src/client/incoming-payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ import {
getRSPath,
CreateIncomingPaymentArgs,
PaginationArgs,
IncomingPaymentPaginationResult
IncomingPaymentPaginationResult,
IncomingPaymentWithPaymentMethods
} from '../types'
import { get, post } from './requests'

type AnyIncomingPayment = IncomingPayment | IncomingPaymentWithPaymentMethods

export interface IncomingPaymentRoutes {
get(args: ResourceRequestArgs): Promise<IncomingPayment>
get(args: ResourceRequestArgs): Promise<IncomingPaymentWithPaymentMethods>
create(
args: CollectionRequestArgs,
createArgs: CreateIncomingPaymentArgs
): Promise<IncomingPayment>
): Promise<IncomingPaymentWithPaymentMethods>
complete(args: ResourceRequestArgs): Promise<IncomingPayment>
list(
args: CollectionRequestArgs,
Expand All @@ -33,13 +36,13 @@ export const createIncomingPaymentRoutes = (
const { axiosInstance, openApi, logger } = deps

const getIncomingPaymentOpenApiValidator =
openApi.createResponseValidator<IncomingPayment>({
openApi.createResponseValidator<IncomingPaymentWithPaymentMethods>({
path: getRSPath('/incoming-payments/{id}'),
method: HttpMethod.GET
})

const createIncomingPaymentOpenApiValidator =
openApi.createResponseValidator<IncomingPayment>({
openApi.createResponseValidator<IncomingPaymentWithPaymentMethods>({
path: getRSPath('/incoming-payments'),
method: HttpMethod.POST
})
Expand Down Expand Up @@ -92,7 +95,7 @@ export const createIncomingPaymentRoutes = (
export const getIncomingPayment = async (
deps: BaseDeps,
args: ResourceRequestArgs,
validateOpenApiResponse: ResponseValidator<IncomingPayment>
validateOpenApiResponse: ResponseValidator<IncomingPaymentWithPaymentMethods>
) => {
const { axiosInstance, logger } = deps
const { url } = args
Expand All @@ -119,7 +122,7 @@ export const getIncomingPayment = async (
export const createIncomingPayment = async (
deps: BaseDeps,
requestArgs: CollectionRequestArgs,
validateOpenApiResponse: ResponseValidator<IncomingPayment>,
validateOpenApiResponse: ResponseValidator<IncomingPaymentWithPaymentMethods>,
createArgs: CreateIncomingPaymentArgs
) => {
const { axiosInstance, logger } = deps
Expand Down Expand Up @@ -215,9 +218,9 @@ export const listIncomingPayment = async (
return incomingPayments
}

export const validateIncomingPayment = (
payment: IncomingPayment
): IncomingPayment => {
export const validateIncomingPayment = <T extends AnyIncomingPayment>(
payment: T
): T => {
if (payment.incomingAmount) {
const { incomingAmount, receivedAmount } = payment
if (
Expand All @@ -242,8 +245,8 @@ export const validateIncomingPayment = (
}

export const validateCreatedIncomingPayment = (
payment: IncomingPayment
): IncomingPayment => {
payment: IncomingPaymentWithPaymentMethods
): IncomingPaymentWithPaymentMethods => {
const { receivedAmount, completed } = payment

if (BigInt(receivedAmount.value) !== BigInt(0)) {
Expand Down
12 changes: 8 additions & 4 deletions packages/open-payments/src/client/quote.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ describe('quote', (): void => {
accessToken
},
openApiValidators.successfulValidator,
{ receiver: quote.receiver }
{ receiver: quote.receiver, method: 'ilp' }
)
expect(result).toStrictEqual(quote)
scope.done()
Expand All @@ -105,7 +105,7 @@ describe('quote', (): void => {
accessToken
},
openApiValidators.failedValidator,
{ receiver: quote.receiver }
{ receiver: quote.receiver, method: 'ilp' }
)
).rejects.toThrowError()
scope.done()
Expand Down Expand Up @@ -172,15 +172,19 @@ describe('quote', (): void => {
walletAddress,
accessToken
},
{ receiver: quote.receiver }
{ receiver: quote.receiver, method: 'ilp' }
)

expect(postSpy).toHaveBeenCalledWith(
{
axiosInstance,
logger
},
{ url, accessToken, body: { receiver: quote.receiver } },
{
url,
accessToken,
body: { receiver: quote.receiver, method: 'ilp' }
},
true
)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ export interface components {
*/
updatedAt: string;
};
/**
* Incoming Payment with payment methods
* @description An **incoming payment** resource with public details.
*/
"incoming-payment-with-methods": components["schemas"]["incoming-payment"] & {
/** @description The list of payment methods supported by this incoming payment. */
methods: Partial<components["schemas"]["ilp-payment-method"]>[];
};
/**
* Public Incoming Payment
* @description An **incoming payment** resource with public details.
Expand Down Expand Up @@ -195,7 +203,7 @@ export interface components {
quoteId?: string;
/** @description Describes whether the payment failed to send its full amount. */
failed?: boolean;
/** @description The URL of the incoming payment or ILP STREAM Connection that is being paid. */
/** @description The URL of the incoming payment that is being paid. */
receiver: external["schemas.yaml"]["components"]["schemas"]["receiver"];
/** @description The total amount that should be received by the receiver when this outgoing payment has been paid. */
receiveAmount: external["schemas.yaml"]["components"]["schemas"]["amount"];
Expand Down Expand Up @@ -231,12 +239,13 @@ export interface components {
* @description The URL of the wallet address from which this quote's payment would be sent.
*/
walletAddress: string;
/** @description The URL of the incoming payment or ILP STREAM Connection that the quote is created for. */
/** @description The URL of the incoming payment that the quote is created for. */
receiver: external["schemas.yaml"]["components"]["schemas"]["receiver"];
/** @description The total amount that should be received by the receiver when the corresponding outgoing payment has been paid. */
receiveAmount: external["schemas.yaml"]["components"]["schemas"]["amount"];
/** @description The total amount that should be deducted from the sender's account when the corresponding outgoing payment has been paid. */
debitAmount: external["schemas.yaml"]["components"]["schemas"]["amount"];
method: components["schemas"]["payment-method"];
/** @description The date and time when the calculated `debitAmount` is no longer valid. */
expiresAt?: string;
/**
Expand Down Expand Up @@ -269,6 +278,14 @@ export interface components {
/** @description The base64 url-encoded public key. */
x: string;
};
"payment-method": "ilp";
"ilp-payment-method": {
type: "ilp";
/** @description The ILP address to use when establishing a STREAM connection. */
ilpAddress: string;
/** @description The base64 url-encoded shared secret to use when establishing a STREAM connection. */
sharedSecret: string;
};
};
responses: {
/** Authorization required */
Expand Down Expand Up @@ -385,12 +402,7 @@ export interface operations {
};
responses: {
/** Incoming Payment Created */
201: {
content: {
"application/json": components["schemas"]["incoming-payment"];
};
};
401: components["responses"]["401"];
201: components["responses"]["401"];
403: components["responses"]["403"];
};
/**
Expand Down Expand Up @@ -521,14 +533,17 @@ export interface operations {
"application/json":
| {
receiver: external["schemas.yaml"]["components"]["schemas"]["receiver"];
method: components["schemas"]["payment-method"];
}
| {
receiver: external["schemas.yaml"]["components"]["schemas"]["receiver"];
method: components["schemas"]["payment-method"];
/** @description The fixed amount that would be paid into the receiving wallet address given a successful outgoing payment. */
receiveAmount: external["schemas.yaml"]["components"]["schemas"]["amount"];
}
| {
receiver: external["schemas.yaml"]["components"]["schemas"]["receiver"];
method: components["schemas"]["payment-method"];
/** @description The fixed amount that would be sent from the sending wallet address given a successful outgoing payment. */
debitAmount: external["schemas.yaml"]["components"]["schemas"]["amount"];
};
Expand All @@ -554,7 +569,7 @@ export interface operations {
200: {
content: {
"application/json": Partial<
components["schemas"]["incoming-payment"]
components["schemas"]["incoming-payment-with-methods"]
> &
Partial<components["schemas"]["public-incoming-payment"]>;
};
Expand Down
37 changes: 36 additions & 1 deletion packages/open-payments/src/test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import {
Quote,
IncomingPaymentPaginationResult,
PendingGrant,
Grant
Grant,
IncomingPaymentWithPaymentMethods,
IlpPaymentMethod
} from '../types'
import { v4 as uuid } from 'uuid'
import { ResponseValidator } from '@interledger/openapi'
import base64url from 'base64url'

export const silentLogger = createLogger({
level: 'silent'
Expand Down Expand Up @@ -97,6 +100,37 @@ export const mockIncomingPayment = (
...overrides
})

export const mockIncomingPaymentWithPaymentMethods = (
overrides?: Partial<IncomingPaymentWithPaymentMethods>
): IncomingPaymentWithPaymentMethods => ({
id: `https://example.com/.well-known/pay/incoming-payments/${uuid()}`,
walletAddress: 'https://example.com/.well-known/pay',
completed: false,
incomingAmount: {
assetCode: 'USD',
assetScale: 2,
value: '10'
},
receivedAmount: {
assetCode: 'USD',
assetScale: 2,
value: '0'
},
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
methods: [mockIlpPaymentMethod()],
...overrides
})

export const mockIlpPaymentMethod = (
overrides?: Partial<IlpPaymentMethod>
): IlpPaymentMethod => ({
type: 'ilp',
sharedSecret: base64url('sharedSecret'),
ilpAddress: 'test.ilpAddress',
...overrides
})

export const mockIncomingPaymentPaginationResult = (
overrides?: Partial<IncomingPaymentPaginationResult>
): IncomingPaymentPaginationResult => {
Expand Down Expand Up @@ -267,5 +301,6 @@ export const mockQuote = (overrides?: Partial<Quote>): Quote => ({
},
createdAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 60_000).toISOString(),
method: 'ilp',
...overrides
})
Loading

0 comments on commit 12ec9e9

Please sign in to comment.