Skip to content

Commit

Permalink
feat(open-payment): add unauthenticated payment get (#307)
Browse files Browse the repository at this point in the history
* faet(open-payment): add unauthenticated payment get

* fix(open-payments): update readme

* chore(open-payments): add changeset

* fix(open-payments): use public incoming payment type

* fix(open-payments): fix field name

* test(open-payments): update tests

* fix(open-payments): add key request mention back

* feat(open-payments): add getPublic to incomingPayment

* fix(open-payments): eslint warning
  • Loading branch information
BlairCurrey authored Oct 10, 2023
1 parent 3f79915 commit f756fed
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/short-sloths-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@interledger/open-payments': minor
---

Adds incomingPayment get method to unauthenticated client
2 changes: 1 addition & 1 deletion openapi/resource-server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,7 @@ components:
assetCode: USD
assetScale: 2
properties:
receiveAmount:
receivedAmount:
$ref: ./schemas.yaml#/components/schemas/amount
unresolvedProperites: false
outgoing-payment:
Expand Down
6 changes: 5 additions & 1 deletion packages/open-payments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ This package exports two clients, an `UnauthenticatedClient` and an `Authenticat
### `UnauthenticatedClient`

This client allows making requests to access publicly available resources, without needing authentication.
The three available resources are [Wallet Addresses](https://docs.openpayments.guide/reference/get-wallet-address), [Wallet Address Keys](https://docs.openpayments.guide/reference/get-wallet-address-keys), and [ILP Stream Connections](https://docs.openpayments.guide/reference/get-ilp-stream-connection).
The available resources are [Wallet Addresses](https://docs.openpayments.guide/reference/get-wallet-address), [Wallet Address Keys](https://docs.openpayments.guide/reference/get-wallet-address-keys), and the public version of [Incoming Payments](https://docs.openpayments.guide/reference/get-incoming-payment).

```ts
import { createUnauthenticatedClient } from '@interledger/open-payments'
Expand All @@ -47,6 +47,10 @@ const client = await createUnauthenticatedClient({
const walletAddress = await client.walletAddress.get({
url: 'https://cloud-nine-wallet/alice'
})

const incomingPayment = await client.walletAddress.get({
url: 'https://cloud-nine-wallet/incoming-payment/'
})
```

### `AuthenticatedClient`
Expand Down
116 changes: 115 additions & 1 deletion packages/open-payments/src/client/incoming-payment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
getIncomingPayment,
validateCompletedIncomingPayment,
validateCreatedIncomingPayment,
validateIncomingPayment
validateIncomingPayment,
createUnauthenticatedIncomingPaymentRoutes,
getPublicIncomingPayment
} from './incoming-payment'
import { OpenAPI, HttpMethod, createOpenAPI } from '@interledger/openapi'
import {
Expand All @@ -15,6 +17,7 @@ import {
mockIncomingPaymentPaginationResult,
mockIncomingPaymentWithPaymentMethods,
mockOpenApiResponseValidators,
mockPublicIncomingPayment,
silentLogger
} from '../test/helpers'
import nock from 'nock'
Expand Down Expand Up @@ -122,6 +125,46 @@ describe('incoming-payment', (): void => {
})
})

describe('getPublicIncomingPayment', (): void => {
test('returns incoming payment if passes validation', async (): Promise<void> => {
const publicIncomingPayment = mockPublicIncomingPayment()

nock(walletAddress)
.get('/incoming-payments/1')
.reply(200, publicIncomingPayment)

const result = await getPublicIncomingPayment(
{ axiosInstance, logger },
{
url: `${walletAddress}/incoming-payments/1`
},
openApiValidators.successfulValidator
)
expect(result).toStrictEqual(publicIncomingPayment)
})

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

nock(walletAddress)
.get('/incoming-payments/1')
.reply(200, publicIncomingPayment)

await expect(
getPublicIncomingPayment(
{
axiosInstance,
logger
},
{
url: `${walletAddress}/incoming-payments/1`
},
openApiValidators.failedValidator
)
).rejects.toThrowError()
})
})

describe('createIncomingPayment', (): void => {
test.each`
incomingAmount | expiresAt | metadata
Expand Down Expand Up @@ -622,6 +665,41 @@ describe('incoming-payment', (): void => {
})
})

describe('getPublic', (): void => {
test('calls getPublic method with correct validator', async (): Promise<void> => {
const mockResponseValidator = ({ path, method }) =>
path === '/incoming-payments/{id}' && method === HttpMethod.GET

const url = `${walletAddress}/incoming-payments/1`

jest
.spyOn(openApi, 'createResponseValidator')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.mockImplementation(mockResponseValidator as any)

const publicIncomingPayment = mockPublicIncomingPayment()

const getSpy = jest
.spyOn(requestors, 'get')
.mockResolvedValueOnce(publicIncomingPayment)

await createIncomingPaymentRoutes({
openApi,
axiosInstance,
logger
}).getPublic({ accessToken, url })

expect(getSpy).toHaveBeenCalledWith(
{
axiosInstance,
logger
},
{ url },
true
)
})
})

describe('list', (): void => {
test('calls get method with correct validator', async (): Promise<void> => {
const mockResponseValidator = ({ path, method }) =>
Expand Down Expand Up @@ -730,4 +808,40 @@ describe('incoming-payment', (): void => {
})
})
})
describe('unauthenticated routes', (): void => {
describe('get', (): void => {
test('calls get method with correct validator', async (): Promise<void> => {
const mockResponseValidator = ({ path, method }) =>
path === '/incoming-payments/{id}' && method === HttpMethod.GET

const url = `${walletAddress}/incoming-payments/1`

jest
.spyOn(openApi, 'createResponseValidator')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.mockImplementation(mockResponseValidator as any)

const publicIncomingPayment = mockPublicIncomingPayment()

const getSpy = jest
.spyOn(requestors, 'get')
.mockResolvedValueOnce(publicIncomingPayment)

await createUnauthenticatedIncomingPaymentRoutes({
openApi,
axiosInstance,
logger
}).get({ url })

expect(getSpy).toHaveBeenCalledWith(
{
axiosInstance,
logger
},
{ url },
true
)
})
})
})
})
54 changes: 53 additions & 1 deletion packages/open-payments/src/client/incoming-payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import {
BaseDeps,
CollectionRequestArgs,
ResourceRequestArgs,
RouteDeps
RouteDeps,
UnauthenticatedResourceRequestArgs
} from '.'
import {
IncomingPayment,
getRSPath,
CreateIncomingPaymentArgs,
PaginationArgs,
IncomingPaymentPaginationResult,
PublicIncomingPayment,
IncomingPaymentWithPaymentMethods
} from '../types'
import { get, post } from './requests'
Expand All @@ -19,6 +21,7 @@ type AnyIncomingPayment = IncomingPayment | IncomingPaymentWithPaymentMethods

export interface IncomingPaymentRoutes {
get(args: ResourceRequestArgs): Promise<IncomingPaymentWithPaymentMethods>
getPublic(args: ResourceRequestArgs): Promise<PublicIncomingPayment>
create(
args: CollectionRequestArgs,
createArgs: CreateIncomingPaymentArgs
Expand All @@ -41,6 +44,12 @@ export const createIncomingPaymentRoutes = (
method: HttpMethod.GET
})

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

const createIncomingPaymentOpenApiValidator =
openApi.createResponseValidator<IncomingPaymentWithPaymentMethods>({
path: getRSPath('/incoming-payments'),
Expand All @@ -66,6 +75,15 @@ export const createIncomingPaymentRoutes = (
args,
getIncomingPaymentOpenApiValidator
),
getPublic: (args: ResourceRequestArgs) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { accessToken, ...argsWithoutAccessToken } = args
return getPublicIncomingPayment(
{ axiosInstance, logger },
argsWithoutAccessToken,
getPublicIncomingPaymentOpenApiValidator
)
},
create: (
requestArgs: CollectionRequestArgs,
createArgs: CreateIncomingPaymentArgs
Expand All @@ -92,6 +110,31 @@ export const createIncomingPaymentRoutes = (
}
}

export interface UnauthenticatedIncomingPaymentRoutes {
get(args: UnauthenticatedResourceRequestArgs): Promise<PublicIncomingPayment>
}

export const createUnauthenticatedIncomingPaymentRoutes = (
deps: RouteDeps
): UnauthenticatedIncomingPaymentRoutes => {
const { axiosInstance, openApi, logger } = deps

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

return {
get: (args: UnauthenticatedResourceRequestArgs) =>
getPublicIncomingPayment(
{ axiosInstance, logger },
args,
getPublicIncomingPaymentOpenApiValidator
)
}
}

export const getIncomingPayment = async (
deps: BaseDeps,
args: ResourceRequestArgs,
Expand Down Expand Up @@ -119,6 +162,15 @@ export const getIncomingPayment = async (
}
}

export const getPublicIncomingPayment = async (
deps: BaseDeps,
args: UnauthenticatedResourceRequestArgs,
validateOpenApiResponse: ResponseValidator<PublicIncomingPayment>
) => {
const { axiosInstance, logger } = deps
return await get({ axiosInstance, logger }, args, validateOpenApiResponse)
}

export const createIncomingPayment = async (
deps: BaseDeps,
requestArgs: CollectionRequestArgs,
Expand Down
10 changes: 9 additions & 1 deletion packages/open-payments/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import createLogger, { Logger } from 'pino'
import config from '../config'
import {
createIncomingPaymentRoutes,
IncomingPaymentRoutes
createUnauthenticatedIncomingPaymentRoutes,
IncomingPaymentRoutes,
UnauthenticatedIncomingPaymentRoutes
} from './incoming-payment'
import {
createWalletAddressRoutes,
Expand Down Expand Up @@ -107,6 +109,7 @@ export interface CreateUnauthenticatedClientArgs {

export interface UnauthenticatedClient {
walletAddress: WalletAddressRoutes
incomingPayment: UnauthenticatedIncomingPaymentRoutes
}

/**
Expand All @@ -124,6 +127,11 @@ export const createUnauthenticatedClient = async (
axiosInstance,
openApi: resourceServerOpenApi,
logger
}),
incomingPayment: createUnauthenticatedIncomingPaymentRoutes({
axiosInstance,
openApi: resourceServerOpenApi,
logger
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export interface components {
* @description An **incoming payment** resource with public details.
*/
"public-incoming-payment": {
receiveAmount?: external["schemas.yaml"]["components"]["schemas"]["amount"];
receivedAmount?: external["schemas.yaml"]["components"]["schemas"]["amount"];
};
/**
* Outgoing Payment
Expand Down
14 changes: 13 additions & 1 deletion packages/open-payments/src/test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
PendingGrant,
Grant,
IncomingPaymentWithPaymentMethods,
IlpPaymentMethod
IlpPaymentMethod,
PublicIncomingPayment
} from '../types'
import { v4 as uuid } from 'uuid'
import { ResponseValidator } from '@interledger/openapi'
Expand Down Expand Up @@ -122,6 +123,17 @@ export const mockIncomingPaymentWithPaymentMethods = (
...overrides
})

export const mockPublicIncomingPayment = (
overrides?: Partial<PublicIncomingPayment>
): PublicIncomingPayment => ({
receivedAmount: {
assetCode: 'USD',
assetScale: 2,
value: '0'
},
...overrides
})

export const mockIlpPaymentMethod = (
overrides?: Partial<IlpPaymentMethod>
): IlpPaymentMethod => ({
Expand Down
2 changes: 2 additions & 0 deletions packages/open-payments/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const getRSPath = <P extends keyof RSPaths>(path: P): string =>
path as string

export type IncomingPayment = RSComponents['schemas']['incoming-payment']
export type PublicIncomingPayment =
RSComponents['schemas']['public-incoming-payment']

export type IlpPaymentMethod = RSComponents['schemas']['ilp-payment-method']
type PaymentMethods = IlpPaymentMethod
Expand Down

0 comments on commit f756fed

Please sign in to comment.