diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Approve Incoming Payment.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Approve Incoming Payment.bru new file mode 100644 index 0000000000..d13c7f02cf --- /dev/null +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Approve Incoming Payment.bru @@ -0,0 +1,35 @@ +meta { + name: Approve Incoming Payment + type: graphql + seq: 47 +} + +post { + url: {{PeerGraphqlHost}}/graphql + body: graphql + auth: none +} + +body:graphql { + mutation ApproveIncomingPayment($input: ApproveIncomingPaymentInput!) { + approveIncomingPayment(input:$input) { + payment { + id + } + } + } +} + +body:graphql:vars { + { + "input": { + "id": "{{incomingPaymentId}}" + } + } +} + +script:pre-request { + const scripts = require('./scripts'); + + scripts.addApiSignatureHeader(); +} diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Cancel Incoming Payment.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Cancel Incoming Payment.bru new file mode 100644 index 0000000000..775e508187 --- /dev/null +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Cancel Incoming Payment.bru @@ -0,0 +1,36 @@ +meta { + name: Cancel Incoming Payment + type: graphql + seq: 48 +} + +post { + url: {{PeerGraphqlHost}}/graphql + body: graphql + auth: none +} + +body:graphql { + mutation CancelIncomingPayment($input: CancelIncomingPaymentInput!) { + cancelIncomingPayment(input: $input) { + payment { + id + } + } + } + +} + +body:graphql:vars { + { + "input": { + "id": "{{incomingPaymentId}}" + } + } +} + +script:pre-request { + const scripts = require('./scripts'); + + scripts.addApiSignatureHeader(); +} diff --git a/localenv/mock-account-servicing-entity/generated/graphql.ts b/localenv/mock-account-servicing-entity/generated/graphql.ts index 55a68afa65..1acc448110 100644 --- a/localenv/mock-account-servicing-entity/generated/graphql.ts +++ b/localenv/mock-account-servicing-entity/generated/graphql.ts @@ -77,6 +77,16 @@ export type AmountInput = { value: Scalars['UInt64']['input']; }; +export type ApproveIncomingPaymentInput = { + /** Unique identifier of the incoming payment to be approved. Note: Incoming Payment must be PENDING. */ + id: Scalars['ID']['input']; +}; + +export type ApproveIncomingPaymentResponse = { + __typename?: 'ApproveIncomingPaymentResponse'; + payment?: Maybe; +}; + export type Asset = Model & { __typename?: 'Asset'; /** [ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217), e.g. `USD` */ @@ -135,6 +145,16 @@ export type BasePayment = { walletAddressId: Scalars['ID']['output']; }; +export type CancelIncomingPaymentInput = { + /** Unique identifier of the incoming payment to be cancelled. Note: Incoming Payment must be PENDING. */ + id: Scalars['ID']['input']; +}; + +export type CancelIncomingPaymentResponse = { + __typename?: 'CancelIncomingPaymentResponse'; + payment?: Maybe; +}; + export type CancelOutgoingPaymentInput = { /** Outgoing payment id */ id: Scalars['ID']['input']; @@ -607,6 +627,10 @@ export type Model = { export type Mutation = { __typename?: 'Mutation'; + /** Approves the incoming payment if the incoming payment is in the PENDING state */ + approveIncomingPayment: ApproveIncomingPaymentResponse; + /** Cancel the incoming payment if the incoming payment is in the PENDING state */ + cancelIncomingPayment: CancelIncomingPaymentResponse; /** Cancel Outgoing Payment */ cancelOutgoingPayment: OutgoingPaymentResponse; /** Create an asset */ @@ -678,6 +702,16 @@ export type Mutation = { }; +export type MutationApproveIncomingPaymentArgs = { + input: ApproveIncomingPaymentInput; +}; + + +export type MutationCancelIncomingPaymentArgs = { + input: CancelIncomingPaymentInput; +}; + + export type MutationCancelOutgoingPaymentArgs = { input: CancelOutgoingPaymentInput; }; @@ -1523,12 +1557,16 @@ export type ResolversTypes = { Alg: ResolverTypeWrapper>; Amount: ResolverTypeWrapper>; AmountInput: ResolverTypeWrapper>; + ApproveIncomingPaymentInput: ResolverTypeWrapper>; + ApproveIncomingPaymentResponse: ResolverTypeWrapper>; Asset: ResolverTypeWrapper>; AssetEdge: ResolverTypeWrapper>; AssetMutationResponse: ResolverTypeWrapper>; AssetsConnection: ResolverTypeWrapper>; BasePayment: ResolverTypeWrapper['BasePayment']>; Boolean: ResolverTypeWrapper>; + CancelIncomingPaymentInput: ResolverTypeWrapper>; + CancelIncomingPaymentResponse: ResolverTypeWrapper>; CancelOutgoingPaymentInput: ResolverTypeWrapper>; CreateAssetInput: ResolverTypeWrapper>; CreateAssetLiquidityWithdrawalInput: ResolverTypeWrapper>; @@ -1648,12 +1686,16 @@ export type ResolversParentTypes = { AdditionalPropertyInput: Partial; Amount: Partial; AmountInput: Partial; + ApproveIncomingPaymentInput: Partial; + ApproveIncomingPaymentResponse: Partial; Asset: Partial; AssetEdge: Partial; AssetMutationResponse: Partial; AssetsConnection: Partial; BasePayment: ResolversInterfaceTypes['BasePayment']; Boolean: Partial; + CancelIncomingPaymentInput: Partial; + CancelIncomingPaymentResponse: Partial; CancelOutgoingPaymentInput: Partial; CreateAssetInput: Partial; CreateAssetLiquidityWithdrawalInput: Partial; @@ -1786,6 +1828,11 @@ export type AmountResolvers; }; +export type ApproveIncomingPaymentResponseResolvers = { + payment?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type AssetResolvers = { code?: Resolver; createdAt?: Resolver; @@ -1826,6 +1873,11 @@ export type BasePaymentResolvers; }; +export type CancelIncomingPaymentResponseResolvers = { + payment?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type CreateOrUpdatePeerByUrlMutationResponseResolvers = { peer?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -1950,6 +2002,8 @@ export type ModelResolvers = { + approveIncomingPayment?: Resolver>; + cancelIncomingPayment?: Resolver>; cancelOutgoingPayment?: Resolver>; createAsset?: Resolver>; createAssetLiquidityWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; @@ -2253,11 +2307,13 @@ export type Resolvers = { AccountingTransferConnection?: AccountingTransferConnectionResolvers; AdditionalProperty?: AdditionalPropertyResolvers; Amount?: AmountResolvers; + ApproveIncomingPaymentResponse?: ApproveIncomingPaymentResponseResolvers; Asset?: AssetResolvers; AssetEdge?: AssetEdgeResolvers; AssetMutationResponse?: AssetMutationResponseResolvers; AssetsConnection?: AssetsConnectionResolvers; BasePayment?: BasePaymentResolvers; + CancelIncomingPaymentResponse?: CancelIncomingPaymentResponseResolvers; CreateOrUpdatePeerByUrlMutationResponse?: CreateOrUpdatePeerByUrlMutationResponseResolvers; CreatePeerMutationResponse?: CreatePeerMutationResponseResolvers; CreateReceiverResponse?: CreateReceiverResponseResolvers; diff --git a/packages/backend/migrations/20240729210134_incoming_payment_cancel_approved_timestamp.js b/packages/backend/migrations/20240729210134_incoming_payment_cancel_approved_timestamp.js new file mode 100644 index 0000000000..248eaebb46 --- /dev/null +++ b/packages/backend/migrations/20240729210134_incoming_payment_cancel_approved_timestamp.js @@ -0,0 +1,21 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function (knex) { + return knex.schema.alterTable('incomingPayments', (table) => { + table.timestamp('approvedAt').nullable() + table.timestamp('cancelledAt').nullable() + }) +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) { + return knex.schema.alterTable('incomingPayments', (table) => { + table.dropColumn('approvedAt') + table.dropColumn('cancelledAt') + }) +} diff --git a/packages/backend/src/config/app.ts b/packages/backend/src/config/app.ts index a2a77684c8..29a3c7070a 100644 --- a/packages/backend/src/config/app.ts +++ b/packages/backend/src/config/app.ts @@ -132,6 +132,18 @@ export const Config = { incomingPaymentWorkers: envInt('INCOMING_PAYMENT_WORKERS', 1), incomingPaymentWorkerIdle: envInt('INCOMING_PAYMENT_WORKER_IDLE', 200), // milliseconds + pollIncomingPaymentCreatedWebhook: envBool( + 'POLL_INCOMING_PAYMENT_CREATED_WEBHOOK', + false + ), + incomingPaymentCreatedPollTimeout: envInt( + 'INCOMING_PAYMENT_CREATED_POLL_TIMEOUT_MS', + 10000 + ), // milliseconds + incomingPaymentCreatedPollFrequency: envInt( + 'INCOMING_PAYMENT_CREATED_POLL_FREQUENCY_MS', + 1000 + ), // milliseconds webhookWorkers: envInt('WEBHOOK_WORKERS', 1), webhookWorkerIdle: envInt('WEBHOOK_WORKER_IDLE', 200), // milliseconds diff --git a/packages/backend/src/graphql/errors/index.ts b/packages/backend/src/graphql/errors/index.ts index e19601440a..b9e8530317 100644 --- a/packages/backend/src/graphql/errors/index.ts +++ b/packages/backend/src/graphql/errors/index.ts @@ -5,5 +5,6 @@ export enum GraphQLErrorCode { Inactive = 'INACTIVE', InternalServerError = 'INTERNAL_SERVER_ERROR', NotFound = 'NOT_FOUND', - Conflict = 'CONFLICT' + Conflict = 'CONFLICT', + Timeout = 'TIMEOUT' } diff --git a/packages/backend/src/graphql/generated/graphql.schema.json b/packages/backend/src/graphql/generated/graphql.schema.json index 58c79a704c..afae163e2e 100644 --- a/packages/backend/src/graphql/generated/graphql.schema.json +++ b/packages/backend/src/graphql/generated/graphql.schema.json @@ -449,6 +449,56 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "ApproveIncomingPaymentInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "id", + "description": "Unique identifier of the incoming payment to be approved. Note: Incoming Payment must be PENDING.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ApproveIncomingPaymentResponse", + "description": null, + "fields": [ + { + "name": "payment", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "IncomingPayment", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "Asset", @@ -889,6 +939,56 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "CancelIncomingPaymentInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "id", + "description": "Unique identifier of the incoming payment to be cancelled. Note: Incoming Payment must be PENDING.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "CancelIncomingPaymentResponse", + "description": null, + "fields": [ + { + "name": "payment", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "IncomingPayment", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "CancelOutgoingPaymentInput", @@ -3882,6 +3982,72 @@ "name": "Mutation", "description": null, "fields": [ + { + "name": "approveIncomingPayment", + "description": "Approves the incoming payment if the incoming payment is in the PENDING state", + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "ApproveIncomingPaymentInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ApproveIncomingPaymentResponse", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cancelIncomingPayment", + "description": "Cancel the incoming payment if the incoming payment is in the PENDING state", + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "CancelIncomingPaymentInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "CancelIncomingPaymentResponse", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "cancelOutgoingPayment", "description": "Cancel Outgoing Payment", diff --git a/packages/backend/src/graphql/generated/graphql.ts b/packages/backend/src/graphql/generated/graphql.ts index 55a68afa65..1acc448110 100644 --- a/packages/backend/src/graphql/generated/graphql.ts +++ b/packages/backend/src/graphql/generated/graphql.ts @@ -77,6 +77,16 @@ export type AmountInput = { value: Scalars['UInt64']['input']; }; +export type ApproveIncomingPaymentInput = { + /** Unique identifier of the incoming payment to be approved. Note: Incoming Payment must be PENDING. */ + id: Scalars['ID']['input']; +}; + +export type ApproveIncomingPaymentResponse = { + __typename?: 'ApproveIncomingPaymentResponse'; + payment?: Maybe; +}; + export type Asset = Model & { __typename?: 'Asset'; /** [ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217), e.g. `USD` */ @@ -135,6 +145,16 @@ export type BasePayment = { walletAddressId: Scalars['ID']['output']; }; +export type CancelIncomingPaymentInput = { + /** Unique identifier of the incoming payment to be cancelled. Note: Incoming Payment must be PENDING. */ + id: Scalars['ID']['input']; +}; + +export type CancelIncomingPaymentResponse = { + __typename?: 'CancelIncomingPaymentResponse'; + payment?: Maybe; +}; + export type CancelOutgoingPaymentInput = { /** Outgoing payment id */ id: Scalars['ID']['input']; @@ -607,6 +627,10 @@ export type Model = { export type Mutation = { __typename?: 'Mutation'; + /** Approves the incoming payment if the incoming payment is in the PENDING state */ + approveIncomingPayment: ApproveIncomingPaymentResponse; + /** Cancel the incoming payment if the incoming payment is in the PENDING state */ + cancelIncomingPayment: CancelIncomingPaymentResponse; /** Cancel Outgoing Payment */ cancelOutgoingPayment: OutgoingPaymentResponse; /** Create an asset */ @@ -678,6 +702,16 @@ export type Mutation = { }; +export type MutationApproveIncomingPaymentArgs = { + input: ApproveIncomingPaymentInput; +}; + + +export type MutationCancelIncomingPaymentArgs = { + input: CancelIncomingPaymentInput; +}; + + export type MutationCancelOutgoingPaymentArgs = { input: CancelOutgoingPaymentInput; }; @@ -1523,12 +1557,16 @@ export type ResolversTypes = { Alg: ResolverTypeWrapper>; Amount: ResolverTypeWrapper>; AmountInput: ResolverTypeWrapper>; + ApproveIncomingPaymentInput: ResolverTypeWrapper>; + ApproveIncomingPaymentResponse: ResolverTypeWrapper>; Asset: ResolverTypeWrapper>; AssetEdge: ResolverTypeWrapper>; AssetMutationResponse: ResolverTypeWrapper>; AssetsConnection: ResolverTypeWrapper>; BasePayment: ResolverTypeWrapper['BasePayment']>; Boolean: ResolverTypeWrapper>; + CancelIncomingPaymentInput: ResolverTypeWrapper>; + CancelIncomingPaymentResponse: ResolverTypeWrapper>; CancelOutgoingPaymentInput: ResolverTypeWrapper>; CreateAssetInput: ResolverTypeWrapper>; CreateAssetLiquidityWithdrawalInput: ResolverTypeWrapper>; @@ -1648,12 +1686,16 @@ export type ResolversParentTypes = { AdditionalPropertyInput: Partial; Amount: Partial; AmountInput: Partial; + ApproveIncomingPaymentInput: Partial; + ApproveIncomingPaymentResponse: Partial; Asset: Partial; AssetEdge: Partial; AssetMutationResponse: Partial; AssetsConnection: Partial; BasePayment: ResolversInterfaceTypes['BasePayment']; Boolean: Partial; + CancelIncomingPaymentInput: Partial; + CancelIncomingPaymentResponse: Partial; CancelOutgoingPaymentInput: Partial; CreateAssetInput: Partial; CreateAssetLiquidityWithdrawalInput: Partial; @@ -1786,6 +1828,11 @@ export type AmountResolvers; }; +export type ApproveIncomingPaymentResponseResolvers = { + payment?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type AssetResolvers = { code?: Resolver; createdAt?: Resolver; @@ -1826,6 +1873,11 @@ export type BasePaymentResolvers; }; +export type CancelIncomingPaymentResponseResolvers = { + payment?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type CreateOrUpdatePeerByUrlMutationResponseResolvers = { peer?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -1950,6 +2002,8 @@ export type ModelResolvers = { + approveIncomingPayment?: Resolver>; + cancelIncomingPayment?: Resolver>; cancelOutgoingPayment?: Resolver>; createAsset?: Resolver>; createAssetLiquidityWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; @@ -2253,11 +2307,13 @@ export type Resolvers = { AccountingTransferConnection?: AccountingTransferConnectionResolvers; AdditionalProperty?: AdditionalPropertyResolvers; Amount?: AmountResolvers; + ApproveIncomingPaymentResponse?: ApproveIncomingPaymentResponseResolvers; Asset?: AssetResolvers; AssetEdge?: AssetEdgeResolvers; AssetMutationResponse?: AssetMutationResponseResolvers; AssetsConnection?: AssetsConnectionResolvers; BasePayment?: BasePaymentResolvers; + CancelIncomingPaymentResponse?: CancelIncomingPaymentResponseResolvers; CreateOrUpdatePeerByUrlMutationResponse?: CreateOrUpdatePeerByUrlMutationResponseResolvers; CreatePeerMutationResponse?: CreatePeerMutationResponseResolvers; CreateReceiverResponse?: CreateReceiverResponseResolvers; diff --git a/packages/backend/src/graphql/resolvers/incoming_payment.ts b/packages/backend/src/graphql/resolvers/incoming_payment.ts index cde669d166..0931c553f2 100644 --- a/packages/backend/src/graphql/resolvers/incoming_payment.ts +++ b/packages/backend/src/graphql/resolvers/incoming_payment.ts @@ -108,6 +108,60 @@ export const createIncomingPayment: MutationResolvers['createInco } } +export const approveIncomingPayment: MutationResolvers['approveIncomingPayment'] = + async ( + parent, + args, + ctx + ): Promise => { + const incomingPaymentService = await ctx.container.use( + 'incomingPaymentService' + ) + + const incomingPaymentOrError = await incomingPaymentService.approve( + args.input.id + ) + + if (isIncomingPaymentError(incomingPaymentOrError)) { + throw new GraphQLError(errorToMessage[incomingPaymentOrError], { + extensions: { + code: errorToCode[incomingPaymentOrError] + } + }) + } + + return { + payment: paymentToGraphql(incomingPaymentOrError) + } + } + +export const cancelIncomingPayment: MutationResolvers['cancelIncomingPayment'] = + async ( + parent, + args, + ctx + ): Promise => { + const incomingPaymentService = await ctx.container.use( + 'incomingPaymentService' + ) + + const incomingPaymentOrError = await incomingPaymentService.cancel( + args.input.id + ) + + if (isIncomingPaymentError(incomingPaymentOrError)) { + throw new GraphQLError(errorToMessage[incomingPaymentOrError], { + extensions: { + code: errorToCode[incomingPaymentOrError] + } + }) + } + + return { + payment: paymentToGraphql(incomingPaymentOrError) + } + } + export function paymentToGraphql( payment: IncomingPayment ): SchemaIncomingPayment { diff --git a/packages/backend/src/graphql/resolvers/index.ts b/packages/backend/src/graphql/resolvers/index.ts index c4f5c6f5ec..1cbfd9285e 100644 --- a/packages/backend/src/graphql/resolvers/index.ts +++ b/packages/backend/src/graphql/resolvers/index.ts @@ -19,7 +19,9 @@ import { import { getWalletAddressIncomingPayments, createIncomingPayment, - getIncomingPayment + getIncomingPayment, + approveIncomingPayment, + cancelIncomingPayment } from './incoming_payment' import { getQuote, createQuote, getWalletAddressQuotes } from './quote' import { @@ -124,6 +126,8 @@ export const resolvers: Resolvers = { createOutgoingPaymentFromIncomingPayment, cancelOutgoingPayment, createIncomingPayment, + approveIncomingPayment, + cancelIncomingPayment, createReceiver, createPeer: createPeer, createOrUpdatePeerByUrl: createOrUpdatePeerByUrl, diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index ed137ba407..ddf249f8fd 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -244,6 +244,16 @@ type Mutation { "Set the fee on an asset" setFee(input: SetFeeInput!): SetFeeResponse! + + "Approves the incoming payment if the incoming payment is in the PENDING state" + approveIncomingPayment( + input: ApproveIncomingPaymentInput! + ): ApproveIncomingPaymentResponse! + + "Cancel the incoming payment if the incoming payment is in the PENDING state" + cancelIncomingPayment( + input: CancelIncomingPaymentInput! + ): CancelIncomingPaymentResponse! } type PageInfo { @@ -267,6 +277,16 @@ type AssetEdge { cursor: String! } +input ApproveIncomingPaymentInput { + "Unique identifier of the incoming payment to be approved. Note: Incoming Payment must be PENDING." + id: ID! +} + +input CancelIncomingPaymentInput { + "Unique identifier of the incoming payment to be cancelled. Note: Incoming Payment must be PENDING." + id: ID! +} + input CreateAssetInput { "[ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217), e.g. `USD`" code: String! @@ -1286,6 +1306,14 @@ type SetFeeResponse { fee: Fee } +type ApproveIncomingPaymentResponse { + payment: IncomingPayment +} + +type CancelIncomingPaymentResponse { + payment: IncomingPayment +} + scalar UInt8 scalar UInt64 scalar JSONObject diff --git a/packages/backend/src/open_payments/payment/incoming/errors.ts b/packages/backend/src/open_payments/payment/incoming/errors.ts index 6d6ea90bbc..96a90f8d13 100644 --- a/packages/backend/src/open_payments/payment/incoming/errors.ts +++ b/packages/backend/src/open_payments/payment/incoming/errors.ts @@ -7,7 +7,9 @@ export enum IncomingPaymentError { InvalidState = 'InvalidState', InvalidExpiry = 'InvalidExpiry', WrongState = 'WrongState', - InactiveWalletAddress = 'InactiveWalletAddress' + InactiveWalletAddress = 'InactiveWalletAddress', + ActionNotPerformed = 'ActionNotPerformed', + AlreadyActioned = 'AlreadyActioned' } // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types @@ -23,7 +25,9 @@ export const errorToHTTPCode: { [IncomingPaymentError.InvalidState]: 400, [IncomingPaymentError.InvalidExpiry]: 400, [IncomingPaymentError.WrongState]: 409, - [IncomingPaymentError.InactiveWalletAddress]: 400 + [IncomingPaymentError.InactiveWalletAddress]: 400, + [IncomingPaymentError.ActionNotPerformed]: 403, + [IncomingPaymentError.AlreadyActioned]: 403 } export const errorToCode: { @@ -35,7 +39,9 @@ export const errorToCode: { [IncomingPaymentError.InvalidState]: GraphQLErrorCode.BadUserInput, [IncomingPaymentError.InvalidExpiry]: GraphQLErrorCode.BadUserInput, [IncomingPaymentError.WrongState]: GraphQLErrorCode.Conflict, - [IncomingPaymentError.InactiveWalletAddress]: GraphQLErrorCode.Inactive + [IncomingPaymentError.InactiveWalletAddress]: GraphQLErrorCode.Inactive, + [IncomingPaymentError.ActionNotPerformed]: GraphQLErrorCode.Timeout, + [IncomingPaymentError.AlreadyActioned]: GraphQLErrorCode.Forbidden } export const errorToMessage: { @@ -47,5 +53,7 @@ export const errorToMessage: { [IncomingPaymentError.InvalidState]: 'invalid state', [IncomingPaymentError.InvalidExpiry]: 'invalid expiresAt', [IncomingPaymentError.WrongState]: 'wrong state', - [IncomingPaymentError.InactiveWalletAddress]: 'inactive wallet address' + [IncomingPaymentError.InactiveWalletAddress]: 'inactive wallet address', + [IncomingPaymentError.ActionNotPerformed]: 'action not performed', + [IncomingPaymentError.AlreadyActioned]: 'action already performed' } diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index 26c854fbae..b22f086af7 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -46,6 +46,8 @@ export interface IncomingPaymentResponse { receivedAmount: AmountJSON completed: boolean metadata?: Record + approvedAt?: string + cancelledAt?: string } export type IncomingPaymentData = IncomingPaymentResponse & @@ -100,6 +102,8 @@ export class IncomingPayment public metadata?: Record public processAt!: Date | null + public approvedAt?: Date | null + public cancelledAt?: Date | null public readonly assetId!: string public asset!: Asset @@ -206,6 +210,12 @@ export class IncomingPayment if (this.metadata) { data.metadata = this.metadata } + if (this.approvedAt) { + data.approvedAt = new Date(this.approvedAt).toISOString() + } + if (this.cancelledAt) { + data.cancelledAt = new Date(this.cancelledAt).toISOString() + } return data } diff --git a/packages/backend/src/open_payments/payment/incoming/service.test.ts b/packages/backend/src/open_payments/payment/incoming/service.test.ts index 710056ba40..5b2e98df1e 100644 --- a/packages/backend/src/open_payments/payment/incoming/service.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/service.test.ts @@ -25,6 +25,8 @@ import { IncomingPaymentError, isIncomingPaymentError } from './errors' import { Amount } from '../../amount' import { getTests } from '../../wallet_address/model.test' import { WalletAddress } from '../../wallet_address/model' +import { withConfigOverride } from '../../../tests/helpers' +import { sleep } from '../../../shared/utils' describe('Incoming Payment Service', (): void => { let deps: IocContract @@ -62,6 +64,224 @@ describe('Incoming Payment Service', (): void => { await appContainer.shutdown() }) + describe('Actionable IncomingPayment', (): void => { + function actionableIncomingPaymentConfigOverride() { + return { + pollIncomingPaymentCreatedWebhook: true, + incomingPaymentCreatedPollFrequency: 1, + incomingPaymentCreatedPollTimeout: 100 + } + } + async function patchIncomingPaymentHelper(options: { + approvedAt?: Date + cancelledAt?: Date + }) { + await sleep(50) + const incomingPaymentEvent = await IncomingPaymentEvent.query( + knex + ).findOne({ + type: IncomingPaymentEventType.IncomingPaymentCreated + }) + assert.ok(!!incomingPaymentEvent) + await IncomingPayment.query(knex) + .findById(incomingPaymentEvent.incomingPaymentId as string) + .patch(options) + + return incomingPaymentService.get({ + id: incomingPaymentEvent.incomingPaymentId as string + }) + } + + function createIncomingPaymentHelper(): Promise< + IncomingPayment | IncomingPaymentError + > { + const options = { + client: faker.internet.url({ appendSlash: false }), + incomingAmount: true, + expiresAt: new Date(Date.now() + 30_000) + } + + return incomingPaymentService.create({ + walletAddressId, + ...options, + incomingAmount: undefined + }) + } + + test( + 'should return cancelled incoming payment', + withConfigOverride( + () => config, + actionableIncomingPaymentConfigOverride(), + async (): Promise => { + const options = { cancelledAt: new Date(Date.now() - 1) } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [incomingPayment, _] = await Promise.all([ + createIncomingPaymentHelper(), + patchIncomingPaymentHelper(options) + ]) + + assert.ok(isIncomingPaymentError(incomingPayment)) + expect(incomingPayment).toBe(IncomingPaymentError.ActionNotPerformed) + } + ) + ) + + test( + 'should return approved incoming payment', + withConfigOverride( + () => config, + actionableIncomingPaymentConfigOverride(), + async (): Promise => { + const options = { approvedAt: new Date(Date.now() - 1) } + const [incomingPayment, approvedIncomingPayment] = await Promise.all([ + createIncomingPaymentHelper(), + patchIncomingPaymentHelper(options) + ]) + + assert.ok(!isIncomingPaymentError(incomingPayment)) + expect(incomingPayment.id).toEqual(approvedIncomingPayment?.id) + expect(incomingPayment.approvedAt).toEqual(options.approvedAt) + expect(!incomingPayment.cancelledAt).toBeTruthy() + } + ) + ) + test( + 'should return ActionNotPerformed Error if no action taken', + withConfigOverride( + () => config, + actionableIncomingPaymentConfigOverride(), + async (): Promise => { + await expect( + IncomingPaymentEvent.query(knex).where({ + type: IncomingPaymentEventType.IncomingPaymentCreated + }) + ).resolves.toHaveLength(0) + + const incomingPayment = await createIncomingPaymentHelper() + + assert.ok(isIncomingPaymentError(incomingPayment)) + expect(incomingPayment).toBe(IncomingPaymentError.ActionNotPerformed) + } + ) + ) + + describe('approveIncomingPayment', (): void => { + it('should return UnknownPayment error if payment does not exist', async (): Promise => { + expect(incomingPaymentService.approve(uuid())).resolves.toBe( + IncomingPaymentError.UnknownPayment + ) + }) + + it('should not approve already cancelled incoming payment', async (): Promise => { + const incomingPayment = await createIncomingPaymentHelper() + assert.ok(!isIncomingPaymentError(incomingPayment)) + + await IncomingPayment.query(knex) + .findOne({ id: incomingPayment.id }) + .patch({ cancelledAt: new Date() }) + + const response = await incomingPaymentService.approve( + incomingPayment.id + ) + expect(response).toBe(IncomingPaymentError.AlreadyActioned) + }) + + it('should not update approvedAt field of already approved incoming payment', async (): Promise => { + const approvedAt = new Date() + const incomingPayment = await createIncomingPaymentHelper() + assert.ok(!isIncomingPaymentError(incomingPayment)) + + await IncomingPayment.query(knex) + .findOne({ id: incomingPayment.id }) + .patch({ approvedAt }) + + const approvedPayment = await incomingPaymentService.approve( + incomingPayment.id + ) + assert.ok(!isIncomingPaymentError(approvedPayment)) + + expect(approvedPayment.approvedAt?.toISOString()).toBe( + approvedAt.toISOString() + ) + }) + + it('should approve incoming payment', async (): Promise => { + const incomingPayment = await createIncomingPaymentHelper() + assert.ok(!isIncomingPaymentError(incomingPayment)) + + await IncomingPayment.query(knex) + .findOne({ id: incomingPayment.id }) + .patch({ state: IncomingPaymentState.Pending }) + + const approvedIncomingPayment = await incomingPaymentService.approve( + incomingPayment.id + ) + assert.ok(!isIncomingPaymentError(approvedIncomingPayment)) + expect(approvedIncomingPayment.id).toBe(incomingPayment.id) + expect(approvedIncomingPayment.approvedAt).toBeDefined() + expect(!approvedIncomingPayment.cancelledAt).toBeTruthy() + expect(approvedIncomingPayment.cancelledAt).toBeFalsy() + }) + }) + + describe('cancelIncomingPayment', (): void => { + it('should return UnknownPayment error if payment does not exist', async (): Promise => { + expect(incomingPaymentService.cancel(uuid())).resolves.toBe( + IncomingPaymentError.UnknownPayment + ) + }) + + it('should not cancel already approved incoming payment', async (): Promise => { + const incomingPayment = await createIncomingPaymentHelper() + assert.ok(!isIncomingPaymentError(incomingPayment)) + + await IncomingPayment.query(knex) + .findOne({ id: incomingPayment.id }) + .patch({ approvedAt: new Date() }) + + const response = await incomingPaymentService.cancel(incomingPayment.id) + expect(response).toBe(IncomingPaymentError.AlreadyActioned) + }) + + it('should not update cancelledAt field of already cancelled incoming payment', async (): Promise => { + const cancelledAt = new Date() + const incomingPayment = await createIncomingPaymentHelper() + assert.ok(!isIncomingPaymentError(incomingPayment)) + + await IncomingPayment.query(knex) + .findOne({ id: incomingPayment.id }) + .patch({ cancelledAt }) + + const cancelledPayment = await incomingPaymentService.cancel( + incomingPayment.id + ) + assert.ok(!isIncomingPaymentError(cancelledPayment)) + + expect(cancelledPayment.cancelledAt?.toISOString()).toBe( + cancelledAt.toISOString() + ) + }) + + it('should cancel incoming payment', async (): Promise => { + const incomingPayment = await createIncomingPaymentHelper() + assert.ok(!isIncomingPaymentError(incomingPayment)) + + await IncomingPayment.query(knex) + .findOne({ id: incomingPayment.id }) + .patch({ state: IncomingPaymentState.Pending }) + + const canceledIncomingPayment = await incomingPaymentService.cancel( + incomingPayment.id + ) + assert.ok(!isIncomingPaymentError(canceledIncomingPayment)) + expect(canceledIncomingPayment.id).toBe(incomingPayment.id) + expect(canceledIncomingPayment.cancelledAt).toBeDefined() + expect(!canceledIncomingPayment.approvedAt).toBeTruthy() + }) + }) + }) + describe('Create IncomingPayment', (): void => { let amount: Amount diff --git a/packages/backend/src/open_payments/payment/incoming/service.ts b/packages/backend/src/open_payments/payment/incoming/service.ts index afad128bbe..6d7bd48e0a 100644 --- a/packages/backend/src/open_payments/payment/incoming/service.ts +++ b/packages/backend/src/open_payments/payment/incoming/service.ts @@ -17,6 +17,7 @@ import { import { Amount } from '../../amount' import { IncomingPaymentError } from './errors' import { IAppConfig } from '../../../config/app' +import { poll } from '../../../shared/utils' export const POSITIVE_SLIPPAGE = BigInt(1) // First retry waits 10 seconds @@ -38,6 +39,8 @@ export interface IncomingPaymentService options: CreateIncomingPaymentOptions, trx?: Knex.Transaction ): Promise + approve(id: string): Promise + cancel(id: string): Promise complete(id: string): Promise processNext(): Promise } @@ -62,6 +65,8 @@ export async function createIncomingPaymentService( return { get: (options) => getIncomingPayment(deps, options), create: (options, trx) => createIncomingPayment(deps, options, trx), + approve: (id) => approveIncomingPayment(deps, id), + cancel: (id) => cancelIncomingPayment(deps, id), complete: (id) => completeIncomingPayment(deps, id), getWalletAddressPage: (options) => getWalletAddressPage(deps, options), processNext: () => processNextIncomingPayment(deps) @@ -116,7 +121,8 @@ async function createIncomingPayment( return IncomingPaymentError.InvalidAmount } } - const incomingPayment = await IncomingPayment.query(trx || deps.knex) + + let incomingPayment = await IncomingPayment.query(trx || deps.knex) .insertAndFetch({ walletAddressId: walletAddressId, client, @@ -135,7 +141,55 @@ async function createIncomingPayment( data: incomingPayment.toData(0n) }) - return await addReceivedAmount(deps, incomingPayment, BigInt(0)) + incomingPayment = await addReceivedAmount(deps, incomingPayment, BigInt(0)) + if (!deps.config.pollIncomingPaymentCreatedWebhook) { + return incomingPayment + } + + try { + const response = await poll({ + request: async () => + getApprovedOrCanceledIncomingPayment(deps, { id: incomingPayment.id }), + pollingFrequencyMs: deps.config.incomingPaymentCreatedPollFrequency, + timeoutMs: deps.config.incomingPaymentCreatedPollTimeout + }) + + if (response?.cancelledAt) { + deps.logger.info( + { + cancelledAt: response.cancelledAt.toISOString(), + paymentId: incomingPayment.id + }, + 'Incoming payment was cancelled' + ) + return IncomingPaymentError.ActionNotPerformed + } + + if (response?.approvedAt) return response + deps.logger.error( + { response }, + 'Got response, but incoming payment is not approved or cancelled' + ) + return IncomingPaymentError.ActionNotPerformed + } catch (err) { + const errorMessage = 'Got error / timeout while polling incoming payment' + deps.logger.error( + { errorMessage: err instanceof Error && err.message }, + errorMessage + ) + return IncomingPaymentError.ActionNotPerformed + } +} + +async function getApprovedOrCanceledIncomingPayment( + deps: ServiceDependencies, + options: GetOptions +) { + return IncomingPayment.query(deps.knex) + .get(options) + .withGraphFetched('[asset, walletAddress]') + .whereNotNull('approvedAt') + .orWhereNotNull('cancelledAt') } // Fetch (and lock) an incoming payment for work. @@ -278,6 +332,70 @@ async function getWalletAddressPage( }) } +async function approveIncomingPayment( + deps: ServiceDependencies, + id: string +): Promise { + return deps.knex.transaction(async (trx) => { + const payment = await IncomingPayment.query(trx) + .findById(id) + .forUpdate() + .withGraphFetched('[asset, walletAddress]') + + if (!payment) return IncomingPaymentError.UnknownPayment + if (payment.state !== IncomingPaymentState.Pending) + return IncomingPaymentError.WrongState + + if (payment.cancelledAt) { + deps.logger.info({ + errorMessage: 'Cannot approve already cancelled incoming payment', + paymentId: payment.id + }) + return IncomingPaymentError.AlreadyActioned + } + + if (!payment.approvedAt) { + await payment.$query(trx).patch({ + approvedAt: new Date(Date.now()) + }) + } + + return await addReceivedAmount(deps, payment) + }) +} + +async function cancelIncomingPayment( + deps: ServiceDependencies, + id: string +): Promise { + return deps.knex.transaction(async (trx) => { + const payment = await IncomingPayment.query(trx) + .findById(id) + .forUpdate() + .withGraphFetched('[asset, walletAddress]') + + if (!payment) return IncomingPaymentError.UnknownPayment + if (payment.state !== IncomingPaymentState.Pending) + return IncomingPaymentError.WrongState + + if (payment.approvedAt) { + deps.logger.info({ + errorMessage: 'Cannot cancel already approved incoming payment', + paymentId: payment.id + }) + return IncomingPaymentError.AlreadyActioned + } + + if (!payment.cancelledAt) { + await payment.$query(trx).patch({ + cancelledAt: new Date(Date.now()) + }) + } + + return await addReceivedAmount(deps, payment) + }) +} + async function completeIncomingPayment( deps: ServiceDependencies, id: string diff --git a/packages/frontend/app/generated/graphql.ts b/packages/frontend/app/generated/graphql.ts index db27546a81..e8e28e3baa 100644 --- a/packages/frontend/app/generated/graphql.ts +++ b/packages/frontend/app/generated/graphql.ts @@ -77,6 +77,16 @@ export type AmountInput = { value: Scalars['UInt64']['input']; }; +export type ApproveIncomingPaymentInput = { + /** Unique identifier of the incoming payment to be approved. Note: Incoming Payment must be PENDING. */ + id: Scalars['ID']['input']; +}; + +export type ApproveIncomingPaymentResponse = { + __typename?: 'ApproveIncomingPaymentResponse'; + payment?: Maybe; +}; + export type Asset = Model & { __typename?: 'Asset'; /** [ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217), e.g. `USD` */ @@ -135,6 +145,16 @@ export type BasePayment = { walletAddressId: Scalars['ID']['output']; }; +export type CancelIncomingPaymentInput = { + /** Unique identifier of the incoming payment to be cancelled. Note: Incoming Payment must be PENDING. */ + id: Scalars['ID']['input']; +}; + +export type CancelIncomingPaymentResponse = { + __typename?: 'CancelIncomingPaymentResponse'; + payment?: Maybe; +}; + export type CancelOutgoingPaymentInput = { /** Outgoing payment id */ id: Scalars['ID']['input']; @@ -607,6 +627,10 @@ export type Model = { export type Mutation = { __typename?: 'Mutation'; + /** Approves the incoming payment if the incoming payment is in the PENDING state */ + approveIncomingPayment: ApproveIncomingPaymentResponse; + /** Cancel the incoming payment if the incoming payment is in the PENDING state */ + cancelIncomingPayment: CancelIncomingPaymentResponse; /** Cancel Outgoing Payment */ cancelOutgoingPayment: OutgoingPaymentResponse; /** Create an asset */ @@ -678,6 +702,16 @@ export type Mutation = { }; +export type MutationApproveIncomingPaymentArgs = { + input: ApproveIncomingPaymentInput; +}; + + +export type MutationCancelIncomingPaymentArgs = { + input: CancelIncomingPaymentInput; +}; + + export type MutationCancelOutgoingPaymentArgs = { input: CancelOutgoingPaymentInput; }; @@ -1523,12 +1557,16 @@ export type ResolversTypes = { Alg: ResolverTypeWrapper>; Amount: ResolverTypeWrapper>; AmountInput: ResolverTypeWrapper>; + ApproveIncomingPaymentInput: ResolverTypeWrapper>; + ApproveIncomingPaymentResponse: ResolverTypeWrapper>; Asset: ResolverTypeWrapper>; AssetEdge: ResolverTypeWrapper>; AssetMutationResponse: ResolverTypeWrapper>; AssetsConnection: ResolverTypeWrapper>; BasePayment: ResolverTypeWrapper['BasePayment']>; Boolean: ResolverTypeWrapper>; + CancelIncomingPaymentInput: ResolverTypeWrapper>; + CancelIncomingPaymentResponse: ResolverTypeWrapper>; CancelOutgoingPaymentInput: ResolverTypeWrapper>; CreateAssetInput: ResolverTypeWrapper>; CreateAssetLiquidityWithdrawalInput: ResolverTypeWrapper>; @@ -1648,12 +1686,16 @@ export type ResolversParentTypes = { AdditionalPropertyInput: Partial; Amount: Partial; AmountInput: Partial; + ApproveIncomingPaymentInput: Partial; + ApproveIncomingPaymentResponse: Partial; Asset: Partial; AssetEdge: Partial; AssetMutationResponse: Partial; AssetsConnection: Partial; BasePayment: ResolversInterfaceTypes['BasePayment']; Boolean: Partial; + CancelIncomingPaymentInput: Partial; + CancelIncomingPaymentResponse: Partial; CancelOutgoingPaymentInput: Partial; CreateAssetInput: Partial; CreateAssetLiquidityWithdrawalInput: Partial; @@ -1786,6 +1828,11 @@ export type AmountResolvers; }; +export type ApproveIncomingPaymentResponseResolvers = { + payment?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type AssetResolvers = { code?: Resolver; createdAt?: Resolver; @@ -1826,6 +1873,11 @@ export type BasePaymentResolvers; }; +export type CancelIncomingPaymentResponseResolvers = { + payment?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type CreateOrUpdatePeerByUrlMutationResponseResolvers = { peer?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -1950,6 +2002,8 @@ export type ModelResolvers = { + approveIncomingPayment?: Resolver>; + cancelIncomingPayment?: Resolver>; cancelOutgoingPayment?: Resolver>; createAsset?: Resolver>; createAssetLiquidityWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; @@ -2253,11 +2307,13 @@ export type Resolvers = { AccountingTransferConnection?: AccountingTransferConnectionResolvers; AdditionalProperty?: AdditionalPropertyResolvers; Amount?: AmountResolvers; + ApproveIncomingPaymentResponse?: ApproveIncomingPaymentResponseResolvers; Asset?: AssetResolvers; AssetEdge?: AssetEdgeResolvers; AssetMutationResponse?: AssetMutationResponseResolvers; AssetsConnection?: AssetsConnectionResolvers; BasePayment?: BasePaymentResolvers; + CancelIncomingPaymentResponse?: CancelIncomingPaymentResponseResolvers; CreateOrUpdatePeerByUrlMutationResponse?: CreateOrUpdatePeerByUrlMutationResponseResolvers; CreatePeerMutationResponse?: CreatePeerMutationResponseResolvers; CreateReceiverResponse?: CreateReceiverResponseResolvers; diff --git a/packages/mock-account-service-lib/src/generated/graphql.ts b/packages/mock-account-service-lib/src/generated/graphql.ts index 55a68afa65..1acc448110 100644 --- a/packages/mock-account-service-lib/src/generated/graphql.ts +++ b/packages/mock-account-service-lib/src/generated/graphql.ts @@ -77,6 +77,16 @@ export type AmountInput = { value: Scalars['UInt64']['input']; }; +export type ApproveIncomingPaymentInput = { + /** Unique identifier of the incoming payment to be approved. Note: Incoming Payment must be PENDING. */ + id: Scalars['ID']['input']; +}; + +export type ApproveIncomingPaymentResponse = { + __typename?: 'ApproveIncomingPaymentResponse'; + payment?: Maybe; +}; + export type Asset = Model & { __typename?: 'Asset'; /** [ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217), e.g. `USD` */ @@ -135,6 +145,16 @@ export type BasePayment = { walletAddressId: Scalars['ID']['output']; }; +export type CancelIncomingPaymentInput = { + /** Unique identifier of the incoming payment to be cancelled. Note: Incoming Payment must be PENDING. */ + id: Scalars['ID']['input']; +}; + +export type CancelIncomingPaymentResponse = { + __typename?: 'CancelIncomingPaymentResponse'; + payment?: Maybe; +}; + export type CancelOutgoingPaymentInput = { /** Outgoing payment id */ id: Scalars['ID']['input']; @@ -607,6 +627,10 @@ export type Model = { export type Mutation = { __typename?: 'Mutation'; + /** Approves the incoming payment if the incoming payment is in the PENDING state */ + approveIncomingPayment: ApproveIncomingPaymentResponse; + /** Cancel the incoming payment if the incoming payment is in the PENDING state */ + cancelIncomingPayment: CancelIncomingPaymentResponse; /** Cancel Outgoing Payment */ cancelOutgoingPayment: OutgoingPaymentResponse; /** Create an asset */ @@ -678,6 +702,16 @@ export type Mutation = { }; +export type MutationApproveIncomingPaymentArgs = { + input: ApproveIncomingPaymentInput; +}; + + +export type MutationCancelIncomingPaymentArgs = { + input: CancelIncomingPaymentInput; +}; + + export type MutationCancelOutgoingPaymentArgs = { input: CancelOutgoingPaymentInput; }; @@ -1523,12 +1557,16 @@ export type ResolversTypes = { Alg: ResolverTypeWrapper>; Amount: ResolverTypeWrapper>; AmountInput: ResolverTypeWrapper>; + ApproveIncomingPaymentInput: ResolverTypeWrapper>; + ApproveIncomingPaymentResponse: ResolverTypeWrapper>; Asset: ResolverTypeWrapper>; AssetEdge: ResolverTypeWrapper>; AssetMutationResponse: ResolverTypeWrapper>; AssetsConnection: ResolverTypeWrapper>; BasePayment: ResolverTypeWrapper['BasePayment']>; Boolean: ResolverTypeWrapper>; + CancelIncomingPaymentInput: ResolverTypeWrapper>; + CancelIncomingPaymentResponse: ResolverTypeWrapper>; CancelOutgoingPaymentInput: ResolverTypeWrapper>; CreateAssetInput: ResolverTypeWrapper>; CreateAssetLiquidityWithdrawalInput: ResolverTypeWrapper>; @@ -1648,12 +1686,16 @@ export type ResolversParentTypes = { AdditionalPropertyInput: Partial; Amount: Partial; AmountInput: Partial; + ApproveIncomingPaymentInput: Partial; + ApproveIncomingPaymentResponse: Partial; Asset: Partial; AssetEdge: Partial; AssetMutationResponse: Partial; AssetsConnection: Partial; BasePayment: ResolversInterfaceTypes['BasePayment']; Boolean: Partial; + CancelIncomingPaymentInput: Partial; + CancelIncomingPaymentResponse: Partial; CancelOutgoingPaymentInput: Partial; CreateAssetInput: Partial; CreateAssetLiquidityWithdrawalInput: Partial; @@ -1786,6 +1828,11 @@ export type AmountResolvers; }; +export type ApproveIncomingPaymentResponseResolvers = { + payment?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type AssetResolvers = { code?: Resolver; createdAt?: Resolver; @@ -1826,6 +1873,11 @@ export type BasePaymentResolvers; }; +export type CancelIncomingPaymentResponseResolvers = { + payment?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type CreateOrUpdatePeerByUrlMutationResponseResolvers = { peer?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -1950,6 +2002,8 @@ export type ModelResolvers = { + approveIncomingPayment?: Resolver>; + cancelIncomingPayment?: Resolver>; cancelOutgoingPayment?: Resolver>; createAsset?: Resolver>; createAssetLiquidityWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; @@ -2253,11 +2307,13 @@ export type Resolvers = { AccountingTransferConnection?: AccountingTransferConnectionResolvers; AdditionalProperty?: AdditionalPropertyResolvers; Amount?: AmountResolvers; + ApproveIncomingPaymentResponse?: ApproveIncomingPaymentResponseResolvers; Asset?: AssetResolvers; AssetEdge?: AssetEdgeResolvers; AssetMutationResponse?: AssetMutationResponseResolvers; AssetsConnection?: AssetsConnectionResolvers; BasePayment?: BasePaymentResolvers; + CancelIncomingPaymentResponse?: CancelIncomingPaymentResponseResolvers; CreateOrUpdatePeerByUrlMutationResponse?: CreateOrUpdatePeerByUrlMutationResponseResolvers; CreatePeerMutationResponse?: CreatePeerMutationResponseResolvers; CreateReceiverResponse?: CreateReceiverResponseResolvers; diff --git a/test/integration/lib/generated/graphql.ts b/test/integration/lib/generated/graphql.ts index 55a68afa65..1acc448110 100644 --- a/test/integration/lib/generated/graphql.ts +++ b/test/integration/lib/generated/graphql.ts @@ -77,6 +77,16 @@ export type AmountInput = { value: Scalars['UInt64']['input']; }; +export type ApproveIncomingPaymentInput = { + /** Unique identifier of the incoming payment to be approved. Note: Incoming Payment must be PENDING. */ + id: Scalars['ID']['input']; +}; + +export type ApproveIncomingPaymentResponse = { + __typename?: 'ApproveIncomingPaymentResponse'; + payment?: Maybe; +}; + export type Asset = Model & { __typename?: 'Asset'; /** [ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217), e.g. `USD` */ @@ -135,6 +145,16 @@ export type BasePayment = { walletAddressId: Scalars['ID']['output']; }; +export type CancelIncomingPaymentInput = { + /** Unique identifier of the incoming payment to be cancelled. Note: Incoming Payment must be PENDING. */ + id: Scalars['ID']['input']; +}; + +export type CancelIncomingPaymentResponse = { + __typename?: 'CancelIncomingPaymentResponse'; + payment?: Maybe; +}; + export type CancelOutgoingPaymentInput = { /** Outgoing payment id */ id: Scalars['ID']['input']; @@ -607,6 +627,10 @@ export type Model = { export type Mutation = { __typename?: 'Mutation'; + /** Approves the incoming payment if the incoming payment is in the PENDING state */ + approveIncomingPayment: ApproveIncomingPaymentResponse; + /** Cancel the incoming payment if the incoming payment is in the PENDING state */ + cancelIncomingPayment: CancelIncomingPaymentResponse; /** Cancel Outgoing Payment */ cancelOutgoingPayment: OutgoingPaymentResponse; /** Create an asset */ @@ -678,6 +702,16 @@ export type Mutation = { }; +export type MutationApproveIncomingPaymentArgs = { + input: ApproveIncomingPaymentInput; +}; + + +export type MutationCancelIncomingPaymentArgs = { + input: CancelIncomingPaymentInput; +}; + + export type MutationCancelOutgoingPaymentArgs = { input: CancelOutgoingPaymentInput; }; @@ -1523,12 +1557,16 @@ export type ResolversTypes = { Alg: ResolverTypeWrapper>; Amount: ResolverTypeWrapper>; AmountInput: ResolverTypeWrapper>; + ApproveIncomingPaymentInput: ResolverTypeWrapper>; + ApproveIncomingPaymentResponse: ResolverTypeWrapper>; Asset: ResolverTypeWrapper>; AssetEdge: ResolverTypeWrapper>; AssetMutationResponse: ResolverTypeWrapper>; AssetsConnection: ResolverTypeWrapper>; BasePayment: ResolverTypeWrapper['BasePayment']>; Boolean: ResolverTypeWrapper>; + CancelIncomingPaymentInput: ResolverTypeWrapper>; + CancelIncomingPaymentResponse: ResolverTypeWrapper>; CancelOutgoingPaymentInput: ResolverTypeWrapper>; CreateAssetInput: ResolverTypeWrapper>; CreateAssetLiquidityWithdrawalInput: ResolverTypeWrapper>; @@ -1648,12 +1686,16 @@ export type ResolversParentTypes = { AdditionalPropertyInput: Partial; Amount: Partial; AmountInput: Partial; + ApproveIncomingPaymentInput: Partial; + ApproveIncomingPaymentResponse: Partial; Asset: Partial; AssetEdge: Partial; AssetMutationResponse: Partial; AssetsConnection: Partial; BasePayment: ResolversInterfaceTypes['BasePayment']; Boolean: Partial; + CancelIncomingPaymentInput: Partial; + CancelIncomingPaymentResponse: Partial; CancelOutgoingPaymentInput: Partial; CreateAssetInput: Partial; CreateAssetLiquidityWithdrawalInput: Partial; @@ -1786,6 +1828,11 @@ export type AmountResolvers; }; +export type ApproveIncomingPaymentResponseResolvers = { + payment?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type AssetResolvers = { code?: Resolver; createdAt?: Resolver; @@ -1826,6 +1873,11 @@ export type BasePaymentResolvers; }; +export type CancelIncomingPaymentResponseResolvers = { + payment?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type CreateOrUpdatePeerByUrlMutationResponseResolvers = { peer?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -1950,6 +2002,8 @@ export type ModelResolvers = { + approveIncomingPayment?: Resolver>; + cancelIncomingPayment?: Resolver>; cancelOutgoingPayment?: Resolver>; createAsset?: Resolver>; createAssetLiquidityWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; @@ -2253,11 +2307,13 @@ export type Resolvers = { AccountingTransferConnection?: AccountingTransferConnectionResolvers; AdditionalProperty?: AdditionalPropertyResolvers; Amount?: AmountResolvers; + ApproveIncomingPaymentResponse?: ApproveIncomingPaymentResponseResolvers; Asset?: AssetResolvers; AssetEdge?: AssetEdgeResolvers; AssetMutationResponse?: AssetMutationResponseResolvers; AssetsConnection?: AssetsConnectionResolvers; BasePayment?: BasePaymentResolvers; + CancelIncomingPaymentResponse?: CancelIncomingPaymentResponseResolvers; CreateOrUpdatePeerByUrlMutationResponse?: CreateOrUpdatePeerByUrlMutationResponseResolvers; CreatePeerMutationResponse?: CreatePeerMutationResponseResolvers; CreateReceiverResponse?: CreateReceiverResponseResolvers;