diff --git a/bruno/collections/Rafiki/Sample Webhook Events/Asset Liquidity Low.bru b/bruno/collections/Rafiki/Sample Webhook Events/Asset Liquidity Low.bru index a20ffb085f..45b8adfbc2 100644 --- a/bruno/collections/Rafiki/Sample Webhook Events/Asset Liquidity Low.bru +++ b/bruno/collections/Rafiki/Sample Webhook Events/Asset Liquidity Low.bru @@ -13,7 +13,7 @@ post { body:json { { "id": "{{uuid}}", - "type": "asset.liquidity_low", + "type": "ASSET_LIQUIDITY_LOW", "data": { "id": "{{assetId}}", "asset": { diff --git a/bruno/collections/Rafiki/Sample Webhook Events/Incoming Payment Completed.bru b/bruno/collections/Rafiki/Sample Webhook Events/Incoming Payment Completed.bru index eddb038073..32a5892f89 100644 --- a/bruno/collections/Rafiki/Sample Webhook Events/Incoming Payment Completed.bru +++ b/bruno/collections/Rafiki/Sample Webhook Events/Incoming Payment Completed.bru @@ -13,7 +13,7 @@ post { body:json { { "id": "{{uuid}}", - "type": "incoming_payment.completed", + "type": "INCOMING_PAYMENT_COMPLETED", "data": { "id": "0141ee62-1c0f-4a57-9231-9e515a7cdffb", "walletAddressId": "{{gfranklinWalletAddressId}}", diff --git a/bruno/collections/Rafiki/Sample Webhook Events/Incoming Payment Created.bru b/bruno/collections/Rafiki/Sample Webhook Events/Incoming Payment Created.bru index 9056280e66..f9d14992ec 100644 --- a/bruno/collections/Rafiki/Sample Webhook Events/Incoming Payment Created.bru +++ b/bruno/collections/Rafiki/Sample Webhook Events/Incoming Payment Created.bru @@ -13,7 +13,7 @@ post { body:json { { "id": "{{uuid}}", - "type": "incoming_payment.created", + "type": "INCOMING_PAYMENT_CREATED", "data": { "id": "0141ee62-1c0f-4a57-9231-9e515a7cdffb", "walletAddressId": "{{gfranklinWalletAddressId}}", diff --git a/bruno/collections/Rafiki/Sample Webhook Events/Incoming Payment Expired.bru b/bruno/collections/Rafiki/Sample Webhook Events/Incoming Payment Expired.bru index 4f6741860b..d2bb0b2c63 100644 --- a/bruno/collections/Rafiki/Sample Webhook Events/Incoming Payment Expired.bru +++ b/bruno/collections/Rafiki/Sample Webhook Events/Incoming Payment Expired.bru @@ -13,7 +13,7 @@ post { body:json { { "id": "{{uuid}}", - "type": "incoming_payment.expired", + "type": "INCOMING_PAYMENT_EXPIRED", "data": { "id": "0141ee62-1c0f-4a57-9231-9e515a7cdffb", "walletAddressId": "{{gfranklinWalletAddressId}}", diff --git a/bruno/collections/Rafiki/Sample Webhook Events/Outgoing Payment Completed.bru b/bruno/collections/Rafiki/Sample Webhook Events/Outgoing Payment Completed.bru index 7fef08deeb..668be4efa9 100644 --- a/bruno/collections/Rafiki/Sample Webhook Events/Outgoing Payment Completed.bru +++ b/bruno/collections/Rafiki/Sample Webhook Events/Outgoing Payment Completed.bru @@ -13,7 +13,7 @@ post { body:json { { "id": "{{uuid}}", - "type": "outgoing_payment.created", + "type": "OUTGOING_PAYMENT_CREATED", "data": { "id": "2de34961-9bb8-404e-866a-288307114c74", "walletAddressId": "{{gfranklinWalletAddressId}}", diff --git a/bruno/collections/Rafiki/Sample Webhook Events/Outgoing Payment Created.bru b/bruno/collections/Rafiki/Sample Webhook Events/Outgoing Payment Created.bru index a54ecc4957..94a9655c75 100644 --- a/bruno/collections/Rafiki/Sample Webhook Events/Outgoing Payment Created.bru +++ b/bruno/collections/Rafiki/Sample Webhook Events/Outgoing Payment Created.bru @@ -13,7 +13,7 @@ post { body:json { { "id": "{{uuid}}", - "type": "outgoing_payment.completed", + "type": "OUTGOING_PAYMENT_COMPLETED", "data": { "id": "2de34961-9bb8-404e-866a-288307114c74", "walletAddressId": "{{gfranklinWalletAddressId}}", diff --git a/bruno/collections/Rafiki/Sample Webhook Events/Outgoing Payment Failed.bru b/bruno/collections/Rafiki/Sample Webhook Events/Outgoing Payment Failed.bru index d8cf4c8fa9..3df4d7bae9 100644 --- a/bruno/collections/Rafiki/Sample Webhook Events/Outgoing Payment Failed.bru +++ b/bruno/collections/Rafiki/Sample Webhook Events/Outgoing Payment Failed.bru @@ -13,7 +13,7 @@ post { body:json { { "id": "{{uuid}}", - "type": "outgoing_payment.failed", + "type": "OUTGOING_PAYMENT_FAILED", "data": { "id": "2de34961-9bb8-404e-866a-288307114c74", "walletAddressId": "{{gfranklinWalletAddressId}}", diff --git a/bruno/collections/Rafiki/Sample Webhook Events/Peer Liquidity Low.bru b/bruno/collections/Rafiki/Sample Webhook Events/Peer Liquidity Low.bru index 3f25aab70c..373a4fdae1 100644 --- a/bruno/collections/Rafiki/Sample Webhook Events/Peer Liquidity Low.bru +++ b/bruno/collections/Rafiki/Sample Webhook Events/Peer Liquidity Low.bru @@ -13,7 +13,7 @@ post { body:json { { "id": "{{uuid}}", - "type": "peer.liquidity_low", + "type": "PEER_LIQUIDITY_LOW", "data": { "id": "{{peerId}}", "asset": { diff --git a/bruno/collections/Rafiki/Sample Webhook Events/Wallet Address Not Found.bru b/bruno/collections/Rafiki/Sample Webhook Events/Wallet Address Not Found.bru index 2c25acaa02..4f34bb5659 100644 --- a/bruno/collections/Rafiki/Sample Webhook Events/Wallet Address Not Found.bru +++ b/bruno/collections/Rafiki/Sample Webhook Events/Wallet Address Not Found.bru @@ -13,7 +13,7 @@ post { body:json { { "id": "{{uuid}}", - "type":"wallet_address.not_found", + "type":"WALLET_ADDRESS_NOT_FOUND", "data": { "walletAddressUrl": "https://cloud-nine-wallet-backend/accounts/ya" } diff --git a/bruno/collections/Rafiki/Sample Webhook Events/Wallet Address Web Monetization.bru b/bruno/collections/Rafiki/Sample Webhook Events/Wallet Address Web Monetization.bru index 17b4c390e7..e952d291b6 100644 --- a/bruno/collections/Rafiki/Sample Webhook Events/Wallet Address Web Monetization.bru +++ b/bruno/collections/Rafiki/Sample Webhook Events/Wallet Address Web Monetization.bru @@ -13,7 +13,7 @@ post { body:json { { "id": "{{uuid}}", - "type": "wallet_address.web_monetization", + "type": "WALLET_ADDRESS_WEB_MONETIZATION", "data": { "walletAddress": { "id": "{{gfranklinWalletAddressId}}", diff --git a/localenv/mock-account-servicing-entity/app/lib/webhooks.server.ts b/localenv/mock-account-servicing-entity/app/lib/webhooks.server.ts index 72c44fd7fc..f93c4d1214 100644 --- a/localenv/mock-account-servicing-entity/app/lib/webhooks.server.ts +++ b/localenv/mock-account-servicing-entity/app/lib/webhooks.server.ts @@ -288,7 +288,7 @@ export async function handleLowLiquidity(wh: Webhook) { throw new Error('id not found') } - if (wh.type == 'asset.liquidity_low') { + if (wh.type == WebhookEventType.ASSET_LIQUIDITY_LOW) { await depositAssetLiquidity(id, 1000000, uuid()) } else { await depositPeerLiquidity(id, '1000000', uuid()) diff --git a/localenv/mock-account-servicing-entity/generated/graphql.ts b/localenv/mock-account-servicing-entity/generated/graphql.ts index 131de7eb44..21d71433a0 100644 --- a/localenv/mock-account-servicing-entity/generated/graphql.ts +++ b/localenv/mock-account-servicing-entity/generated/graphql.ts @@ -1580,7 +1580,7 @@ export type WebhookEvent = Model & { /** Unique identifier of the webhook event. */ id: Scalars['ID']['output']; /** Type of webhook event. */ - type: Scalars['String']['output']; + type: WebhookEventType; }; export type WebhookEventFilter = { @@ -1588,6 +1588,29 @@ export type WebhookEventFilter = { type?: InputMaybe; }; +export enum WebhookEventType { + /** Asset liquidity is low */ + AssetLiquidityLow = 'ASSET_LIQUIDITY_LOW', + /** An incoming payment was completed */ + IncomingPaymentCompleted = 'INCOMING_PAYMENT_COMPLETED', + /** An incoming payment was created */ + IncomingPaymentCreated = 'INCOMING_PAYMENT_CREATED', + /** An incoming payment has expired */ + IncomingPaymentExpired = 'INCOMING_PAYMENT_EXPIRED', + /** An outgoing payment was completed */ + OutgoingPaymentCompleted = 'OUTGOING_PAYMENT_COMPLETED', + /** An outgoing payment was created */ + OutgoingPaymentCreated = 'OUTGOING_PAYMENT_CREATED', + /** An outgoing payment has failed and won't be retried */ + OutgoingPaymentFailed = 'OUTGOING_PAYMENT_FAILED', + /** Peer liquidity is low */ + PeerLiquidityLow = 'PEER_LIQUIDITY_LOW', + /** Wallet address was not found and it will be created if there exists a corresponding account */ + WalletAddressNotFound = 'WALLET_ADDRESS_NOT_FOUND', + /** A Web Monetization payment was created */ + WalletAddressWebMonetization = 'WALLET_ADDRESS_WEB_MONETIZATION' +} + export type WebhookEventsConnection = { __typename?: 'WebhookEventsConnection'; /** A list of webhook event edges, containing event nodes and cursors for pagination. */ @@ -1812,6 +1835,7 @@ export type ResolversTypes = { WalletAddressesConnection: ResolverTypeWrapper>; WebhookEvent: ResolverTypeWrapper>; WebhookEventFilter: ResolverTypeWrapper>; + WebhookEventType: ResolverTypeWrapper>; WebhookEventsConnection: ResolverTypeWrapper>; WebhookEventsEdge: ResolverTypeWrapper>; WithdrawEventLiquidityInput: ResolverTypeWrapper>; @@ -2429,7 +2453,7 @@ export type WebhookEventResolvers; data?: Resolver; id?: Resolver; - type?: Resolver; + type?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/packages/backend/migrations/20241003113426_change_webhook_event_type_to_enum.js b/packages/backend/migrations/20241003113426_change_webhook_event_type_to_enum.js new file mode 100644 index 0000000000..2d1522503b --- /dev/null +++ b/packages/backend/migrations/20241003113426_change_webhook_event_type_to_enum.js @@ -0,0 +1,94 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function (knex) { + return knex + .raw( + 'ALTER TABLE "webhookEvents" DROP CONSTRAINT IF EXISTS "webhookevents_related_resource_constraint"' + ) + .then(() => { + // Update webhook event type to the enum standard + return knex.table('webhookEvents').update({ + type: knex.raw(`REPLACE(UPPER(type), '.', '_')`) + }) + }) + .then(() => { + // Update the constraint webhookevents_related_resource_constraint to match the enum change + return knex.table('webhookEvents', function (table) { + table.check( + ` (CASE WHEN type != 'WALLET_ADDRESS_NOT_FOUND' THEN + ( + ("outgoingPaymentId" IS NOT NULL)::int + + ("incomingPaymentId" IS NOT NULL)::int + + ("walletAddressId" IS NOT NULL)::int + + ("peerId" IS NOT NULL)::int + + ("assetId" IS NOT NULL)::int + ) = 1 + ELSE + ( + ("outgoingPaymentId" IS NOT NULL)::int + + ("incomingPaymentId" IS NOT NULL)::int + + ("walletAddressId" IS NOT NULL)::int + + ("peerId" IS NOT NULL)::int + + ("assetId" IS NOT NULL)::int + ) = 0 + END) + `, + null, + 'webhookevents_related_resource_constraint' + ) + }) + }) +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) { + return knex + .raw( + 'ALTER TABLE "webhookEvents" DROP CONSTRAINT IF EXISTS "webhookevents_related_resource_constraint"' + ) + .then(() => { + return knex.table('webhookEvents').update({ + type: knex.raw(`LOWER(type)`) + }) + }) + .then(() => { + return knex.raw(`UPDATE "webhookEvents" SET type = ( + CASE + WHEN type LIKE '%payment%' THEN REPLACE(type, 'payment_', 'payment.') + WHEN type LIKE 'wallet_address_%' THEN REPLACE(type, 'address_', 'address.') + WHEN type LIKE '%liquidity%' THEN REPLACE(type, '_liquidity', '.liquidity') + END + )`) + }) + .then(() => { + return knex.schema.table('webhookEvents', function (table) { + table.check( + ` (CASE WHEN type != 'wallet_address.not_found' THEN + ( + ("outgoingPaymentId" IS NOT NULL)::int + + ("incomingPaymentId" IS NOT NULL)::int + + ("walletAddressId" IS NOT NULL)::int + + ("peerId" IS NOT NULL)::int + + ("assetId" IS NOT NULL)::int + ) = 1 + ELSE + ( + ("outgoingPaymentId" IS NOT NULL)::int + + ("incomingPaymentId" IS NOT NULL)::int + + ("walletAddressId" IS NOT NULL)::int + + ("peerId" IS NOT NULL)::int + + ("assetId" IS NOT NULL)::int + ) = 0 + END) + `, + null, + 'webhookevents_related_resource_constraint' + ) + }) + }) +} diff --git a/packages/backend/src/asset/model.test.ts b/packages/backend/src/asset/model.test.ts index 461fb043e5..09b16e7b22 100644 --- a/packages/backend/src/asset/model.test.ts +++ b/packages/backend/src/asset/model.test.ts @@ -10,6 +10,7 @@ import { randomAsset } from '../tests/asset' import { truncateTables } from '../tests/tableManager' import { Asset, AssetEvent, AssetEventError, AssetEventType } from './model' import { isAssetError } from './errors' +import { WebhookEventType } from '../webhook/model' describe('Models', (): void => { let deps: IocContract @@ -57,11 +58,11 @@ describe('Models', (): void => { const event = ( await AssetEvent.query(knex).where( 'type', - AssetEventType.LiquidityLow + WebhookEventType.AssetLiquidityLow ) )[0] expect(event).toMatchObject({ - type: AssetEventType.LiquidityLow, + type: WebhookEventType.AssetLiquidityLow, data: { id: asset.id, asset: { @@ -78,7 +79,10 @@ describe('Models', (): void => { test('does not create webhook event if balance > liquidityThreshold', async (): Promise => { await asset.onDebit({ balance: BigInt(110) }) await expect( - AssetEvent.query(knex).where('type', AssetEventType.LiquidityLow) + AssetEvent.query(knex).where( + 'type', + WebhookEventType.AssetLiquidityLow + ) ).resolves.toEqual([]) }) }) @@ -94,7 +98,7 @@ describe('Models', (): void => { )('Asset Id is required', async ({ type, error }): Promise => { expect( AssetEvent.query().insert({ - type + type: type as AssetEventType }) ).rejects.toThrow(error) }) diff --git a/packages/backend/src/asset/model.ts b/packages/backend/src/asset/model.ts index 62237fcd20..bbef935d3d 100644 --- a/packages/backend/src/asset/model.ts +++ b/packages/backend/src/asset/model.ts @@ -1,7 +1,7 @@ import { QueryContext } from 'objection' import { LiquidityAccount, OnDebitOptions } from '../accounting/service' import { BaseModel } from '../shared/baseModel' -import { WebhookEvent } from '../webhook/model' +import { WebhookEvent, WebhookEventType } from '../webhook/model' export class Asset extends BaseModel implements LiquidityAccount { public static get tableName(): string { @@ -33,7 +33,7 @@ export class Asset extends BaseModel implements LiquidityAccount { if (balance <= this.liquidityThreshold) { await AssetEvent.query().insert({ assetId: this.id, - type: AssetEventType.LiquidityLow, + type: WebhookEventType.AssetLiquidityLow, data: { id: this.id, asset: { @@ -51,9 +51,11 @@ export class Asset extends BaseModel implements LiquidityAccount { } } -export enum AssetEventType { - LiquidityLow = 'asset.liquidity_low' -} +export type AssetEventType = Extract< + WebhookEventType, + WebhookEventType.AssetLiquidityLow +> +export const AssetEventType = [WebhookEventType.AssetLiquidityLow] export type AssetEventData = { id: string diff --git a/packages/backend/src/graphql/generated/graphql.schema.json b/packages/backend/src/graphql/generated/graphql.schema.json index 3f5f20485e..2b341f345f 100644 --- a/packages/backend/src/graphql/generated/graphql.schema.json +++ b/packages/backend/src/graphql/generated/graphql.schema.json @@ -8845,8 +8845,8 @@ "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "String", + "kind": "ENUM", + "name": "WebhookEventType", "ofType": null } }, @@ -8888,6 +8888,77 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "ENUM", + "name": "WebhookEventType", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "ASSET_LIQUIDITY_LOW", + "description": "Asset liquidity is low", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INCOMING_PAYMENT_COMPLETED", + "description": "An incoming payment was completed", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INCOMING_PAYMENT_CREATED", + "description": "An incoming payment was created", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INCOMING_PAYMENT_EXPIRED", + "description": "An incoming payment has expired", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OUTGOING_PAYMENT_COMPLETED", + "description": "An outgoing payment was completed", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OUTGOING_PAYMENT_CREATED", + "description": "An outgoing payment was created", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OUTGOING_PAYMENT_FAILED", + "description": "An outgoing payment has failed and won't be retried", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "PEER_LIQUIDITY_LOW", + "description": "Peer liquidity is low", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "WALLET_ADDRESS_NOT_FOUND", + "description": "Wallet address was not found and it will be created if there exists a corresponding account", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "WALLET_ADDRESS_WEB_MONETIZATION", + "description": "A Web Monetization payment was created", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, { "kind": "OBJECT", "name": "WebhookEventsConnection", diff --git a/packages/backend/src/graphql/generated/graphql.ts b/packages/backend/src/graphql/generated/graphql.ts index 131de7eb44..21d71433a0 100644 --- a/packages/backend/src/graphql/generated/graphql.ts +++ b/packages/backend/src/graphql/generated/graphql.ts @@ -1580,7 +1580,7 @@ export type WebhookEvent = Model & { /** Unique identifier of the webhook event. */ id: Scalars['ID']['output']; /** Type of webhook event. */ - type: Scalars['String']['output']; + type: WebhookEventType; }; export type WebhookEventFilter = { @@ -1588,6 +1588,29 @@ export type WebhookEventFilter = { type?: InputMaybe; }; +export enum WebhookEventType { + /** Asset liquidity is low */ + AssetLiquidityLow = 'ASSET_LIQUIDITY_LOW', + /** An incoming payment was completed */ + IncomingPaymentCompleted = 'INCOMING_PAYMENT_COMPLETED', + /** An incoming payment was created */ + IncomingPaymentCreated = 'INCOMING_PAYMENT_CREATED', + /** An incoming payment has expired */ + IncomingPaymentExpired = 'INCOMING_PAYMENT_EXPIRED', + /** An outgoing payment was completed */ + OutgoingPaymentCompleted = 'OUTGOING_PAYMENT_COMPLETED', + /** An outgoing payment was created */ + OutgoingPaymentCreated = 'OUTGOING_PAYMENT_CREATED', + /** An outgoing payment has failed and won't be retried */ + OutgoingPaymentFailed = 'OUTGOING_PAYMENT_FAILED', + /** Peer liquidity is low */ + PeerLiquidityLow = 'PEER_LIQUIDITY_LOW', + /** Wallet address was not found and it will be created if there exists a corresponding account */ + WalletAddressNotFound = 'WALLET_ADDRESS_NOT_FOUND', + /** A Web Monetization payment was created */ + WalletAddressWebMonetization = 'WALLET_ADDRESS_WEB_MONETIZATION' +} + export type WebhookEventsConnection = { __typename?: 'WebhookEventsConnection'; /** A list of webhook event edges, containing event nodes and cursors for pagination. */ @@ -1812,6 +1835,7 @@ export type ResolversTypes = { WalletAddressesConnection: ResolverTypeWrapper>; WebhookEvent: ResolverTypeWrapper>; WebhookEventFilter: ResolverTypeWrapper>; + WebhookEventType: ResolverTypeWrapper>; WebhookEventsConnection: ResolverTypeWrapper>; WebhookEventsEdge: ResolverTypeWrapper>; WithdrawEventLiquidityInput: ResolverTypeWrapper>; @@ -2429,7 +2453,7 @@ export type WebhookEventResolvers; data?: Resolver; id?: Resolver; - type?: Resolver; + type?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/packages/backend/src/graphql/resolvers/liquidity.test.ts b/packages/backend/src/graphql/resolvers/liquidity.test.ts index e0968b27e1..0fe56644e9 100644 --- a/packages/backend/src/graphql/resolvers/liquidity.test.ts +++ b/packages/backend/src/graphql/resolvers/liquidity.test.ts @@ -27,7 +27,6 @@ import { import { OutgoingPayment, OutgoingPaymentEvent, - OutgoingPaymentEventType, OutgoingPaymentWithdrawType, isOutgoingPaymentEventType } from '../../open_payments/payment/outgoing/model' @@ -38,7 +37,7 @@ import { createOutgoingPayment } from '../../tests/outgoingPayment' import { createWalletAddress } from '../../tests/walletAddress' import { createPeer } from '../../tests/peer' import { truncateTables } from '../../tests/tableManager' -import { WebhookEvent } from '../../webhook/model' +import { WebhookEvent, WebhookEventType } from '../../webhook/model' import { LiquidityMutationResponse, WalletAddressWithdrawalMutationResponse @@ -1775,27 +1774,64 @@ describe('Liquidity Resolvers', (): void => { }) describe('depositEventLiquidity', (): void => { - describe.each(Object.values(DepositEventType).map((type) => [type]))( - '%s', - (type): void => { - let eventId: string - - beforeEach(async (): Promise => { - eventId = uuid() - await OutgoingPaymentEvent.query(knex).insertAndFetch({ - id: eventId, - outgoingPaymentId: payment.id, - type, - data: payment.toData({ - amountSent: BigInt(0), - balance: BigInt(0) - }) + describe.each(DepositEventType)('%s', (type): void => { + let eventId: string + + beforeEach(async (): Promise => { + eventId = uuid() + await OutgoingPaymentEvent.query(knex).insertAndFetch({ + id: eventId, + outgoingPaymentId: payment.id, + type: type as DepositEventType, + data: payment.toData({ + amountSent: BigInt(0), + balance: BigInt(0) }) }) + }) - test('Can deposit account liquidity', async (): Promise => { - const depositSpy = jest.spyOn(accountingService, 'createDeposit') - const response = await appContainer.apolloClient + test('Can deposit account liquidity', async (): Promise => { + const depositSpy = jest.spyOn(accountingService, 'createDeposit') + const response = await appContainer.apolloClient + .mutate({ + mutation: gql` + mutation DepositLiquidity($input: DepositEventLiquidityInput!) { + depositEventLiquidity(input: $input) { + success + } + } + `, + variables: { + input: { + eventId, + idempotencyKey: uuid() + } + } + }) + .then((query): LiquidityMutationResponse => { + if (query.data) { + return query.data.depositEventLiquidity + } else { + throw new Error('Data was empty') + } + }) + + expect(response.success).toBe(true) + assert.ok(payment.debitAmount) + await expect(depositSpy).toHaveBeenCalledWith({ + id: eventId, + account: expect.any(OutgoingPayment), + amount: payment.debitAmount.value + }) + await expect( + accountingService.getBalance(payment.id) + ).resolves.toEqual(payment.debitAmount.value) + }) + + test("Can't deposit for non-existent webhook event id", async (): Promise => { + let error + try { + await appContainer.apolloClient .mutate({ mutation: gql` mutation DepositLiquidity( @@ -1808,7 +1844,7 @@ describe('Liquidity Resolvers', (): void => { `, variables: { input: { - eventId, + eventId: uuid(), idempotencyKey: uuid() } } @@ -1820,118 +1856,76 @@ describe('Liquidity Resolvers', (): void => { throw new Error('Data was empty') } }) + } catch (err) { + error = err + } + expect(error).toBeInstanceOf(ApolloError) + expect((error as ApolloError).graphQLErrors).toContainEqual( + expect.objectContaining({ + message: 'Invalid transfer id', + extensions: expect.objectContaining({ + code: GraphQLErrorCode.BadUserInput + }) + }) + ) + }) - expect(response.success).toBe(true) - assert.ok(payment.debitAmount) - await expect(depositSpy).toHaveBeenCalledWith({ + test('Returns an error for existing transfer', async (): Promise => { + await expect( + accountingService.createDeposit({ id: eventId, - account: expect.any(OutgoingPayment), - amount: payment.debitAmount.value + account: incomingPayment, + amount: BigInt(100) }) - await expect( - accountingService.getBalance(payment.id) - ).resolves.toEqual(payment.debitAmount.value) - }) - - test("Can't deposit for non-existent webhook event id", async (): Promise => { - let error - try { - await appContainer.apolloClient - .mutate({ - mutation: gql` - mutation DepositLiquidity( - $input: DepositEventLiquidityInput! - ) { - depositEventLiquidity(input: $input) { - success - } - } - `, - variables: { - input: { - eventId: uuid(), - idempotencyKey: uuid() + ).resolves.toBeUndefined() + let error + try { + await appContainer.apolloClient + .mutate({ + mutation: gql` + mutation DepositLiquidity( + $input: DepositEventLiquidityInput! + ) { + depositEventLiquidity(input: $input) { + success } } - }) - .then((query): LiquidityMutationResponse => { - if (query.data) { - return query.data.depositEventLiquidity - } else { - throw new Error('Data was empty') + `, + variables: { + input: { + eventId, + idempotencyKey: uuid() } - }) - } catch (err) { - error = err - } - expect(error).toBeInstanceOf(ApolloError) - expect((error as ApolloError).graphQLErrors).toContainEqual( - expect.objectContaining({ - message: 'Invalid transfer id', - extensions: expect.objectContaining({ - code: GraphQLErrorCode.BadUserInput - }) + } }) - ) - }) - - test('Returns an error for existing transfer', async (): Promise => { - await expect( - accountingService.createDeposit({ - id: eventId, - account: incomingPayment, - amount: BigInt(100) + .then((query): LiquidityMutationResponse => { + if (query.data) { + return query.data.depositEventLiquidity + } else { + throw new Error('Data was empty') + } }) - ).resolves.toBeUndefined() - let error - try { - await appContainer.apolloClient - .mutate({ - mutation: gql` - mutation DepositLiquidity( - $input: DepositEventLiquidityInput! - ) { - depositEventLiquidity(input: $input) { - success - } - } - `, - variables: { - input: { - eventId, - idempotencyKey: uuid() - } - } - }) - .then((query): LiquidityMutationResponse => { - if (query.data) { - return query.data.depositEventLiquidity - } else { - throw new Error('Data was empty') - } - }) - } catch (err) { - error = err - } - expect(error).toBeInstanceOf(ApolloError) - expect((error as ApolloError).graphQLErrors).toContainEqual( - expect.objectContaining({ - message: 'Transfer already exists', - extensions: expect.objectContaining({ - code: GraphQLErrorCode.Duplicate - }) + } catch (err) { + error = err + } + expect(error).toBeInstanceOf(ApolloError) + expect((error as ApolloError).graphQLErrors).toContainEqual( + expect.objectContaining({ + message: 'Transfer already exists', + extensions: expect.objectContaining({ + code: GraphQLErrorCode.Duplicate }) - ) - }) - } - ) + }) + ) + }) + }) }) - const WithdrawEventType = { + const WithdrawEventType = [ ...WalletAddressEventType, ...IncomingPaymentEventType, ...OutgoingPaymentWithdrawType - } + ] type WithdrawEventType = | WalletAddressEventType | IncomingPaymentEventType @@ -1958,79 +1952,109 @@ describe('Liquidity Resolvers', (): void => { Object.values(IncomingPaymentEventType).includes(o) describe('withdrawEventLiquidity', (): void => { - describe.each(Object.values(WithdrawEventType).map((type) => [type]))( - '%s', - (type): void => { - let eventId: string - let withdrawal: Withdrawal - - beforeEach(async (): Promise => { - eventId = uuid() - const amount = BigInt(10) - let liquidityAccount: LiquidityAccount - let data: Record - let resourceId: - | 'outgoingPaymentId' - | 'incomingPaymentId' - | 'walletAddressId' - | null = null - if (isOutgoingPaymentEventType(type)) { - liquidityAccount = payment - data = payment.toData({ - amountSent: BigInt(0), - balance: amount - }) - resourceId = 'outgoingPaymentId' - } else if (isIncomingPaymentEventType(type)) { - liquidityAccount = incomingPayment - data = incomingPayment.toData(amount) - resourceId = 'incomingPaymentId' - } else { - liquidityAccount = walletAddress - await accountingService.createLiquidityAccount( - walletAddress, - LiquidityAccountType.WEB_MONETIZATION - ) - data = walletAddress.toData(amount) - if (type !== WalletAddressEventType.WalletAddressNotFound) { - resourceId = 'walletAddressId' - } + describe.each(WithdrawEventType)('%s', (type): void => { + let eventId: string + let withdrawal: Withdrawal + + beforeEach(async (): Promise => { + eventId = uuid() + const amount = BigInt(10) + let liquidityAccount: LiquidityAccount + let data: Record + let resourceId: + | 'outgoingPaymentId' + | 'incomingPaymentId' + | 'walletAddressId' + | null = null + if (isOutgoingPaymentEventType(type)) { + liquidityAccount = payment + data = payment.toData({ + amountSent: BigInt(0), + balance: amount + }) + resourceId = 'outgoingPaymentId' + } else if (isIncomingPaymentEventType(type)) { + liquidityAccount = incomingPayment + data = incomingPayment.toData(amount) + resourceId = 'incomingPaymentId' + } else { + liquidityAccount = walletAddress + await accountingService.createLiquidityAccount( + walletAddress, + LiquidityAccountType.WEB_MONETIZATION + ) + data = walletAddress.toData(amount) + if (type !== WebhookEventType.WalletAddressNotFound) { + resourceId = 'walletAddressId' } - const insertPayload: WithdrawWebhookData = { - id: eventId, - type, - data, - withdrawal: { - accountId: liquidityAccount.id, - assetId: liquidityAccount.asset.id, - amount - } + } + const insertPayload: WithdrawWebhookData = { + id: eventId, + type: type as WithdrawEventType, + data, + withdrawal: { + accountId: liquidityAccount.id, + assetId: liquidityAccount.asset.id, + amount } + } - if (resourceId) { - insertPayload[resourceId] = liquidityAccount.id - } + if (resourceId) { + insertPayload[resourceId] = liquidityAccount.id + } - await WebhookEvent.query(knex).insertAndFetch(insertPayload) - await expect( - accountingService.createDeposit({ - id: uuid(), - account: liquidityAccount, - amount - }) - ).resolves.toBeUndefined() - await expect( - accountingService.getBalance(liquidityAccount.id) - ).resolves.toEqual(amount) - withdrawal = { - id: eventId, + await WebhookEvent.query(knex).insertAndFetch(insertPayload) + await expect( + accountingService.createDeposit({ + id: uuid(), account: liquidityAccount, amount - } - }) + }) + ).resolves.toBeUndefined() + await expect( + accountingService.getBalance(liquidityAccount.id) + ).resolves.toEqual(amount) + withdrawal = { + id: eventId, + account: liquidityAccount, + amount + } + }) - test('Can withdraw account liquidity', async (): Promise => { - const response = await appContainer.apolloClient + test('Can withdraw account liquidity', async (): Promise => { + const response = await appContainer.apolloClient + .mutate({ + mutation: gql` + mutation WithdrawLiquidity( + $input: WithdrawEventLiquidityInput! + ) { + withdrawEventLiquidity(input: $input) { + success + } + } + `, + variables: { + input: { + eventId, + idempotencyKey: uuid() + } + } + }) + .then((query): LiquidityMutationResponse => { + if (query.data) { + return query.data.withdrawEventLiquidity + } else { + throw new Error('Data was empty') + } + }) + + expect(response.success).toBe(true) + }) + + test('Returns error for non-existent webhook event id', async (): Promise => { + let error + try { + await appContainer.apolloClient .mutate({ mutation: gql` mutation WithdrawLiquidity( @@ -2043,7 +2067,7 @@ describe('Liquidity Resolvers', (): void => { `, variables: { input: { - eventId, + eventId: uuid(), idempotencyKey: uuid() } } @@ -2055,99 +2079,66 @@ describe('Liquidity Resolvers', (): void => { throw new Error('Data was empty') } }) + } catch (err) { + error = err + } + expect(error).toBeInstanceOf(ApolloError) + expect((error as ApolloError).graphQLErrors).toContainEqual( + expect.objectContaining({ + message: 'Invalid transfer id', + extensions: expect.objectContaining({ + code: GraphQLErrorCode.BadUserInput + }) + }) + ) + }) - expect(response.success).toBe(true) - }) + test('Returns error for already completed withdrawal', async (): Promise => { + await expect( + accountingService.createWithdrawal(withdrawal) + ).resolves.toBeUndefined() - test('Returns error for non-existent webhook event id', async (): Promise => { - let error - try { - await appContainer.apolloClient - .mutate({ - mutation: gql` - mutation WithdrawLiquidity( - $input: WithdrawEventLiquidityInput! - ) { - withdrawEventLiquidity(input: $input) { - success - } - } - `, - variables: { - input: { - eventId: uuid(), - idempotencyKey: uuid() + let error + try { + await appContainer.apolloClient + .mutate({ + mutation: gql` + mutation WithdrawLiquidity( + $input: WithdrawEventLiquidityInput! + ) { + withdrawEventLiquidity(input: $input) { + success } } - }) - .then((query): LiquidityMutationResponse => { - if (query.data) { - return query.data.withdrawEventLiquidity - } else { - throw new Error('Data was empty') + `, + variables: { + input: { + eventId, + idempotencyKey: uuid() } - }) - } catch (err) { - error = err - } - expect(error).toBeInstanceOf(ApolloError) - expect((error as ApolloError).graphQLErrors).toContainEqual( - expect.objectContaining({ - message: 'Invalid transfer id', - extensions: expect.objectContaining({ - code: GraphQLErrorCode.BadUserInput - }) + } }) - ) - }) - - test('Returns error for already completed withdrawal', async (): Promise => { - await expect( - accountingService.createWithdrawal(withdrawal) - ).resolves.toBeUndefined() - - let error - try { - await appContainer.apolloClient - .mutate({ - mutation: gql` - mutation WithdrawLiquidity( - $input: WithdrawEventLiquidityInput! - ) { - withdrawEventLiquidity(input: $input) { - success - } - } - `, - variables: { - input: { - eventId, - idempotencyKey: uuid() - } - } - }) - .then((query): LiquidityMutationResponse => { - if (query.data) { - return query.data.withdrawEventLiquidity - } else { - throw new Error('Data was empty') - } - }) - } catch (err) { - error = err - } - expect(error).toBeInstanceOf(ApolloError) - expect((error as ApolloError).graphQLErrors).toContainEqual( - expect.objectContaining({ - message: 'Transfer already exists', - extensions: expect.objectContaining({ - code: GraphQLErrorCode.Duplicate - }) + .then((query): LiquidityMutationResponse => { + if (query.data) { + return query.data.withdrawEventLiquidity + } else { + throw new Error('Data was empty') + } }) - ) - }) - } - ) + } catch (err) { + error = err + } + expect(error).toBeInstanceOf(ApolloError) + expect((error as ApolloError).graphQLErrors).toContainEqual( + expect.objectContaining({ + message: 'Transfer already exists', + extensions: expect.objectContaining({ + code: GraphQLErrorCode.Duplicate + }) + }) + ) + }) + }) }) }) @@ -2204,8 +2195,8 @@ describe('Liquidity Resolvers', (): void => { describe('Can withdraw liquidity', () => { test.each([ - IncomingPaymentEventType.IncomingPaymentCompleted, - IncomingPaymentEventType.IncomingPaymentExpired + WebhookEventType.IncomingPaymentCompleted, + WebhookEventType.IncomingPaymentExpired ])('for incoming payment event %s', async (eventType) => { const balance = await accountingService.getBalance(incomingPayment.id) assert.ok(balance === amount) @@ -2261,7 +2252,7 @@ describe('Liquidity Resolvers', (): void => { await WebhookEvent.query(knex).insert({ id: uuid(), incomingPaymentId: incomingPayment.id, - type: IncomingPaymentEventType.IncomingPaymentCompleted, + type: WebhookEventType.IncomingPaymentCompleted, data: {}, withdrawal: { accountId: incomingPayment.id, @@ -2359,7 +2350,7 @@ describe('Liquidity Resolvers', (): void => { await WebhookEvent.query(knex).insert({ id: eventId, incomingPaymentId: incomingPayment.id, - type: IncomingPaymentEventType.IncomingPaymentCompleted, + type: WebhookEventType.IncomingPaymentCompleted, data: {}, withdrawal: { accountId: incomingPayment.id, @@ -2436,57 +2427,59 @@ describe('Liquidity Resolvers', (): void => { }) describe('Can withdraw liquidity', () => { - test.each([ - OutgoingPaymentEventType.PaymentCompleted, - OutgoingPaymentEventType.PaymentFailed - ])('for outgoing payment event %s', async (eventType) => { - const balance = await accountingService.getBalance(outgoingPayment.id) - assert.ok(balance === amount) + test.each(OutgoingPaymentWithdrawType)( + 'for outgoing payment event %s', + async (eventType) => { + const balance = await accountingService.getBalance( + outgoingPayment.id + ) + assert.ok(balance === amount) - await WebhookEvent.query(knex).insert({ - id: uuid(), - outgoingPaymentId: outgoingPayment.id, - type: eventType, - data: {}, - withdrawal: { - accountId: outgoingPayment.id, - assetId: outgoingPayment.asset.id, - amount - } - }) + await WebhookEvent.query(knex).insert({ + id: uuid(), + outgoingPaymentId: outgoingPayment.id, + type: eventType, + data: {}, + withdrawal: { + accountId: outgoingPayment.id, + assetId: outgoingPayment.asset.id, + amount + } + }) - const response = await appContainer.apolloClient - .mutate({ - mutation: gql` - mutation CreateOutgoingPaymentWithdrawal( - $input: CreateOutgoingPaymentWithdrawalInput! - ) { - createOutgoingPaymentWithdrawal(input: $input) { - success + const response = await appContainer.apolloClient + .mutate({ + mutation: gql` + mutation CreateOutgoingPaymentWithdrawal( + $input: CreateOutgoingPaymentWithdrawalInput! + ) { + createOutgoingPaymentWithdrawal(input: $input) { + success + } + } + `, + variables: { + input: { + outgoingPaymentId: outgoingPayment.id, + idempotencyKey: uuid(), + timeoutSeconds: 0 } } - `, - variables: { - input: { - outgoingPaymentId: outgoingPayment.id, - idempotencyKey: uuid(), - timeoutSeconds: 0 + }) + .then((query): LiquidityMutationResponse => { + if (query.data) { + return query.data.createOutgoingPaymentWithdrawal + } else { + throw new Error('Data was empty') } - } - }) - .then((query): LiquidityMutationResponse => { - if (query.data) { - return query.data.createOutgoingPaymentWithdrawal - } else { - throw new Error('Data was empty') - } - }) + }) - expect(response.success).toBe(true) - expect( - accountingService.getBalance(outgoingPayment.id) - ).resolves.toEqual(balance - amount) - }) + expect(response.success).toBe(true) + expect( + accountingService.getBalance(outgoingPayment.id) + ).resolves.toEqual(balance - amount) + } + ) }) describe('Cannot withdraw liquidity', () => { @@ -2587,7 +2580,7 @@ describe('Liquidity Resolvers', (): void => { await WebhookEvent.query(knex).insert({ id: uuid(), outgoingPaymentId: outgoingPayment.id, - type: OutgoingPaymentEventType.PaymentCompleted, + type: WebhookEventType.OutgoingPaymentCompleted, data: {}, withdrawal: { accountId: outgoingPayment.id, @@ -2648,27 +2641,66 @@ describe('Liquidity Resolvers', (): void => { }) describe('depositOutgoingPaymentLiquidity', (): void => { - describe.each(Object.values(DepositEventType).map((type) => [type]))( - '%s', - (type): void => { - let eventId: string - - beforeEach(async (): Promise => { - eventId = uuid() - await OutgoingPaymentEvent.query(knex).insertAndFetch({ - id: eventId, - outgoingPaymentId: outgoingPayment.id, - type, - data: outgoingPayment.toData({ - amountSent: BigInt(0), - balance: BigInt(0) - }) + describe.each(DepositEventType)('%s', (type): void => { + let eventId: string + + beforeEach(async (): Promise => { + eventId = uuid() + await OutgoingPaymentEvent.query(knex).insertAndFetch({ + id: eventId, + outgoingPaymentId: outgoingPayment.id, + type: type as DepositEventType, + data: outgoingPayment.toData({ + amountSent: BigInt(0), + balance: BigInt(0) }) }) + }) - test('Can deposit account liquidity', async (): Promise => { - const depositSpy = jest.spyOn(accountingService, 'createDeposit') - const response = await appContainer.apolloClient + test('Can deposit account liquidity', async (): Promise => { + const depositSpy = jest.spyOn(accountingService, 'createDeposit') + const response = await appContainer.apolloClient + .mutate({ + mutation: gql` + mutation DepositLiquidity( + $input: DepositOutgoingPaymentLiquidityInput! + ) { + depositOutgoingPaymentLiquidity(input: $input) { + success + } + } + `, + variables: { + input: { + outgoingPaymentId: outgoingPayment.id, + idempotencyKey: uuid() + } + } + }) + .then((query): LiquidityMutationResponse => { + if (query.data) { + return query.data.depositOutgoingPaymentLiquidity + } else { + throw new Error('Data was empty') + } + }) + + expect(response.success).toBe(true) + assert.ok(outgoingPayment.debitAmount) + await expect(depositSpy).toHaveBeenCalledWith({ + id: eventId, + account: expect.any(OutgoingPayment), + amount: outgoingPayment.debitAmount.value + }) + await expect( + accountingService.getBalance(outgoingPayment.id) + ).resolves.toEqual(outgoingPayment.debitAmount.value) + }) + + test("Can't deposit for non-existent outgoing payment id", async (): Promise => { + let error + try { + await appContainer.apolloClient .mutate({ mutation: gql` mutation DepositLiquidity( @@ -2681,7 +2713,7 @@ describe('Liquidity Resolvers', (): void => { `, variables: { input: { - outgoingPaymentId: outgoingPayment.id, + outgoingPaymentId: uuid(), idempotencyKey: uuid() } } @@ -2693,111 +2725,69 @@ describe('Liquidity Resolvers', (): void => { throw new Error('Data was empty') } }) + } catch (err) { + error = err + } + expect(error).toBeInstanceOf(ApolloError) + expect((error as ApolloError).graphQLErrors).toContainEqual( + expect.objectContaining({ + message: 'Invalid transfer id', + extensions: expect.objectContaining({ + code: GraphQLErrorCode.BadUserInput + }) + }) + ) + }) - expect(response.success).toBe(true) - assert.ok(outgoingPayment.debitAmount) - await expect(depositSpy).toHaveBeenCalledWith({ + test('Returns an error for existing transfer', async (): Promise => { + await expect( + accountingService.createDeposit({ id: eventId, - account: expect.any(OutgoingPayment), - amount: outgoingPayment.debitAmount.value + account: incomingPayment, + amount: BigInt(100) }) - await expect( - accountingService.getBalance(outgoingPayment.id) - ).resolves.toEqual(outgoingPayment.debitAmount.value) - }) - - test("Can't deposit for non-existent outgoing payment id", async (): Promise => { - let error - try { - await appContainer.apolloClient - .mutate({ - mutation: gql` - mutation DepositLiquidity( - $input: DepositOutgoingPaymentLiquidityInput! - ) { - depositOutgoingPaymentLiquidity(input: $input) { - success - } - } - `, - variables: { - input: { - outgoingPaymentId: uuid(), - idempotencyKey: uuid() + ).resolves.toBeUndefined() + let error + try { + await appContainer.apolloClient + .mutate({ + mutation: gql` + mutation DepositLiquidity( + $input: DepositOutgoingPaymentLiquidityInput! + ) { + depositOutgoingPaymentLiquidity(input: $input) { + success } } - }) - .then((query): LiquidityMutationResponse => { - if (query.data) { - return query.data.depositOutgoingPaymentLiquidity - } else { - throw new Error('Data was empty') + `, + variables: { + input: { + outgoingPaymentId: outgoingPayment.id, + idempotencyKey: uuid() } - }) - } catch (err) { - error = err - } - expect(error).toBeInstanceOf(ApolloError) - expect((error as ApolloError).graphQLErrors).toContainEqual( - expect.objectContaining({ - message: 'Invalid transfer id', - extensions: expect.objectContaining({ - code: GraphQLErrorCode.BadUserInput - }) + } }) - ) - }) - - test('Returns an error for existing transfer', async (): Promise => { - await expect( - accountingService.createDeposit({ - id: eventId, - account: incomingPayment, - amount: BigInt(100) + .then((query): LiquidityMutationResponse => { + if (query.data) { + return query.data.depositOutgoingPaymentLiquidity + } else { + throw new Error('Data was empty') + } }) - ).resolves.toBeUndefined() - let error - try { - await appContainer.apolloClient - .mutate({ - mutation: gql` - mutation DepositLiquidity( - $input: DepositOutgoingPaymentLiquidityInput! - ) { - depositOutgoingPaymentLiquidity(input: $input) { - success - } - } - `, - variables: { - input: { - outgoingPaymentId: outgoingPayment.id, - idempotencyKey: uuid() - } - } - }) - .then((query): LiquidityMutationResponse => { - if (query.data) { - return query.data.depositOutgoingPaymentLiquidity - } else { - throw new Error('Data was empty') - } - }) - } catch (err) { - error = err - } - expect(error).toBeInstanceOf(ApolloError) - expect((error as ApolloError).graphQLErrors).toContainEqual( - expect.objectContaining({ - message: 'Transfer already exists', - extensions: expect.objectContaining({ - code: GraphQLErrorCode.Duplicate - }) + } catch (err) { + error = err + } + expect(error).toBeInstanceOf(ApolloError) + expect((error as ApolloError).graphQLErrors).toContainEqual( + expect.objectContaining({ + message: 'Transfer already exists', + extensions: expect.objectContaining({ + code: GraphQLErrorCode.Duplicate }) - ) - }) - } - ) + }) + ) + }) + }) }) }) }) diff --git a/packages/backend/src/graphql/resolvers/liquidity.ts b/packages/backend/src/graphql/resolvers/liquidity.ts index ba434e31ea..4dfdfc894c 100644 --- a/packages/backend/src/graphql/resolvers/liquidity.ts +++ b/packages/backend/src/graphql/resolvers/liquidity.ts @@ -24,15 +24,14 @@ import { } from '../../accounting/errors' import { isOutgoingPaymentEvent, - OutgoingPaymentDepositType, - OutgoingPaymentEventType + OutgoingPaymentDepositType } from '../../open_payments/payment/outgoing/model' import { PeerError, errorToMessage as peerErrorToMessage, errorToCode as peerErrorToCode } from '../../payment-method/ilp/peer/errors' -import { IncomingPaymentEventType } from '../../open_payments/payment/incoming/model' +import { WebhookEventType } from '../../webhook/model' export const getAssetLiquidity: AssetResolvers['liquidity'] = async (parent, args, ctx): Promise => { @@ -348,7 +347,7 @@ export type DepositEventType = OutgoingPaymentDepositType // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types const isDepositEventType = (o: any): o is DepositEventType => - Object.values(DepositEventType).includes(o) + Object.values(OutgoingPaymentDepositType).includes(o) export const depositEventLiquidity: MutationResolvers['depositEventLiquidity'] = async ( @@ -444,7 +443,7 @@ export const depositOutgoingPaymentLiquidity: MutationResolvers[' const webhookService = await ctx.container.use('webhookService') const event = await webhookService.getLatestByResourceId({ outgoingPaymentId, - types: [OutgoingPaymentDepositType.PaymentCreated] + types: [WebhookEventType.OutgoingPaymentCreated] }) if (!event || !isOutgoingPaymentEvent(event)) { throw new GraphQLError(errorToMessage[LiquidityError.InvalidId], { @@ -494,8 +493,8 @@ export const createIncomingPaymentWithdrawal: MutationResolvers[' const event = await webhookService.getLatestByResourceId({ incomingPaymentId, types: [ - IncomingPaymentEventType.IncomingPaymentCompleted, - IncomingPaymentEventType.IncomingPaymentExpired + WebhookEventType.IncomingPaymentCompleted, + WebhookEventType.IncomingPaymentExpired ] }) if (!incomingPayment || !incomingPayment.receivedAmount || !event?.id) { @@ -546,8 +545,8 @@ export const createOutgoingPaymentWithdrawal: MutationResolvers[' const event = await webhookService.getLatestByResourceId({ outgoingPaymentId, types: [ - OutgoingPaymentEventType.PaymentCompleted, - OutgoingPaymentEventType.PaymentFailed + WebhookEventType.OutgoingPaymentCompleted, + WebhookEventType.OutgoingPaymentFailed ] }) if (!outgoingPayment || !event?.id) { diff --git a/packages/backend/src/graphql/resolvers/wallet_address.test.ts b/packages/backend/src/graphql/resolvers/wallet_address.test.ts index ff51dd0d1f..0f0826d26b 100644 --- a/packages/backend/src/graphql/resolvers/wallet_address.test.ts +++ b/packages/backend/src/graphql/resolvers/wallet_address.test.ts @@ -17,8 +17,7 @@ import { } from '../../open_payments/wallet_address/errors' import { WalletAddress as WalletAddressModel, - WalletAddressEvent, - WalletAddressEventType + WalletAddressEvent } from '../../open_payments/wallet_address/model' import { WalletAddressService } from '../../open_payments/wallet_address/service' import { createAsset } from '../../tests/asset' @@ -35,6 +34,7 @@ import { import { getPageTests } from './page.test' import { WalletAddressAdditionalProperty } from '../../open_payments/wallet_address/additional_property/model' import { GraphQLErrorCode } from '../errors' +import { WebhookEventType } from '../../webhook/model' describe('Wallet Address Resolvers', (): void => { let deps: IocContract @@ -872,7 +872,7 @@ describe('Wallet Address Resolvers', (): void => { expect(response.count).toEqual(count) await expect( WalletAddressEvent.query(knex).where({ - type: WalletAddressEventType.WalletAddressWebMonetization + type: WebhookEventType.WalletAddressWebMonetization }) ).resolves.toHaveLength(count) for (let i = 1; i <= count; i++) { diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index b6a57d5523..0ae5e5d7fb 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -1289,13 +1289,36 @@ type WebhookEvent implements Model { "Unique identifier of the webhook event." id: ID! "Type of webhook event." - type: String! + type: WebhookEventType! "Stringified JSON data for the webhook event." data: JSONObject! "The date and time when the webhook event was created." createdAt: String! } +enum WebhookEventType { + "An incoming payment was created" + INCOMING_PAYMENT_CREATED + "An incoming payment was completed" + INCOMING_PAYMENT_COMPLETED + "An incoming payment has expired" + INCOMING_PAYMENT_EXPIRED + "An outgoing payment was created" + OUTGOING_PAYMENT_CREATED + "An outgoing payment was completed" + OUTGOING_PAYMENT_COMPLETED + "An outgoing payment has failed and won't be retried" + OUTGOING_PAYMENT_FAILED + "A Web Monetization payment was created" + WALLET_ADDRESS_WEB_MONETIZATION + "Wallet address was not found and it will be created if there exists a corresponding account" + WALLET_ADDRESS_NOT_FOUND + "Asset liquidity is low" + ASSET_LIQUIDITY_LOW + "Peer liquidity is low" + PEER_LIQUIDITY_LOW +} + type WebhookEventsConnection { "Pagination information for webhook events." pageInfo: PageInfo! diff --git a/packages/backend/src/open_payments/payment/incoming/model.test.ts b/packages/backend/src/open_payments/payment/incoming/model.test.ts index e4577af3ad..c65c3c5c67 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.test.ts @@ -167,7 +167,7 @@ describe('Models', (): void => { async ({ type, error }): Promise => { expect( IncomingPaymentEvent.query().insert({ - type + type: type as IncomingPaymentEventType }) ).rejects.toThrow(error) } diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index b22f086af7..ce9a5732d8 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -9,18 +9,24 @@ import { import { Asset } from '../../../asset/model' import { LiquidityAccount, OnCreditOptions } from '../../../accounting/service' import { ConnectorAccount } from '../../../payment-method/ilp/connector/core/rafiki' -import { WebhookEvent } from '../../../webhook/model' +import { WebhookEvent, WebhookEventType } from '../../../webhook/model' import { IncomingPayment as OpenPaymentsIncomingPayment, IncomingPaymentWithPaymentMethods as OpenPaymentsIncomingPaymentWithPaymentMethod } from '@interledger/open-payments' import base64url from 'base64url' -export enum IncomingPaymentEventType { - IncomingPaymentCreated = 'incoming_payment.created', - IncomingPaymentExpired = 'incoming_payment.expired', - IncomingPaymentCompleted = 'incoming_payment.completed' -} +export type IncomingPaymentEventType = Extract< + WebhookEventType, + | WebhookEventType.IncomingPaymentCreated + | WebhookEventType.IncomingPaymentCompleted + | WebhookEventType.IncomingPaymentExpired +> +export const IncomingPaymentEventType = [ + WebhookEventType.IncomingPaymentCreated, + WebhookEventType.IncomingPaymentCompleted, + WebhookEventType.IncomingPaymentExpired +] export enum IncomingPaymentState { // The payment has a state of `PENDING` when it is initially created. 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 d26e5c4ac4..b0e2220ff8 100644 --- a/packages/backend/src/open_payments/payment/incoming/service.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/service.test.ts @@ -9,7 +9,6 @@ import { createTestApp, TestContainer } from '../../../tests/app' import { IncomingPayment, IncomingPaymentEvent, - IncomingPaymentEventType, IncomingPaymentState } from './model' import { Config, IAppConfig } from '../../../config/app' @@ -27,6 +26,7 @@ import { getTests } from '../../wallet_address/model.test' import { WalletAddress } from '../../wallet_address/model' import { withConfigOverride } from '../../../tests/helpers' import { sleep } from '../../../shared/utils' +import { WebhookEventType } from '../../../webhook/model' describe('Incoming Payment Service', (): void => { let deps: IocContract @@ -80,7 +80,7 @@ describe('Incoming Payment Service', (): void => { const incomingPaymentEvent = await IncomingPaymentEvent.query( knex ).findOne({ - type: IncomingPaymentEventType.IncomingPaymentCreated + type: WebhookEventType.IncomingPaymentCreated }) assert.ok(!!incomingPaymentEvent) await IncomingPayment.query(knex) @@ -154,7 +154,7 @@ describe('Incoming Payment Service', (): void => { async (): Promise => { await expect( IncomingPaymentEvent.query(knex).where({ - type: IncomingPaymentEventType.IncomingPaymentCreated + type: WebhookEventType.IncomingPaymentCreated }) ).resolves.toHaveLength(0) @@ -300,7 +300,7 @@ describe('Incoming Payment Service', (): void => { `('An incoming payment can be created', async (options): Promise => { await expect( IncomingPaymentEvent.query(knex).where({ - type: IncomingPaymentEventType.IncomingPaymentCreated + type: WebhookEventType.IncomingPaymentCreated }) ).resolves.toHaveLength(0) options.client = client @@ -319,7 +319,7 @@ describe('Incoming Payment Service', (): void => { }) await expect( IncomingPaymentEvent.query(knex).where({ - type: IncomingPaymentEventType.IncomingPaymentCreated + type: WebhookEventType.IncomingPaymentCreated }) ).resolves.toHaveLength(1) }) @@ -706,9 +706,9 @@ describe('Incoming Payment Service', (): void => { }) describe.each` - eventType | expiresAt | amountReceived - ${IncomingPaymentEventType.IncomingPaymentExpired} | ${new Date(Date.now() + 30_000)} | ${BigInt(1)} - ${IncomingPaymentEventType.IncomingPaymentCompleted} | ${undefined} | ${BigInt(123)} + eventType | expiresAt | amountReceived + ${WebhookEventType.IncomingPaymentExpired} | ${new Date(Date.now() + 30_000)} | ${BigInt(1)} + ${WebhookEventType.IncomingPaymentCompleted} | ${undefined} | ${BigInt(123)} `( 'handleDeactivated ($eventType)', ({ eventType, expiresAt, amountReceived }): void => { @@ -736,7 +736,7 @@ describe('Incoming Payment Service', (): void => { amount: amountReceived }) ).resolves.toBeUndefined() - if (eventType === IncomingPaymentEventType.IncomingPaymentExpired) { + if (eventType === WebhookEventType.IncomingPaymentExpired) { jest.useFakeTimers() jest.setSystemTime(incomingPayment.expiresAt) await expect(incomingPaymentService.processNext()).resolves.toBe( @@ -753,7 +753,7 @@ describe('Incoming Payment Service', (): void => { })) as IncomingPayment expect(incomingPayment).toMatchObject({ state: - eventType === IncomingPaymentEventType.IncomingPaymentExpired + eventType === WebhookEventType.IncomingPaymentExpired ? IncomingPaymentState.Expired : IncomingPaymentState.Completed, processAt: expect.any(Date), diff --git a/packages/backend/src/open_payments/payment/incoming/service.ts b/packages/backend/src/open_payments/payment/incoming/service.ts index 62a8a99547..2dc4dbeea3 100644 --- a/packages/backend/src/open_payments/payment/incoming/service.ts +++ b/packages/backend/src/open_payments/payment/incoming/service.ts @@ -1,7 +1,6 @@ import { IncomingPayment, IncomingPaymentEvent, - IncomingPaymentEventType, IncomingPaymentState } from './model' import { AccountingService } from '../../../accounting/service' @@ -18,6 +17,7 @@ import { Amount } from '../../amount' import { IncomingPaymentError } from './errors' import { IAppConfig } from '../../../config/app' import { poll } from '../../../shared/utils' +import { WebhookEventType } from '../../../webhook/model' export const POSITIVE_SLIPPAGE = BigInt(1) // First retry waits 10 seconds @@ -159,7 +159,7 @@ async function createIncomingPayment( await IncomingPaymentEvent.query(trx || deps.knex).insert({ incomingPaymentId: incomingPayment.id, - type: IncomingPaymentEventType.IncomingPaymentCreated, + type: WebhookEventType.IncomingPaymentCreated, data: incomingPayment.toData(0n) }) @@ -300,8 +300,8 @@ async function handleDeactivated( const type = incomingPayment.state == IncomingPaymentState.Expired - ? IncomingPaymentEventType.IncomingPaymentExpired - : IncomingPaymentEventType.IncomingPaymentCompleted + ? WebhookEventType.IncomingPaymentExpired + : WebhookEventType.IncomingPaymentCompleted deps.logger.trace({ type }, 'creating incoming payment webhook event') await IncomingPaymentEvent.query(deps.knex).insert({ diff --git a/packages/backend/src/open_payments/payment/outgoing/lifecycle.ts b/packages/backend/src/open_payments/payment/outgoing/lifecycle.ts index 1b402db2d3..a0cf027abf 100644 --- a/packages/backend/src/open_payments/payment/outgoing/lifecycle.ts +++ b/packages/backend/src/open_payments/payment/outgoing/lifecycle.ts @@ -9,6 +9,7 @@ import { ServiceDependencies } from './service' import { Receiver } from '../../receiver/model' import { TransactionOrKnex } from 'objection' import { ValueType } from '@opentelemetry/api' +import { WebhookEventType } from '../../../webhook/model' // "payment" is locked by the "deps.knex" transaction. export async function handleSending( @@ -146,7 +147,7 @@ export async function handleFailed( state: OutgoingPaymentState.Failed, error }) - await sendWebhookEvent(deps, payment, OutgoingPaymentEventType.PaymentFailed) + await sendWebhookEvent(deps, payment, WebhookEventType.OutgoingPaymentFailed) } async function handleCompleted( @@ -160,7 +161,7 @@ async function handleCompleted( await sendWebhookEvent( deps, payment, - OutgoingPaymentEventType.PaymentCompleted + WebhookEventType.OutgoingPaymentCompleted ) } diff --git a/packages/backend/src/open_payments/payment/outgoing/model.test.ts b/packages/backend/src/open_payments/payment/outgoing/model.test.ts index f724192d6c..900b1b0327 100644 --- a/packages/backend/src/open_payments/payment/outgoing/model.test.ts +++ b/packages/backend/src/open_payments/payment/outgoing/model.test.ts @@ -41,7 +41,7 @@ describe('Outgoing Payment Event Model', (): void => { async ({ type, error }): Promise => { expect( OutgoingPaymentEvent.query(knex).insert({ - type + type: type as OutgoingPaymentEventType }) ).rejects.toThrow(error) } diff --git a/packages/backend/src/open_payments/payment/outgoing/model.ts b/packages/backend/src/open_payments/payment/outgoing/model.ts index 130e9391b6..9d643337c0 100644 --- a/packages/backend/src/open_payments/payment/outgoing/model.ts +++ b/packages/backend/src/open_payments/payment/outgoing/model.ts @@ -10,7 +10,7 @@ import { } from '../../wallet_address/model' import { Quote } from '../../quote/model' import { Amount, AmountJSON, serializeAmount } from '../../amount' -import { WebhookEvent } from '../../../webhook/model' +import { WebhookEvent, WebhookEventType } from '../../../webhook/model' import { OutgoingPayment as OpenPaymentsOutgoingPayment, OutgoingPaymentWithSpentAmounts @@ -237,19 +237,28 @@ export enum OutgoingPaymentState { Cancelled = 'CANCELLED' } -export enum OutgoingPaymentDepositType { - PaymentCreated = 'outgoing_payment.created' -} - -export enum OutgoingPaymentWithdrawType { - PaymentFailed = 'outgoing_payment.failed', - PaymentCompleted = 'outgoing_payment.completed' -} - -export const OutgoingPaymentEventType = { +export type OutgoingPaymentDepositType = Extract< + WebhookEventType, + WebhookEventType.OutgoingPaymentCreated +> +export const OutgoingPaymentDepositType = [ + WebhookEventType.OutgoingPaymentCreated +] + +export type OutgoingPaymentWithdrawType = Extract< + WebhookEventType, + | WebhookEventType.OutgoingPaymentCompleted + | WebhookEventType.OutgoingPaymentFailed +> +export const OutgoingPaymentWithdrawType = [ + WebhookEventType.OutgoingPaymentCompleted, + WebhookEventType.OutgoingPaymentFailed +] + +export const OutgoingPaymentEventType = [ ...OutgoingPaymentDepositType, ...OutgoingPaymentWithdrawType -} +] export type OutgoingPaymentEventType = | OutgoingPaymentDepositType | OutgoingPaymentWithdrawType @@ -279,8 +288,7 @@ export type PaymentData = Omit & { export const isOutgoingPaymentEventType = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types o: any -): o is OutgoingPaymentEventType => - Object.values(OutgoingPaymentEventType).includes(o) +): o is OutgoingPaymentEventType => OutgoingPaymentEventType.includes(o) // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types export const isOutgoingPaymentEvent = (o: any): o is OutgoingPaymentEvent => diff --git a/packages/backend/src/open_payments/payment/outgoing/service.test.ts b/packages/backend/src/open_payments/payment/outgoing/service.test.ts index 240eefcef6..0a045ec13c 100644 --- a/packages/backend/src/open_payments/payment/outgoing/service.test.ts +++ b/packages/backend/src/open_payments/payment/outgoing/service.test.ts @@ -53,6 +53,7 @@ import { withConfigOverride } from '../../../tests/helpers' import { TelemetryService } from '../../../telemetry/service' import { getPageTests } from '../../../shared/baseModel.test' import { Pagination, SortOrder } from '../../../shared/baseModel' +import { WebhookEventType } from '../../../webhook/model' describe('OutgoingPaymentService', (): void => { let deps: IocContract @@ -94,10 +95,10 @@ describe('OutgoingPaymentService', (): void => { const webhookTypes: { [key in OutgoingPaymentState]: OutgoingPaymentEventType | undefined } = { - [OutgoingPaymentState.Funding]: OutgoingPaymentEventType.PaymentCreated, + [OutgoingPaymentState.Funding]: WebhookEventType.OutgoingPaymentCreated, [OutgoingPaymentState.Sending]: undefined, - [OutgoingPaymentState.Failed]: OutgoingPaymentEventType.PaymentFailed, - [OutgoingPaymentState.Completed]: OutgoingPaymentEventType.PaymentCompleted, + [OutgoingPaymentState.Failed]: WebhookEventType.OutgoingPaymentFailed, + [OutgoingPaymentState.Completed]: WebhookEventType.OutgoingPaymentCompleted, [OutgoingPaymentState.Cancelled]: undefined } @@ -830,7 +831,7 @@ describe('OutgoingPaymentService', (): void => { } await expect( OutgoingPaymentEvent.query(knex).where({ - type: OutgoingPaymentEventType.PaymentCreated + type: WebhookEventType.OutgoingPaymentCreated }) ).resolves.toMatchObject([ { diff --git a/packages/backend/src/open_payments/payment/outgoing/service.ts b/packages/backend/src/open_payments/payment/outgoing/service.ts index 336444e518..6818c4bbcf 100644 --- a/packages/backend/src/open_payments/payment/outgoing/service.ts +++ b/packages/backend/src/open_payments/payment/outgoing/service.ts @@ -15,8 +15,7 @@ import { Limits, getInterval } from './limits' import { OutgoingPayment, OutgoingPaymentGrant, - OutgoingPaymentState, - OutgoingPaymentEventType + OutgoingPaymentState } from './model' import { Grant } from '../../auth/middleware' import { @@ -42,6 +41,7 @@ import { QuoteService } from '../../quote/service' import { isQuoteError } from '../../quote/errors' import { Pagination, SortOrder } from '../../../shared/baseModel' import { FilterString } from '../../../shared/filters' +import { WebhookEventType } from '../../../webhook/model' export interface OutgoingPaymentService extends WalletAddressSubresourceService { @@ -297,7 +297,7 @@ async function createOutgoingPayment( await sendWebhookEvent( deps, payment, - OutgoingPaymentEventType.PaymentCreated, + WebhookEventType.OutgoingPaymentCreated, trx ) diff --git a/packages/backend/src/open_payments/wallet_address/model.test.ts b/packages/backend/src/open_payments/wallet_address/model.test.ts index 6aaeb79184..aa2f28f602 100644 --- a/packages/backend/src/open_payments/wallet_address/model.test.ts +++ b/packages/backend/src/open_payments/wallet_address/model.test.ts @@ -32,6 +32,7 @@ import assert from 'assert' import { ReadContextWithAuthenticatedStatus } from '../payment/incoming/routes' import { Knex } from 'knex' import { OpenPaymentsServerRouteError } from '../route-errors' +import { WebhookEventType } from '../../webhook/model' export interface SetupOptions { reqOpts: httpMocks.RequestOptions @@ -430,7 +431,7 @@ describe('Models', (): void => { describe('beforeInsert', (): void => { test.each([ { - type: WalletAddressEventType.WalletAddressWebMonetization, + type: WebhookEventType.WalletAddressWebMonetization, error: WalletAddressEventError.WalletAddressIdRequired } ])( @@ -438,7 +439,7 @@ describe('Models', (): void => { async ({ type, error }): Promise => { expect( WalletAddressEvent.query(knex).insert({ - type + type: type as WalletAddressEventType }) ).rejects.toThrow(error) } @@ -446,7 +447,7 @@ describe('Models', (): void => { test.each([ { - type: WalletAddressEventType.WalletAddressNotFound, + type: WebhookEventType.WalletAddressNotFound, error: WalletAddressEventError.WalletAddressIdProhibited } ])( @@ -455,7 +456,7 @@ describe('Models', (): void => { expect( WalletAddressEvent.query(knex).insert({ walletAddressId: uuid(), - type + type: type as WalletAddressEventType }) ).rejects.toThrow(error) } diff --git a/packages/backend/src/open_payments/wallet_address/model.ts b/packages/backend/src/open_payments/wallet_address/model.ts index 81dd603a1d..218b897182 100644 --- a/packages/backend/src/open_payments/wallet_address/model.ts +++ b/packages/backend/src/open_payments/wallet_address/model.ts @@ -4,7 +4,7 @@ import { LiquidityAccount, OnCreditOptions } from '../../accounting/service' import { ConnectorAccount } from '../../payment-method/ilp/connector/core/rafiki' import { Asset } from '../../asset/model' import { BaseModel, Pagination, SortOrder } from '../../shared/baseModel' -import { WebhookEvent } from '../../webhook/model' +import { WebhookEvent, WebhookEventType } from '../../webhook/model' import { WalletAddressKey } from '../../open_payments/wallet_address/key/model' import { AmountJSON } from '../amount' import { WalletAddressAdditionalProperty } from './additional_property/model' @@ -54,7 +54,7 @@ export class WalletAddress public asset!: Asset // The cumulative received amount tracked by - // `wallet_address.web_monetization` webhook events. + // `WALLET_ADDRESS_WEB_MONETIZATION` webhook events. // The value should be equivalent to the following query: // select sum(`withdrawalAmount`) from `webhookEvents` where `withdrawalAccountId` = `walletAddress.id` public totalEventsAmount!: bigint @@ -133,10 +133,15 @@ export class WalletAddress } } -export enum WalletAddressEventType { - WalletAddressWebMonetization = 'wallet_address.web_monetization', - WalletAddressNotFound = 'wallet_address.not_found' -} +export type WalletAddressEventType = Extract< + WebhookEventType, + | WebhookEventType.WalletAddressWebMonetization + | WebhookEventType.WalletAddressNotFound +> +export const WalletAddressEventType = [ + WebhookEventType.WalletAddressWebMonetization, + WebhookEventType.WalletAddressNotFound +] export type WalletAddressData = { walletAddress: { @@ -163,12 +168,12 @@ export class WalletAddressEvent extends WebhookEvent { super.$beforeInsert(context) if ( - this.type === WalletAddressEventType.WalletAddressNotFound && + this.type === WebhookEventType.WalletAddressNotFound && this.walletAddressId ) { throw new Error(WalletAddressEventError.WalletAddressIdProhibited) } else if ( - this.type !== WalletAddressEventType.WalletAddressNotFound && + this.type !== WebhookEventType.WalletAddressNotFound && !this.walletAddressId ) { throw new Error(WalletAddressEventError.WalletAddressIdRequired) diff --git a/packages/backend/src/open_payments/wallet_address/service.test.ts b/packages/backend/src/open_payments/wallet_address/service.test.ts index 30db937dd3..223f566042 100644 --- a/packages/backend/src/open_payments/wallet_address/service.test.ts +++ b/packages/backend/src/open_payments/wallet_address/service.test.ts @@ -3,11 +3,7 @@ import { Knex } from 'knex' import { v4 as uuid } from 'uuid' import { isWalletAddressError, WalletAddressError } from './errors' -import { - WalletAddress, - WalletAddressEvent, - WalletAddressEventType -} from './model' +import { WalletAddress, WalletAddressEvent } from './model' import { CreateOptions, FORBIDDEN_PATHS, WalletAddressService } from './service' import { AccountingService } from '../../accounting/service' import { createTestApp, TestContainer } from '../../tests/app' @@ -25,6 +21,7 @@ import { Pagination, SortOrder } from '../../shared/baseModel' import { sleep } from '../../shared/utils' import { withConfigOverride } from '../../tests/helpers' import { WalletAddressAdditionalProperty } from './additional_property/model' +import { WebhookEventType } from '../../webhook/model' describe('Open Payments Wallet Address Service', (): void => { let deps: IocContract @@ -473,7 +470,7 @@ describe('Open Payments Wallet Address Service', (): void => { const walletAddressNotFoundEvents = await WalletAddressEvent.query( knex ).where({ - type: WalletAddressEventType.WalletAddressNotFound + type: WebhookEventType.WalletAddressNotFound }) expect(walletAddressNotFoundEvents[0]).toMatchObject({ @@ -711,7 +708,7 @@ describe('Open Payments Wallet Address Service', (): void => { }) await expect( WalletAddressEvent.query(knex).where({ - type: WalletAddressEventType.WalletAddressWebMonetization, + type: WebhookEventType.WalletAddressWebMonetization, withdrawalAccountId: walletAddress.id, withdrawalAssetId: walletAddress.assetId, withdrawalAmount @@ -777,7 +774,7 @@ describe('Open Payments Wallet Address Service', (): void => { ) await expect( WalletAddressEvent.query(knex).where({ - type: WalletAddressEventType.WalletAddressWebMonetization + type: WebhookEventType.WalletAddressWebMonetization }) ).resolves.toHaveLength(count) for (let i = 1; i <= count; i++) { diff --git a/packages/backend/src/open_payments/wallet_address/service.ts b/packages/backend/src/open_payments/wallet_address/service.ts index ad14d8f882..d077974a82 100644 --- a/packages/backend/src/open_payments/wallet_address/service.ts +++ b/packages/backend/src/open_payments/wallet_address/service.ts @@ -10,7 +10,6 @@ import { WalletAddressError } from './errors' import { WalletAddress, WalletAddressEvent, - WalletAddressEventType, GetOptions, ListOptions, WalletAddressSubresource @@ -26,6 +25,7 @@ import { Pagination, SortOrder } from '../../shared/baseModel' import { WebhookService } from '../../webhook/service' import { poll } from '../../shared/utils' import { WalletAddressAdditionalProperty } from './additional_property/model' +import { WebhookEventType } from '../../webhook/model' interface Options { publicName?: string @@ -275,7 +275,7 @@ async function getOrPollByUrl( if (existingWalletAddress) return existingWalletAddress await WalletAddressEvent.query(deps.knex).insert({ - type: WalletAddressEventType.WalletAddressNotFound, + type: WebhookEventType.WalletAddressNotFound, data: { walletAddressUrl: url } @@ -399,7 +399,7 @@ async function createWithdrawalEvent( await WalletAddressEvent.query(deps.knex).insert({ walletAddressId: walletAddress.id, - type: WalletAddressEventType.WalletAddressWebMonetization, + type: WebhookEventType.WalletAddressWebMonetization, data: walletAddress.toData(amount), withdrawal: { accountId: walletAddress.id, diff --git a/packages/backend/src/openapi/specs/webhooks.yaml b/packages/backend/src/openapi/specs/webhooks.yaml index d775db1bcc..8e57590272 100644 --- a/packages/backend/src/openapi/specs/webhooks.yaml +++ b/packages/backend/src/openapi/specs/webhooks.yaml @@ -133,9 +133,9 @@ components: type: type: string enum: - - incoming_payment.created - - incoming_payment.completed - - incoming_payment.expired + - INCOMING_PAYMENT_CREATED + - INCOMING_PAYMENT_COMPLETED + - INCOMING_PAYMENT_EXPIRED data: type: object required: @@ -187,9 +187,9 @@ components: type: type: string enum: - - outgoing_payment.created - - outgoing_payment.completed - - outgoing_payment.failed + - OUTGOING_PAYMENT_CREATED + - OUTGOING_PAYMENT_COMPLETED + - OUTGOING_PAYMENT_FAILED data: type: object required: @@ -263,7 +263,7 @@ components: type: type: string enum: - - wallet_address.not_found + - WALLET_ADDRESS_NOT_FOUND data: type: object required: @@ -284,7 +284,7 @@ components: type: type: string enum: - - wallet_address.web_monetization + - WALLET_ADDRESS_WEB_MONETIZATION data: type: object required: @@ -318,8 +318,8 @@ components: type: type: string enum: - - asset.liquidity_low - - peer.liquidity_low + - ASSET_LIQUIDITY_LOW + - PEER_LIQUIDITY_LOW data: type: object required: diff --git a/packages/backend/src/payment-method/ilp/peer/model.test.ts b/packages/backend/src/payment-method/ilp/peer/model.test.ts index a664509eb1..e52ca941d0 100644 --- a/packages/backend/src/payment-method/ilp/peer/model.test.ts +++ b/packages/backend/src/payment-method/ilp/peer/model.test.ts @@ -13,6 +13,7 @@ import { truncateTables } from '../../../tests/tableManager' import { Peer, PeerEvent, PeerEventError, PeerEventType } from './model' import { isPeerError } from './errors' import { Asset } from '../../../asset/model' +import { WebhookEventType } from '../../../webhook/model' describe('Models', (): void => { let deps: IocContract @@ -77,11 +78,11 @@ describe('Models', (): void => { const event = ( await PeerEvent.query(knex).where( 'type', - PeerEventType.LiquidityLow + WebhookEventType.PeerLiquidityLow ) )[0] expect(event).toMatchObject({ - type: PeerEventType.LiquidityLow, + type: WebhookEventType.PeerLiquidityLow, data: { id: peer.id, asset: { @@ -98,7 +99,7 @@ describe('Models', (): void => { test('does not create webhook event if balance > liquidityThreshold', async (): Promise => { await peer.onDebit({ balance: BigInt(110) }) await expect( - PeerEvent.query(knex).where('type', PeerEventType.LiquidityLow) + PeerEvent.query(knex).where('type', WebhookEventType.PeerLiquidityLow) ).resolves.toEqual([]) }) }) @@ -114,7 +115,7 @@ describe('Models', (): void => { )('Peer Id is required', async ({ type, error }): Promise => { expect( PeerEvent.query().insert({ - type + type: type as PeerEventType }) ).rejects.toThrow(error) }) diff --git a/packages/backend/src/payment-method/ilp/peer/model.ts b/packages/backend/src/payment-method/ilp/peer/model.ts index e22619e867..f2e7e01928 100644 --- a/packages/backend/src/payment-method/ilp/peer/model.ts +++ b/packages/backend/src/payment-method/ilp/peer/model.ts @@ -4,7 +4,7 @@ import { Asset } from '../../../asset/model' import { ConnectorAccount } from '../connector/core/rafiki' import { HttpToken } from '../peer-http-token/model' import { BaseModel } from '../../../shared/baseModel' -import { WebhookEvent } from '../../../webhook/model' +import { WebhookEvent, WebhookEventType } from '../../../webhook/model' import { join } from 'path' export class Peer @@ -58,7 +58,7 @@ export class Peer if (balance <= this.liquidityThreshold) { await PeerEvent.query().insert({ peerId: this.id, - type: PeerEventType.LiquidityLow, + type: WebhookEventType.PeerLiquidityLow, data: { id: this.id, asset: { @@ -100,9 +100,11 @@ export class Peer } } -export enum PeerEventType { - LiquidityLow = 'peer.liquidity_low' -} +export type PeerEventType = Extract< + WebhookEventType, + WebhookEventType.PeerLiquidityLow +> +export const PeerEventType = [WebhookEventType.PeerLiquidityLow] export type PeerEventData = { id: string diff --git a/packages/backend/src/tests/webhook.ts b/packages/backend/src/tests/webhook.ts index 87dddc8437..ffda5c773d 100644 --- a/packages/backend/src/tests/webhook.ts +++ b/packages/backend/src/tests/webhook.ts @@ -2,12 +2,16 @@ import { faker } from '@faker-js/faker' import { v4 as uuid } from 'uuid' import { IocContract } from '@adonisjs/fold' import { AppServices } from '../app' -import { WebhookEvent } from '../webhook/model' +import { WebhookEvent, WebhookEventType } from '../webhook/model' import { sample } from 'lodash' import { EventPayload } from '../webhook/service' import { createAsset } from './asset' -export const webhookEventTypes = ['event1', 'event2', 'event3'] as const +export const webhookEventTypes = [ + WebhookEventType.IncomingPaymentCreated, + WebhookEventType.IncomingPaymentCompleted, + WebhookEventType.IncomingPaymentExpired +] as const type WebhookEventPayload = EventPayload & { assetId: string } export async function createWebhookEvent( @@ -19,7 +23,7 @@ export async function createWebhookEvent( const newEvent = { id: uuid(), assetId: asset.id, - type: sample(webhookEventTypes) as string, + type: sample(webhookEventTypes), data: { field1: faker.string.sample() }, ...overrides } diff --git a/packages/backend/src/webhook/model.ts b/packages/backend/src/webhook/model.ts index 9235787025..75ab1a2f19 100644 --- a/packages/backend/src/webhook/model.ts +++ b/packages/backend/src/webhook/model.ts @@ -58,7 +58,7 @@ export class WebhookEvent extends BaseModel { } }) - public type!: string + public type!: WebhookEventType public data!: Record public attempts!: number public statusCode?: number @@ -113,3 +113,16 @@ export class WebhookEvent extends BaseModel { return json } } + +export enum WebhookEventType { + IncomingPaymentCreated = 'INCOMING_PAYMENT_CREATED', + IncomingPaymentCompleted = 'INCOMING_PAYMENT_COMPLETED', + IncomingPaymentExpired = 'INCOMING_PAYMENT_EXPIRED', + OutgoingPaymentCreated = 'OUTGOING_PAYMENT_CREATED', + OutgoingPaymentCompleted = 'OUTGOING_PAYMENT_COMPLETED', + OutgoingPaymentFailed = 'OUTGOING_PAYMENT_FAILED', + WalletAddressWebMonetization = 'WALLET_ADDRESS_WEB_MONETIZATION', + WalletAddressNotFound = 'WALLET_ADDRESS_NOT_FOUND', + AssetLiquidityLow = 'ASSET_LIQUIDITY_LOW', + PeerLiquidityLow = 'PEER_LIQUIDITY_LOW' +} diff --git a/packages/backend/src/webhook/service.test.ts b/packages/backend/src/webhook/service.test.ts index bb33fdec72..67a747a56b 100644 --- a/packages/backend/src/webhook/service.test.ts +++ b/packages/backend/src/webhook/service.test.ts @@ -4,7 +4,7 @@ import { URL } from 'url' import { Knex } from 'knex' import { v4 as uuid } from 'uuid' -import { WebhookEvent } from './model' +import { WebhookEvent, WebhookEventType } from './model' import { WebhookService, generateWebhookSignature, @@ -22,14 +22,9 @@ import { AppServices } from '../app' import { getPageTests } from '../shared/baseModel.test' import { Pagination, SortOrder } from '../shared/baseModel' import { createWebhookEvent, webhookEventTypes } from '../tests/webhook' -import { IncomingPaymentEventType } from '../open_payments/payment/incoming/model' -import { OutgoingPaymentEventType } from '../open_payments/payment/outgoing/model' import { createIncomingPayment } from '../tests/incomingPayment' import { createWalletAddress } from '../tests/walletAddress' -import { - WalletAddress, - WalletAddressEventType -} from '../open_payments/wallet_address/model' +import { WalletAddress } from '../open_payments/wallet_address/model' import { createOutgoingPayment } from '../tests/outgoingPayment' const nock = (global as unknown as { nock: typeof import('nock') }).nock @@ -84,7 +79,7 @@ describe('Webhook Service', (): void => { beforeEach(async (): Promise => { event = await WebhookEvent.query(knex).insertAndFetch({ id: uuid(), - type: WalletAddressEventType.WalletAddressNotFound, + type: WebhookEventType.WalletAddressNotFound, data: { account: { id: uuid() @@ -152,25 +147,25 @@ describe('Webhook Service', (): void => { events = [ await WebhookEvent.query(knex).insertAndFetch({ id: uuid(), - type: IncomingPaymentEventType.IncomingPaymentCompleted, + type: WebhookEventType.IncomingPaymentCompleted, data: { id: uuid() }, incomingPaymentId: incomingPaymentIds[0] }), await WebhookEvent.query(knex).insertAndFetch({ id: uuid(), - type: IncomingPaymentEventType.IncomingPaymentExpired, + type: WebhookEventType.IncomingPaymentExpired, data: { id: uuid() }, incomingPaymentId: incomingPaymentIds[0] }), await WebhookEvent.query(knex).insertAndFetch({ id: uuid(), - type: IncomingPaymentEventType.IncomingPaymentCompleted, + type: WebhookEventType.IncomingPaymentCompleted, data: { id: uuid() }, incomingPaymentId: incomingPaymentIds[1] }), await WebhookEvent.query(knex).insertAndFetch({ id: uuid(), - type: OutgoingPaymentEventType.PaymentCreated, + type: WebhookEventType.OutgoingPaymentCreated, data: { id: uuid() }, outgoingPaymentId: outgoingPaymentIds[0] }) @@ -182,15 +177,15 @@ describe('Webhook Service', (): void => { webhookService.getLatestByResourceId({ incomingPaymentId: incomingPaymentIds[0], types: [ - IncomingPaymentEventType.IncomingPaymentCompleted, - IncomingPaymentEventType.IncomingPaymentExpired + WebhookEventType.IncomingPaymentCompleted, + WebhookEventType.IncomingPaymentExpired ] }) ).resolves.toEqual(events[1]) await expect( webhookService.getLatestByResourceId({ outgoingPaymentId: outgoingPaymentIds[0], - types: [OutgoingPaymentEventType.PaymentCreated] + types: [WebhookEventType.OutgoingPaymentCreated] }) ).resolves.toEqual(events[3]) }) @@ -198,7 +193,7 @@ describe('Webhook Service', (): void => { test('Gets latest of any type when type not provided', async (): Promise => { const newLatestEvent = await WebhookEvent.query(knex).insertAndFetch({ id: uuid(), - type: 'some_new_type', + type: WebhookEventType.IncomingPaymentCreated, data: { id: uuid() }, incomingPaymentId: incomingPaymentIds[0] }) @@ -222,7 +217,7 @@ describe('Webhook Service', (): void => { await expect( webhookService.getLatestByResourceId({ incomingPaymentId: uuid(), - types: [IncomingPaymentEventType.IncomingPaymentCompleted] + types: [WebhookEventType.IncomingPaymentCompleted] }) ).resolves.toBeUndefined() }) @@ -302,7 +297,7 @@ describe('Webhook Service', (): void => { beforeEach(async (): Promise => { event = await WebhookEvent.query(knex).insertAndFetch({ id: uuid(), - type: WalletAddressEventType.WalletAddressNotFound, + type: WebhookEventType.WalletAddressNotFound, data: { account: { id: uuid() @@ -414,7 +409,7 @@ describe('Webhook Service', (): void => { const nextEvent = await WebhookEvent.query(knex).insertAndFetch({ id: uuid(), - type: WalletAddressEventType.WalletAddressNotFound, + type: WebhookEventType.WalletAddressNotFound, data: { account: { id: uuid() diff --git a/packages/documentation/src/content/docs/admin/manage-liquidity.mdx b/packages/documentation/src/content/docs/admin/manage-liquidity.mdx index 906ff80d44..503db0736b 100644 --- a/packages/documentation/src/content/docs/admin/manage-liquidity.mdx +++ b/packages/documentation/src/content/docs/admin/manage-liquidity.mdx @@ -216,7 +216,7 @@ Peer liquidity can also be added through the [Rafiki Admin](/admin/admin-user-gu ## Payment Liquidity -When Open Payments incoming or outgoing payments are created, your Rafiki instance creates a liquidity account within the accounting database. Liquidity must be deposited into an outgoing payment before the payment can be processed. Rafiki will notify you to deposit liquidity via the `outgoing_payment.created` webhook event. Similarly, packets received for an incoming payment increase its liquidity account. Rafiki will notify you to withdraw that liquidity via the `incoming_payment.completed` webhook event. +When Open Payments incoming or outgoing payments are created, your Rafiki instance creates a liquidity account within the accounting database. Liquidity must be deposited into an outgoing payment before the payment can be processed. Rafiki will notify you to deposit liquidity via the `OUTGOING_PAYMENT_CREATED` webhook event. Similarly, packets received for an incoming payment increase its liquidity account. Rafiki will notify you to withdraw that liquidity via the `INCOMING_PAYMENT_COMPLETED` webhook event. ### Withdraw incoming payment liquidity using the `CreateIncomingPaymentWithdrawal` mutation diff --git a/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx b/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx index 03d62e700d..a88e6a1876 100644 --- a/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx @@ -16,7 +16,7 @@ Your Rafiki instance must be set up for at least one asset before wallet address There are a few ways in which you can create wallet addresses. - [Through a script](#create-wallet-addresses-through-a-script) -- [In response to the `wallet_address.not_found` webhook event](#create-wallet-addresses-in-response-to-a-webhook-event) +- [In response to the `WALLET_ADDRESS_NOT_FOUND` webhook event](#create-wallet-addresses-in-response-to-a-webhook-event) - [In the Rafiki Admin app](#create-and-manage-wallet-addresses-using-rafiki-admin) ### Create wallet addresses through a script @@ -116,7 +116,7 @@ We strongly recommend you store at least the `walletAddress.id` in your internal ### Create wallet addresses in response to a webhook event -The [`wallet_address.not_found`](/integration/requirements/webhook-events#wallet-address-not-found) event fires when a wallet address is requested through the Open Payments Get Wallet Address API, but Rafiki can't find the address. +The [`WALLET_ADDRESS_NOT_FOUND`](/integration/requirements/webhook-events#wallet-address-not-found) event fires when a wallet address is requested through the Open Payments Get Wallet Address API, but Rafiki can't find the address. When you receive the event, look up the associated account in your system, then call the `createWalletAddress` mutation to create a wallet address for the account. diff --git a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx index e2125e6a86..023cb62b06 100644 --- a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx @@ -50,7 +50,7 @@ The `id` in the webhook event payload is unique. Your system can use the ID to d ```json { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "type": "incoming_payment.created", + "type": "INCOMING_PAYMENT_CREATED", "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "walletAddressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", @@ -72,7 +72,7 @@ The `id` in the webhook event payload is unique. Your system can use the ID to d ```json { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "type": "outgoing_payment.created", + "type": "OUTGOING_PAYMENT_CREATED", "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "walletAddressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", @@ -99,7 +99,7 @@ The `id` in the webhook event payload is unique. Your system can use the ID to d ```json { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "type": "asset.liquidity_low", + "type": "ASSET_LIQUIDITY_LOW", "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "asset": { @@ -117,7 +117,7 @@ The `id` in the webhook event payload is unique. Your system can use the ID to d ```json { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "type": "wallet_address.not_found", + "type": "WALLET_ADDRESS_NOT_FOUND", "data": { "walletAddressUrl": "string" } @@ -154,9 +154,9 @@ The first retry is after 10 seconds. Additional retries occur after 20 more seco | Event type | Description | | ----------------------------------------------------------- | --------------------------------------------------------------------------------- | -| [`incoming_payment.created`](#incoming-payment-created) | An incoming payment has been created | -| [`incoming_payment.completed`](#incoming-payment-completed) | An incoming payment is complete and will not accept any additional incoming funds | -| [`incoming_payment.expired`](#incoming-payment-expired) | An incoming payment expired and will not accept any additional incoming funds | +| [`INCOMING_PAYMENT_CREATED`](#incoming-payment-created) | An incoming payment has been created | +| [`INCOMING_PAYMENT_COMPLETED`](#incoming-payment-completed) | An incoming payment is complete and will not accept any additional incoming funds | +| [`INCOMING_PAYMENT_EXPIRED`](#incoming-payment-expired) | An incoming payment expired and will not accept any additional incoming funds | #### Incoming payment created @@ -166,7 +166,7 @@ The first retry is after 10 seconds. Additional retries occur after 20 more seco participant R as Rafiki participant ASE as Account servicing entity - R->>ASE: Fires incoming_payment.created event to webhook endpoint + R->>ASE: Fires INCOMING_PAYMENT_CREATED event to webhook endpoint ASE->>ASE: No action required `} @@ -174,7 +174,7 @@ The first retry is after 10 seconds. Additional retries occur after 20 more seco -The `incoming_payment.created` event indicates an incoming payment was created. This event is purely informational because, at this point, the incoming payment has not received any funds and no actions around liquidity are required. You can use this event to display upcoming incoming payments to your users. +The `INCOMING_PAYMENT_CREATED` event indicates an incoming payment was created. This event is purely informational because, at this point, the incoming payment has not received any funds and no actions around liquidity are required. You can use this event to display upcoming incoming payments to your users. The incoming payment will either complete or expire. @@ -192,7 +192,7 @@ of $10 was completed. participant R as Rafiki participant ASE as Account servicing entity - R->>ASE: Fires incoming_payment.completed event to webhook endpoint,
receivedAmount: $10 + R->>ASE: Fires INCOMING_PAYMENT_COMPLETED event to webhook endpoint,
receivedAmount: $10 ASE->>R: Backend Admin API call: CreateIncomingPaymentWithdrawal ASE->>ASE: Credit recipient's account with $10 @@ -209,7 +209,7 @@ of $10 was completed. participant R as Rafiki participant ASE as Account servicing entity - R->>ASE: Fires incoming_payment.completed event to webhook endpoint,
receivedAmount: $10 + R->>ASE: Fires INCOMING_PAYMENT_COMPLETED event to webhook endpoint,
receivedAmount: $10 ASE->>R: Backend Admin API call: CreateIncomingPaymentWithdrawal ASE->>ASE: Credit recipient's account with $10 ASE->>R: Backend Admin API call: PostLiquidityWithdrawal @@ -220,7 +220,7 @@ of $10 was completed. -The `incoming_payment.completed` event indicates the payment completed either automatically or manually, and that any funds received into the incoming payment should be withdrawn and then credited to the recipient's account on your ledger. +The `INCOMING_PAYMENT_COMPLETED` event indicates the payment completed either automatically or manually, and that any funds received into the incoming payment should be withdrawn and then credited to the recipient's account on your ledger. #### Incoming payment expired @@ -232,7 +232,7 @@ The `incoming_payment.completed` event indicates the payment completed either au participant R as Rafiki participant ASE as Account servicing entity - R->>ASE: Fires incoming_payment.expired event to webhook endpoint,
receivedAmount: $2.55 + R->>ASE: Fires INCOMING_PAYMENT_EXPIRED event to webhook endpoint,
receivedAmount: $2.55 ASE->>R: Backend Admin API call: CreateIncomingPaymentWithdrawal ASE->>ASE: Credit recipient's account with $2.55 @@ -241,21 +241,21 @@ The `incoming_payment.completed` event indicates the payment completed either au -The `incoming_payment.expired` event will only fire if funds were received for the incoming payment. The event signals the end of any additional payments. +The `INCOMING_PAYMENT_EXPIRED` event will only fire if funds were received for the incoming payment. The event signals the end of any additional payments. The primary use case for this event is to know when a streaming payment, such as one supported through Web Monetization, has expired. In response to the event, any funds already received for the payment should be withdrawn and credited to the recipient's account on your ledger. :::note -In some scenarios, a sender may not have specified an `incomingAmount` when the incoming payment was created. Receiving an `incoming_payment.expired` event indicates that no further payments are expected. +In some scenarios, a sender may not have specified an `incomingAmount` when the incoming payment was created. Receiving an `INCOMING_PAYMENT_EXPIRED` event indicates that no further payments are expected. ::: ### Outgoing payments | Event type | Description | | ----------------------------------------------------------- | -------------------------------------------------- | -| [`outgoing_payment.created`](#outgoing-payment-created) | An outgoing payment has been created | -| [`outgoing_payment.completed`](#outgoing-payment-completed) | An outgoing payment has completed | -| [`outgoing_payment.failed`](#outgoing-payment-failed) | An outgoing payment partially or completely failed | +| [`OUTGOING_PAYMENT_CREATED`](#outgoing-payment-created) | An outgoing payment has been created | +| [`OUTGOING_PAYMENT_COMPLETED`](#outgoing-payment-completed) | An outgoing payment has completed | +| [`OUTGOING_PAYMENT_FAILED`](#outgoing-payment-failed) | An outgoing payment partially or completely failed | #### Outgoing payment created @@ -268,7 +268,7 @@ An outgoing payment for \$12 was created. participant R as Rafiki participant ASE as Account servicing entity - R->>ASE: Fires outgoing_payment.created event to webhook endpoint,
debitAmount: $12 + R->>ASE: Fires OUTGOING_PAYMENT_CREATED event to webhook endpoint,
debitAmount: $12 ASE->>ASE: Checks that sender's account has sufficient funds alt Account has sufficient funds ASE->>ASE: Put hold of $12 on sender's account @@ -282,7 +282,7 @@ An outgoing payment for \$12 was created. -The `outgoing_payment.created` event indicates an outgoing payment was created and is awaiting liquidity. Verify the sender's account balance and perform any other necessary verifications before funding the payment. +The `OUTGOING_PAYMENT_CREATED` event indicates an outgoing payment was created and is awaiting liquidity. Verify the sender's account balance and perform any other necessary verifications before funding the payment. If the sender has insufficient funds or if the payment should otherwise not be fulfilled, cancel the outgoing payment. Otherwise, put a hold on the sender's account and deposit the funds into Rafiki. @@ -300,7 +300,7 @@ for \$12 is complete. \$11.50 was sent. You choose to keep \$0.50 as a service f participant R as Rafiki participant ASE as Account servicing entity - R->>ASE: Fires outgoing_payment.completed event to webhook endpoint,
debitAmount: $12, sentAmount: $11.50 + R->>ASE: Fires OUTGOING_PAYMENT_COMPLETED event to webhook endpoint,
debitAmount: $12, sentAmount: $11.50 ASE->>R: Backend Admin API call: CreateOutgoingPaymentWithdrawal ASE->>ASE: Remove hold and deduct $12 from sender's account,
credit your account with $0.50 @@ -314,7 +314,7 @@ for \$12 is complete. \$11.50 was sent. You choose to keep \$0.50 as a service f participant R as Rafiki participant ASE as Account servicing entity - R->>ASE: Fires outgoing_payment.completed event to webhook endpoint,
debitAmount: $12, sentAmount: $11.50 + R->>ASE: Fires OUTGOING_PAYMENT_COMPLETED event to webhook endpoint,
debitAmount: $12, sentAmount: $11.50 ASE->>R: Backend Admin API call: CreateOutgoingPaymentWithdrawal ASE->>ASE: Remove hold and deduct $12 from sender's account,
credit your account with $0.50 ASE->>R: Backend Admin API call: PostLiquidityWithdrawal @@ -340,7 +340,7 @@ An outgoing payment for \$12 failed. \$8 was sent successfully. participant R as Rafiki participant ASE as Account servicing entity - R->>ASE: Fires outgoing_payment.failed event to webhook endpoint,
debitAmount: $12, sentAmount: $8 + R->>ASE: Fires OUTGOING_PAYMENT_FAILED event to webhook endpoint,
debitAmount: $12, sentAmount: $8 ASE->>R: Backend Admin API call: CreateOutgoingPaymentWithdrawal ASE->>ASE: Remove hold and deduct $8 from the sender's account @@ -349,14 +349,14 @@ An outgoing payment for \$12 failed. \$8 was sent successfully. -The `outgoing_payment.failed` event indicates that an outgoing payment has either partially or completely failed and a retry was unsuccessful. Withdraw any remaining liquidity from the outgoing payment in Rafiki. If the payment failed completely (the `sentAmount` is `0`), remove the hold from your sender's account. If the payment partially failed, remove the hold from your sender's account, then debit the sender's account on your ledger with the amount that was sent successfully. Since there will be a discrepancy between the quoted amount and the actual sent amount, we suggest you refrain from taking a sending fee. +The `OUTGOING_PAYMENT_FAILED` event indicates that an outgoing payment has either partially or completely failed and a retry was unsuccessful. Withdraw any remaining liquidity from the outgoing payment in Rafiki. If the payment failed completely (the `sentAmount` is `0`), remove the hold from your sender's account. If the payment partially failed, remove the hold from your sender's account, then debit the sender's account on your ledger with the amount that was sent successfully. Since there will be a discrepancy between the quoted amount and the actual sent amount, we suggest you refrain from taking a sending fee. ### Wallet addresses | Event type | Description | | --------------------------------------------------------------------- | ------------------------------------------------------------------ | -| [`wallet_address.not_found`](#wallet-address-not-found) | The requested wallet address was not found on this Rafiki instance | -| [`wallet_address.web_monetization`](#wallet-address-web-monetization) | Web Monetization payments have been received via STREAM | +| [`WALLET_ADDRESS_NOT_FOUND`](#wallet-address-not-found) | The requested wallet address was not found on this Rafiki instance | +| [`WALLET_ADDRESS_WEB_MONETIZATION`](#wallet-address-web-monetization) | Web Monetization payments have been received via STREAM | #### Wallet address not found @@ -369,7 +369,7 @@ The wallet address, `https://wallet.example.com/carla_garcia` was requested but participant R as Rafiki participant ASE as Account servicing entity - R->>ASE: Fires wallet_address.not_found event to webhook endpoint,
wallet address: https://wallet.example.com/carla_garcia + R->>ASE: Fires WALLET_ADDRESS_NOT_FOUND event to webhook endpoint,
wallet address: https://wallet.example.com/carla_garcia ASE->>R: Backend Admin API call: CreateWalletAddress,
url: https://wallet.example.com/carla_garcia,
public name: Carla Eva Garcia `} @@ -377,7 +377,7 @@ The wallet address, `https://wallet.example.com/carla_garcia` was requested but -The `wallet_address.not_found` event indicates that a wallet address was requested via the Open Payments wallet address API call, but the address doesn’t exist in your Rafiki instance. +The `WALLET_ADDRESS_NOT_FOUND` event indicates that a wallet address was requested via the Open Payments wallet address API call, but the address doesn’t exist in your Rafiki instance. When you receive this event, look up the associated account in your system and create a wallet address for the account. The initial wallet address request will succeed if you create it within your configured `WALLET_ADDRESS_LOOKUP_TIMEOUT_MS` time frame. @@ -396,7 +396,7 @@ A wallet address received a Web Monetization payment of \$0.33 participant R as Rafiki participant ASE as Account servicing entity - R->>ASE: Fires wallet_address.web_monetization event to webhook endpoint,
receivedAmount: $0.33 + R->>ASE: Fires WALLET_ADDRESS_WEB_MONETIZATION event to webhook endpoint,
receivedAmount: $0.33 ASE->>R: Backend Admin API call: CreateWalletAddressWithdrawal ASE->>ASE: Credit recipient's account with $0.33 @@ -405,13 +405,13 @@ A wallet address received a Web Monetization payment of \$0.33 -The `wallet_address.web_monetization` event indicates that a wallet address received Web Monetization payments via the ILP STREAM protocol. Withdraw the liquidity from the wallet address in Rafiki and credit the recipient's account on your ledger. +The `WALLET_ADDRESS_WEB_MONETIZATION` event indicates that a wallet address received Web Monetization payments via the ILP STREAM protocol. Withdraw the liquidity from the wallet address in Rafiki and credit the recipient's account on your ledger. ### Low asset liquidity | Event type | Description | | --------------------------------------------- | ------------------------------------------------------------- | -| [`asset.liquidity_low`](#asset-liquidity-low) | Your asset liquidity has dropped below your defined threshold | +| [`ASSET_LIQUIDITY_LOW`](#asset-liquidity-low) | Your asset liquidity has dropped below your defined threshold | #### Asset liquidity low @@ -424,7 +424,7 @@ Your asset liquidity for USD (asset scale: 2) drops below \$100.00. participant R as Rafiki participant ASE as Account servicing entity - R->>ASE: Fires asset.liquidity_low event to webhook endpoint,
asset: USD (scale: 2, id: "abc") + R->>ASE: Fires ASSET_LIQUIDITY_LOW event to webhook endpoint,
asset: USD (scale: 2, id: "abc") ASE->>R: Backend Admin API call: DepositAssetLiquidity `} @@ -432,13 +432,13 @@ Your asset liquidity for USD (asset scale: 2) drops below \$100.00. -The `asset.liquidity_low` event indicates that an asset's liquidity has dropped below your predefined liquidity threshold. Check if you already have, or can acquire, additional liquidity for that specific asset. If so, deposit it in Rafiki. Cross-currency transfers will fail if you don't increase the asset's liquidity. +The `ASSET_LIQUIDITY_LOW` event indicates that an asset's liquidity has dropped below your predefined liquidity threshold. Check if you already have, or can acquire, additional liquidity for that specific asset. If so, deposit it in Rafiki. Cross-currency transfers will fail if you don't increase the asset's liquidity. ### Low peer liquidity | Event type | Description | | ------------------------------------------- | ------------------------------------------------------------ | -| [`peer.liquidity_low`](#peer-liquidity-low) | Your peer liquidity has dropped below your defined threshold | +| [`PEER_LIQUIDITY_LOW`](#peer-liquidity-low) | Your peer liquidity has dropped below your defined threshold | #### Peer liquidity low @@ -451,7 +451,7 @@ The liquidity for your peer, Happy Life Bank, drops below \$100.00 USD. participant R as Rafiki participant ASE as Account servicing entity - R->>ASE: Fires peer.liquidity_low event to webhook endpoint,
peer: Happy Life Bank (asset: "USD", scale: 2, id: "abc") + R->>ASE: Fires PEER_LIQUIDITY_LOW event to webhook endpoint,
peer: Happy Life Bank (asset: "USD", scale: 2, id: "abc") ASE->>R: Backend Admin API call: DepositPeerLiquidity `} @@ -459,4 +459,4 @@ The liquidity for your peer, Happy Life Bank, drops below \$100.00 USD. -The `peer.liquidity_low` event indicates that a peer's liquidity has dropped below your predefined liquidity threshold. Decide whether you want to extend the peer's credit line or if your peer must settle before you will extend a new line of credit. If you cannot or do not increase the peer liquidity in Rafiki, transfers to that peer will fail. +The `PEER_LIQUIDITY_LOW` event indicates that a peer's liquidity has dropped below your predefined liquidity threshold. Decide whether you want to extend the peer's credit line or if your peer must settle before you will extend a new line of credit. If you cannot or do not increase the peer liquidity in Rafiki, transfers to that peer will fail. diff --git a/packages/documentation/src/content/docs/resources/webhook-event-types.mdx b/packages/documentation/src/content/docs/resources/webhook-event-types.mdx index 4291ef7fa9..d07bbaf694 100644 --- a/packages/documentation/src/content/docs/resources/webhook-event-types.mdx +++ b/packages/documentation/src/content/docs/resources/webhook-event-types.mdx @@ -8,13 +8,13 @@ The following is an enumeration of all of Rafiki's [webhook event](/integration/ | Value | Description | | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -| [`incoming_payment.created`](/integration/requirements/webhook-events/#incoming-payment-created) | An incoming payment has been created. | -| [`incoming_payment.completed`](/integration/requirements/webhook-events/#incoming-payment-completed) | An incoming payment is complete and will not accept any additional incoming funds. | -| [`incoming_payment.expired`](/integration/requirements/webhook-events/#incoming-payment-expired) | An incoming payment expired and will not accept any additional incoming funds. | -| [`outgoing_payment.created`](/integration/requirements/webhook-events/#outgoing-payment-created) | An outgoing payment was created. | -| [`outgoing_payment.completed`](/integration/requirements/webhook-events/#outgoing-payment-completed) | An outgoing payment completed. | -| [`outgoing_payment.failed`](/integration/requirements/webhook-events/#outgoing-payment-failed) | An outgoing payment partially or completely failed. | -| [`wallet_address.not_found`](/integration/requirements/webhook-events/#wallet-address-not-found) | A requested wallet address was not found. | -| [`wallet_address.web_monetization`](/integration/requirements/webhook-events/#wallet-address-web-monetization) | Web Monetization payments received via STREAM. | -| [`asset.liquidity_low`](/integration/requirements/webhook-events/#asset-liquidity-low) | Asset liquidity has dropped below defined threshold. | -| [`peer.liquidity_low`](/integration/requirements/webhook-events/#peer-liquidity-low) | Peer liquidity has dropped below defined threshold. | +| [`INCOMING_PAYMENT_CREATED`](/integration/requirements/webhook-events/#incoming-payment-created) | An incoming payment has been created. | +| [`INCOMING_PAYMENT_COMPLETED`](/integration/requirements/webhook-events/#incoming-payment-completed) | An incoming payment is complete and will not accept any additional incoming funds. | +| [`INCOMING_PAYMENT_EXPIRED`](/integration/requirements/webhook-events/#incoming-payment-expired) | An incoming payment expired and will not accept any additional incoming funds. | +| [`OUTGOING_PAYMENT_CREATED`](/integration/requirements/webhook-events/#outgoing-payment-created) | An outgoing payment was created. | +| [`OUTGOING_PAYMENT_COMPLETED`](/integration/requirements/webhook-events/#outgoing-payment-completed) | An outgoing payment completed. | +| [`OUTGOING_PAYMENT_FAILED`](/integration/requirements/webhook-events/#outgoing-payment-failed) | An outgoing payment partially or completely failed. | +| [`WALLET_ADDRESS_NOT_FOUND`](/integration/requirements/webhook-events/#wallet-address-not-found) | A requested wallet address was not found. | +| [`WALLET_ADDRESS_WEB_MONETIZATION`](/integration/requirements/webhook-events/#wallet-address-web-monetization) | Web Monetization payments received via STREAM. | +| [`ASSET_LIQUIDITY_LOW`](/integration/requirements/webhook-events/#asset-liquidity-low) | Asset liquidity has dropped below defined threshold. | +| [`PEER_LIQUIDITY_LOW`](/integration/requirements/webhook-events/#peer-liquidity-low) | Peer liquidity has dropped below defined threshold. | diff --git a/packages/frontend/app/generated/graphql.ts b/packages/frontend/app/generated/graphql.ts index 2d35119c43..dcf22bbd76 100644 --- a/packages/frontend/app/generated/graphql.ts +++ b/packages/frontend/app/generated/graphql.ts @@ -1580,7 +1580,7 @@ export type WebhookEvent = Model & { /** Unique identifier of the webhook event. */ id: Scalars['ID']['output']; /** Type of webhook event. */ - type: Scalars['String']['output']; + type: WebhookEventType; }; export type WebhookEventFilter = { @@ -1588,6 +1588,29 @@ export type WebhookEventFilter = { type?: InputMaybe; }; +export enum WebhookEventType { + /** Asset liquidity is low */ + AssetLiquidityLow = 'ASSET_LIQUIDITY_LOW', + /** An incoming payment was completed */ + IncomingPaymentCompleted = 'INCOMING_PAYMENT_COMPLETED', + /** An incoming payment was created */ + IncomingPaymentCreated = 'INCOMING_PAYMENT_CREATED', + /** An incoming payment has expired */ + IncomingPaymentExpired = 'INCOMING_PAYMENT_EXPIRED', + /** An outgoing payment was completed */ + OutgoingPaymentCompleted = 'OUTGOING_PAYMENT_COMPLETED', + /** An outgoing payment was created */ + OutgoingPaymentCreated = 'OUTGOING_PAYMENT_CREATED', + /** An outgoing payment has failed and won't be retried */ + OutgoingPaymentFailed = 'OUTGOING_PAYMENT_FAILED', + /** Peer liquidity is low */ + PeerLiquidityLow = 'PEER_LIQUIDITY_LOW', + /** Wallet address was not found and it will be created if there exists a corresponding account */ + WalletAddressNotFound = 'WALLET_ADDRESS_NOT_FOUND', + /** A Web Monetization payment was created */ + WalletAddressWebMonetization = 'WALLET_ADDRESS_WEB_MONETIZATION' +} + export type WebhookEventsConnection = { __typename?: 'WebhookEventsConnection'; /** A list of webhook event edges, containing event nodes and cursors for pagination. */ @@ -1812,6 +1835,7 @@ export type ResolversTypes = { WalletAddressesConnection: ResolverTypeWrapper>; WebhookEvent: ResolverTypeWrapper>; WebhookEventFilter: ResolverTypeWrapper>; + WebhookEventType: ResolverTypeWrapper>; WebhookEventsConnection: ResolverTypeWrapper>; WebhookEventsEdge: ResolverTypeWrapper>; WithdrawEventLiquidityInput: ResolverTypeWrapper>; @@ -2429,7 +2453,7 @@ export type WebhookEventResolvers; data?: Resolver; id?: Resolver; - type?: Resolver; + type?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; @@ -2731,4 +2755,4 @@ export type ListWebhookEventsVariables = Exact<{ }>; -export type ListWebhookEvents = { __typename?: 'Query', webhookEvents: { __typename?: 'WebhookEventsConnection', edges: Array<{ __typename?: 'WebhookEventsEdge', cursor: string, node: { __typename?: 'WebhookEvent', id: string, data: any, type: string, createdAt: string } }>, pageInfo: { __typename?: 'PageInfo', startCursor?: string | null, endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean } } }; +export type ListWebhookEvents = { __typename?: 'Query', webhookEvents: { __typename?: 'WebhookEventsConnection', edges: Array<{ __typename?: 'WebhookEventsEdge', cursor: string, node: { __typename?: 'WebhookEvent', id: string, data: any, type: WebhookEventType, createdAt: string } }>, pageInfo: { __typename?: 'PageInfo', startCursor?: string | null, endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean } } }; diff --git a/packages/frontend/app/routes/webhook-events.tsx b/packages/frontend/app/routes/webhook-events.tsx index f23ad24004..7e91c8f536 100644 --- a/packages/frontend/app/routes/webhook-events.tsx +++ b/packages/frontend/app/routes/webhook-events.tsx @@ -107,8 +107,8 @@ export default function WebhookEventsPage() { }, ...Object.values(WebhookEventType).map((value) => ({ name: - value.charAt(0).toUpperCase() + - value.slice(1).replace(/[_.]/g, ' '), + value.charAt(0) + + value.toLowerCase().slice(1).replace(/[_]/g, ' '), value: value, action: () => { setTypeFilterParams(value) diff --git a/packages/frontend/app/shared/enums.ts b/packages/frontend/app/shared/enums.ts index b9d80978a9..e06452585a 100644 --- a/packages/frontend/app/shared/enums.ts +++ b/packages/frontend/app/shared/enums.ts @@ -1,11 +1,12 @@ export enum WebhookEventType { - IncomingPaymentCreated = 'incoming_payment.created', - IncomingPaymentCompleted = 'incoming_payment.completed', - IncomingPaymentExpired = 'incoming_payment.expired', - OutgoingPaymentCreated = 'outgoing_payment.created', - OutgoingPaymentCompleted = 'outgoing_payment.completed', - OutgoingPaymentFailed = 'outgoing_payment.failed', - WalletAddressNotFound = 'wallet_address.not_found', - AssetLiquidityLow = 'asset.liquidity_low', - PeerLiquidityLow = 'peer.liquidity_low' + IncomingPaymentCreated = 'INCOMING_PAYMENT_CREATED', + IncomingPaymentCompleted = 'INCOMING_PAYMENT_COMPLETED', + IncomingPaymentExpired = 'INCOMING_PAYMENT_EXPIRED', + OutgoingPaymentCreated = 'OUTGOING_PAYMENT_CREATED', + OutgoingPaymentCompleted = 'OUTGOING_PAYMENT_COMPLETED', + OutgoingPaymentFailed = 'OUTGOING_PAYMENT_FAILED', + WalletAddressWebMonetization = 'WALLET_ADDRESS_WEB_MONETIZATION', + WalletAddressNotFound = 'WALLET_ADDRESS_NOT_FOUND', + AssetLiquidityLow = 'ASSET_LIQUIDITY_LOW', + PeerLiquidityLow = 'PEER_LIQUIDITY_LOW' } diff --git a/packages/mock-account-service-lib/src/generated/graphql.ts b/packages/mock-account-service-lib/src/generated/graphql.ts index 131de7eb44..21d71433a0 100644 --- a/packages/mock-account-service-lib/src/generated/graphql.ts +++ b/packages/mock-account-service-lib/src/generated/graphql.ts @@ -1580,7 +1580,7 @@ export type WebhookEvent = Model & { /** Unique identifier of the webhook event. */ id: Scalars['ID']['output']; /** Type of webhook event. */ - type: Scalars['String']['output']; + type: WebhookEventType; }; export type WebhookEventFilter = { @@ -1588,6 +1588,29 @@ export type WebhookEventFilter = { type?: InputMaybe; }; +export enum WebhookEventType { + /** Asset liquidity is low */ + AssetLiquidityLow = 'ASSET_LIQUIDITY_LOW', + /** An incoming payment was completed */ + IncomingPaymentCompleted = 'INCOMING_PAYMENT_COMPLETED', + /** An incoming payment was created */ + IncomingPaymentCreated = 'INCOMING_PAYMENT_CREATED', + /** An incoming payment has expired */ + IncomingPaymentExpired = 'INCOMING_PAYMENT_EXPIRED', + /** An outgoing payment was completed */ + OutgoingPaymentCompleted = 'OUTGOING_PAYMENT_COMPLETED', + /** An outgoing payment was created */ + OutgoingPaymentCreated = 'OUTGOING_PAYMENT_CREATED', + /** An outgoing payment has failed and won't be retried */ + OutgoingPaymentFailed = 'OUTGOING_PAYMENT_FAILED', + /** Peer liquidity is low */ + PeerLiquidityLow = 'PEER_LIQUIDITY_LOW', + /** Wallet address was not found and it will be created if there exists a corresponding account */ + WalletAddressNotFound = 'WALLET_ADDRESS_NOT_FOUND', + /** A Web Monetization payment was created */ + WalletAddressWebMonetization = 'WALLET_ADDRESS_WEB_MONETIZATION' +} + export type WebhookEventsConnection = { __typename?: 'WebhookEventsConnection'; /** A list of webhook event edges, containing event nodes and cursors for pagination. */ @@ -1812,6 +1835,7 @@ export type ResolversTypes = { WalletAddressesConnection: ResolverTypeWrapper>; WebhookEvent: ResolverTypeWrapper>; WebhookEventFilter: ResolverTypeWrapper>; + WebhookEventType: ResolverTypeWrapper>; WebhookEventsConnection: ResolverTypeWrapper>; WebhookEventsEdge: ResolverTypeWrapper>; WithdrawEventLiquidityInput: ResolverTypeWrapper>; @@ -2429,7 +2453,7 @@ export type WebhookEventResolvers; data?: Resolver; id?: Resolver; - type?: Resolver; + type?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/packages/mock-account-service-lib/src/types.ts b/packages/mock-account-service-lib/src/types.ts index 76c90bfda8..14d47476dd 100644 --- a/packages/mock-account-service-lib/src/types.ts +++ b/packages/mock-account-service-lib/src/types.ts @@ -57,14 +57,14 @@ export interface Webhook { } export enum WebhookEventType { - IncomingPaymentCreated = 'incoming_payment.created', - IncomingPaymentCompleted = 'incoming_payment.completed', - IncomingPaymentExpired = 'incoming_payment.expired', - OutgoingPaymentCreated = 'outgoing_payment.created', - OutgoingPaymentCompleted = 'outgoing_payment.completed', - OutgoingPaymentFailed = 'outgoing_payment.failed', - WalletAddressWebMonetization = 'wallet_address.web_monetization', - WalletAddressNotFound = 'wallet_address.not_found', - AssetLiquidityLow = 'asset.liquidity_low', - PeerLiquidityLow = 'peer.liquidity_low' + IncomingPaymentCreated = 'INCOMING_PAYMENT_CREATED', + IncomingPaymentCompleted = 'INCOMING_PAYMENT_COMPLETED', + IncomingPaymentExpired = 'INCOMING_PAYMENT_EXPIRED', + OutgoingPaymentCreated = 'OUTGOING_PAYMENT_CREATED', + OutgoingPaymentCompleted = 'OUTGOING_PAYMENT_COMPLETED', + OutgoingPaymentFailed = 'OUTGOING_PAYMENT_FAILED', + WalletAddressWebMonetization = 'WALLET_ADDRESS_WEB_MONETIZATION', + WalletAddressNotFound = 'WALLET_ADDRESS_NOT_FOUND', + AssetLiquidityLow = 'ASSET_LIQUIDITY_LOW', + PeerLiquidityLow = 'PEER_LIQUIDITY_LOW' } diff --git a/test/integration/lib/generated/graphql.ts b/test/integration/lib/generated/graphql.ts index 131de7eb44..21d71433a0 100644 --- a/test/integration/lib/generated/graphql.ts +++ b/test/integration/lib/generated/graphql.ts @@ -1580,7 +1580,7 @@ export type WebhookEvent = Model & { /** Unique identifier of the webhook event. */ id: Scalars['ID']['output']; /** Type of webhook event. */ - type: Scalars['String']['output']; + type: WebhookEventType; }; export type WebhookEventFilter = { @@ -1588,6 +1588,29 @@ export type WebhookEventFilter = { type?: InputMaybe; }; +export enum WebhookEventType { + /** Asset liquidity is low */ + AssetLiquidityLow = 'ASSET_LIQUIDITY_LOW', + /** An incoming payment was completed */ + IncomingPaymentCompleted = 'INCOMING_PAYMENT_COMPLETED', + /** An incoming payment was created */ + IncomingPaymentCreated = 'INCOMING_PAYMENT_CREATED', + /** An incoming payment has expired */ + IncomingPaymentExpired = 'INCOMING_PAYMENT_EXPIRED', + /** An outgoing payment was completed */ + OutgoingPaymentCompleted = 'OUTGOING_PAYMENT_COMPLETED', + /** An outgoing payment was created */ + OutgoingPaymentCreated = 'OUTGOING_PAYMENT_CREATED', + /** An outgoing payment has failed and won't be retried */ + OutgoingPaymentFailed = 'OUTGOING_PAYMENT_FAILED', + /** Peer liquidity is low */ + PeerLiquidityLow = 'PEER_LIQUIDITY_LOW', + /** Wallet address was not found and it will be created if there exists a corresponding account */ + WalletAddressNotFound = 'WALLET_ADDRESS_NOT_FOUND', + /** A Web Monetization payment was created */ + WalletAddressWebMonetization = 'WALLET_ADDRESS_WEB_MONETIZATION' +} + export type WebhookEventsConnection = { __typename?: 'WebhookEventsConnection'; /** A list of webhook event edges, containing event nodes and cursors for pagination. */ @@ -1812,6 +1835,7 @@ export type ResolversTypes = { WalletAddressesConnection: ResolverTypeWrapper>; WebhookEvent: ResolverTypeWrapper>; WebhookEventFilter: ResolverTypeWrapper>; + WebhookEventType: ResolverTypeWrapper>; WebhookEventsConnection: ResolverTypeWrapper>; WebhookEventsEdge: ResolverTypeWrapper>; WithdrawEventLiquidityInput: ResolverTypeWrapper>; @@ -2429,7 +2453,7 @@ export type WebhookEventResolvers; data?: Resolver; id?: Resolver; - type?: Resolver; + type?: Resolver; __isTypeOf?: IsTypeOfResolverFn; };