From 42fc9b7e42ab88e94c395d18da3428b7396803a6 Mon Sep 17 00:00:00 2001 From: Nathan Lie Date: Tue, 20 Aug 2024 10:42:34 +0200 Subject: [PATCH] feat(backend): outgoing payments page resolver --- .../Cancel Outgoing Payment.bru | 2 +- .../Create Asset Liquidity Withdrawal.bru | 2 +- .../Create Incoming Payment Withdrawal.bru | 2 +- .../Create Incoming Payment.bru | 4 +- .../Create Or Update Peer By Url.bru | 4 +- ...Outgoing Payment From Incoming Payment.bru | 4 +- .../Create Outgoing Payment Withdrawal.bru | 2 +- .../Create Outgoing Payment.bru | 4 +- .../Create Peer Liquidity Withdrawal.bru | 2 +- .../Rafiki/Rafiki Admin APIs/Create Peer.bru | 4 +- .../Rafiki/Rafiki Admin APIs/Create Quote.bru | 4 +- ...ate Receiver (remote Incoming Payment).bru | 4 +- .../Create Wallet Address Key.bru | 4 +- .../Create Wallet Address Withdrawal.bru | 2 +- .../Create Wallet Address.bru | 4 +- .../Rafiki/Rafiki Admin APIs/Delete Asset.bru | 2 +- .../Rafiki/Rafiki Admin APIs/Delete Peer.bru | 2 +- .../Deposit Asset Liquidity.bru | 2 +- .../Deposit Event Liquidity.bru | 2 +- .../Deposit Outgoing Payment Liquidity.bru | 2 +- .../Deposit Peer Liquidity.bru | 2 +- .../Get Accounting Ledger Transfers.bru | 2 +- .../Rafiki/Rafiki Admin APIs/Get Asset.bru | 2 +- .../Rafiki/Rafiki Admin APIs/Get Assets.bru | 2 +- .../Get Incoming Payment.bru | 2 +- .../Get Outgoing Payment.bru | 2 +- .../Get Outgoing Payments.bru | 31 +++ .../Rafiki/Rafiki Admin APIs/Get Payments.bru | 2 +- .../Rafiki/Rafiki Admin APIs/Get Peer.bru | 2 +- .../Rafiki/Rafiki Admin APIs/Get Peers.bru | 2 +- .../Rafiki/Rafiki Admin APIs/Get Quote.bru | 2 +- ...Get Receiver (remote Incoming Payment).bru | 2 +- ...t Wallet Address Additional Properties.bru | 2 +- .../Rafiki Admin APIs/Get Wallet Address.bru | 2 +- .../Get Wallet Addresses Keys.bru | 2 +- .../Get Wallet Addresses.bru | 2 +- .../Rafiki Admin APIs/Get Webhook Events.bru | 2 +- .../Post Liquidity Withdrawal.bru | 2 +- .../Revoke Wallet Address Key.bru | 2 +- .../Rafiki/Rafiki Admin APIs/Set Fee.bru | 2 +- .../Trigger Wallet Address Events.bru | 2 +- .../Rafiki/Rafiki Admin APIs/Update Asset.bru | 2 +- .../Rafiki/Rafiki Admin APIs/Update Peer.bru | 2 +- .../Update Wallet Address.bru | 2 +- .../Void Liquidity Withdrawal.bru | 2 +- .../Withdraw Event Liquidity.bru | 2 +- .../generated/graphql.ts | 21 ++ .../src/graphql/generated/graphql.schema.json | 136 +++++++++++ .../backend/src/graphql/generated/graphql.ts | 21 ++ .../backend/src/graphql/resolvers/index.ts | 2 + .../resolvers/outgoing_payment.test.ts | 230 +++++++++++++++++- .../src/graphql/resolvers/outgoing_payment.ts | 35 +++ packages/backend/src/graphql/schema.graphql | 22 ++ .../payment/outgoing/service.test.ts | 123 ++++++++++ .../open_payments/payment/outgoing/service.ts | 55 +++++ packages/frontend/app/generated/graphql.ts | 21 ++ .../src/generated/graphql.ts | 21 ++ test/integration/lib/generated/graphql.ts | 21 ++ 58 files changed, 792 insertions(+), 55 deletions(-) create mode 100644 bruno/collections/Rafiki/Rafiki Admin APIs/Get Outgoing Payments.bru diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Cancel Outgoing Payment.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Cancel Outgoing Payment.bru index 9d3dc30452..5e588f9200 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Cancel Outgoing Payment.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Cancel Outgoing Payment.bru @@ -1,7 +1,7 @@ meta { name: Cancel Outgoing Payment type: graphql - seq: 43 + seq: 46 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Asset Liquidity Withdrawal.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Asset Liquidity Withdrawal.bru index 552163a6ea..23a55ff02c 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Asset Liquidity Withdrawal.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Asset Liquidity Withdrawal.bru @@ -1,7 +1,7 @@ meta { name: Withdraw Asset Liquidity type: graphql - seq: 6 + seq: 7 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Incoming Payment Withdrawal.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Incoming Payment Withdrawal.bru index 3c8243c573..bdef51025b 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Incoming Payment Withdrawal.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Incoming Payment Withdrawal.bru @@ -1,7 +1,7 @@ meta { name: Withdraw Incoming Payment Liquidity type: graphql - seq: 26 + seq: 28 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Incoming Payment.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Incoming Payment.bru index 444f62c4ac..209041819c 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Incoming Payment.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Incoming Payment.bru @@ -1,7 +1,7 @@ meta { name: Create Incoming Payment type: graphql - seq: 24 + seq: 26 } post { @@ -57,7 +57,7 @@ script:pre-request { script:post-response { const body = res.getBody(); - + if (body?.data) { bru.setEnvVar("incomingPaymentId", body.data.createIncomingPayment.payment.id); } diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Or Update Peer By Url.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Or Update Peer By Url.bru index 4d50a9f96b..852c95810c 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Or Update Peer By Url.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Or Update Peer By Url.bru @@ -1,7 +1,7 @@ meta { name: Create Or Update Peer By Url type: graphql - seq: 9 + seq: 10 } post { @@ -45,7 +45,7 @@ script:pre-request { script:post-response { const body = res.getBody(); - + if (body?.data) { bru.setEnvVar("peerId", body.data.createOrUpdatePeerByUrl?.peer.id); } diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment From Incoming Payment.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment From Incoming Payment.bru index ea46af7d07..6c324e3cd5 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment From Incoming Payment.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment From Incoming Payment.bru @@ -1,7 +1,7 @@ meta { name: Create Outgoing Payment From Incoming Payment type: graphql - seq: 32 + seq: 34 } post { @@ -89,7 +89,7 @@ script:pre-request { script:post-response { const body = res.getBody(); - + if (body?.data) { bru.setEnvVar("outgoingPaymentId", body.data.createOutgoingPaymentFromIncomingPayment.payment.id); } diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment Withdrawal.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment Withdrawal.bru index 2e063f382f..8d29298e38 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment Withdrawal.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment Withdrawal.bru @@ -1,7 +1,7 @@ meta { name: Withdraw Outgoing Payment Liquidity type: graphql - seq: 37 + seq: 40 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment.bru index ab0cf3c740..d8765744b8 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Outgoing Payment.bru @@ -1,7 +1,7 @@ meta { name: Create Outgoing Payment type: graphql - seq: 31 + seq: 33 } post { @@ -84,7 +84,7 @@ script:pre-request { script:post-response { const body = res.getBody(); - + if (body?.data) { bru.setEnvVar("outgoingPaymentId", body.data.createOutgoingPayment.payment.id); } diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Peer Liquidity Withdrawal.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Peer Liquidity Withdrawal.bru index d4fcc195a9..1f92ec80f2 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Peer Liquidity Withdrawal.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Peer Liquidity Withdrawal.bru @@ -1,7 +1,7 @@ meta { name: Withdraw Peer Liquidity type: graphql - seq: 15 + seq: 16 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Peer.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Peer.bru index 592c6d093d..d0d957d65b 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Peer.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Peer.bru @@ -1,7 +1,7 @@ meta { name: Create Peer type: graphql - seq: 8 + seq: 9 } post { @@ -54,7 +54,7 @@ script:pre-request { script:post-response { const body = res.getBody(); - + if (body?.data) { bru.setEnvVar("peerId", body.data.createPeer.peer.id); } diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Quote.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Quote.bru index dc52218dc1..5e81e19afb 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Quote.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Quote.bru @@ -1,7 +1,7 @@ meta { name: Create Quote type: graphql - seq: 29 + seq: 31 } post { @@ -87,7 +87,7 @@ script:pre-request { script:post-response { const body = res.getBody(); - + if (body?.data) { bru.setEnvVar("quoteId", body.data.createQuote.quote.id); } diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Receiver (remote Incoming Payment).bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Receiver (remote Incoming Payment).bru index 8c1639167f..eac28fae8b 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Receiver (remote Incoming Payment).bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Receiver (remote Incoming Payment).bru @@ -1,7 +1,7 @@ meta { name: Create Receiver (remote Incoming Payment) type: graphql - seq: 27 + seq: 29 } post { @@ -62,7 +62,7 @@ script:pre-request { script:post-response { const body = res.getBody(); - + if (body?.data) { bru.setEnvVar("receiverId", body.data.createReceiver.receiver.id); } diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address Key.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address Key.bru index 3ff2375291..967749cc84 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address Key.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address Key.bru @@ -1,7 +1,7 @@ meta { name: Create Wallet Address Key type: graphql - seq: 22 + seq: 24 } post { @@ -53,7 +53,7 @@ script:pre-request { script:post-response { const body = res.getBody(); - + if (body?.data) { bru.setEnvVar("walletAddressKeyId", body.data.createWalletAddressKey.walletAddressKey.id); } diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address Withdrawal.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address Withdrawal.bru index 974d5b386a..951fc73170 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address Withdrawal.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address Withdrawal.bru @@ -1,7 +1,7 @@ meta { name: Create Wallet Address Withdrawal type: graphql - seq: 39 + seq: 42 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address.bru index 8fc0bf0e1a..3918a11c8b 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address.bru @@ -1,7 +1,7 @@ meta { name: Create Wallet Address type: graphql - seq: 18 + seq: 19 } post { @@ -61,7 +61,7 @@ script:pre-request { script:post-response { const body = res.getBody(); - + if (body?.data) { bru.setEnvVar("walletAddressId", body.data.createWalletAddress.walletAddress.id); bru.setEnvVar("walletAddressUrl", body.data.createWalletAddress.walletAddress.url); diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Delete Asset.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Delete Asset.bru index b35ceab5e9..d5fbaa9570 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Delete Asset.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Delete Asset.bru @@ -1,7 +1,7 @@ meta { name: Delete Asset type: graphql - seq: 43 + seq: 47 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Delete Peer.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Delete Peer.bru index 3abd964f40..6c9864d7d4 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Delete Peer.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Delete Peer.bru @@ -1,7 +1,7 @@ meta { name: Delete Peer type: graphql - seq: 13 + seq: 14 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Asset Liquidity.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Asset Liquidity.bru index 038029f179..0e1e4f3f5d 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Asset Liquidity.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Asset Liquidity.bru @@ -1,7 +1,7 @@ meta { name: Deposit Asset Liquidity type: graphql - seq: 5 + seq: 6 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Event Liquidity.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Event Liquidity.bru index 48751deca0..38784a407b 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Event Liquidity.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Event Liquidity.bru @@ -1,7 +1,7 @@ meta { name: Deposit Event Liquidity type: graphql - seq: 35 + seq: 38 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Outgoing Payment Liquidity.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Outgoing Payment Liquidity.bru index a02bc5b8c6..52bfd59951 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Outgoing Payment Liquidity.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Outgoing Payment Liquidity.bru @@ -1,7 +1,7 @@ meta { name: Deposit Outgoing Payment Liquidity type: graphql - seq: 36 + seq: 39 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Peer Liquidity.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Peer Liquidity.bru index 3b2ae41c07..18c64e0e8a 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Peer Liquidity.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Deposit Peer Liquidity.bru @@ -1,7 +1,7 @@ meta { name: Deposit Peer Liquidity type: graphql - seq: 14 + seq: 15 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Accounting Ledger Transfers.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Accounting Ledger Transfers.bru index 37efc9760a..e1322e309d 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Accounting Ledger Transfers.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Accounting Ledger Transfers.bru @@ -1,7 +1,7 @@ meta { name: Get Accounting Ledger Transfers type: graphql - seq: 1 + seq: 2 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Asset.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Asset.bru index c081990cf2..44f7dc482b 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Asset.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Asset.bru @@ -1,7 +1,7 @@ meta { name: Get Asset type: graphql - seq: 3 + seq: 4 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Assets.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Assets.bru index f5df6ffcda..e1198c5cd6 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Assets.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Assets.bru @@ -1,7 +1,7 @@ meta { name: Get Assets type: graphql - seq: 4 + seq: 5 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Incoming Payment.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Incoming Payment.bru index 2c1021dd24..f7b95f570d 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Incoming Payment.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Incoming Payment.bru @@ -1,7 +1,7 @@ meta { name: Get Incoming Payment type: graphql - seq: 25 + seq: 27 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Outgoing Payment.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Outgoing Payment.bru index ae9cd5ff47..9f907c46df 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Outgoing Payment.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Outgoing Payment.bru @@ -1,7 +1,7 @@ meta { name: Get Outgoing Payment type: graphql - seq: 33 + seq: 35 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Outgoing Payments.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Outgoing Payments.bru new file mode 100644 index 0000000000..73de44d6b8 --- /dev/null +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Outgoing Payments.bru @@ -0,0 +1,31 @@ +meta { + name: Get Outgoing Payments + type: graphql + seq: 36 +} + +post { + url: {{RafikiGraphqlHost}}/graphql + body: graphql + auth: none +} + +body:graphql { + query OutgoingPayments($filter: OutgoingPaymentFilter) { + outgoingPayments(filter: $filter) { + edges { + node { + id + client + } + cursor + } + } + } +} + +script:pre-request { + const scripts = require('./scripts'); + + scripts.addApiSignatureHeader(); +} diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Payments.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Payments.bru index 0bd1d1f5f0..d1834b4477 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Payments.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Payments.bru @@ -1,7 +1,7 @@ meta { name: Get Payments type: graphql - seq: 34 + seq: 37 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Peer.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Peer.bru index a062ae3bd5..bc035ba90f 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Peer.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Peer.bru @@ -1,7 +1,7 @@ meta { name: Get Peer type: graphql - seq: 11 + seq: 12 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Peers.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Peers.bru index df7be544e3..482fa702ba 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Peers.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Peers.bru @@ -1,7 +1,7 @@ meta { name: Get Peers type: graphql - seq: 12 + seq: 13 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Quote.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Quote.bru index 2d92319182..cf5402f45a 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Quote.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Quote.bru @@ -1,7 +1,7 @@ meta { name: Get Quote type: graphql - seq: 30 + seq: 32 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Receiver (remote Incoming Payment).bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Receiver (remote Incoming Payment).bru index 675fea10e4..8cdd2582fa 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Receiver (remote Incoming Payment).bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Receiver (remote Incoming Payment).bru @@ -1,7 +1,7 @@ meta { name: Get Receiver (remote Incoming Payment) type: graphql - seq: 28 + seq: 30 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Address Additional Properties.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Address Additional Properties.bru index a9f64bf244..9b9a583def 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Address Additional Properties.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Address Additional Properties.bru @@ -1,7 +1,7 @@ meta { name: Get Wallet Address Additional Properties type: graphql - seq: 21 + seq: 22 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Address.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Address.bru index d8142500ff..2cd1addccc 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Address.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Address.bru @@ -1,7 +1,7 @@ meta { name: Get Wallet Address type: graphql - seq: 20 + seq: 21 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses Keys.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses Keys.bru index 4848994b7c..e8ed641928 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses Keys.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses Keys.bru @@ -1,7 +1,7 @@ meta { name: Get Wallet Addresses Keys type: graphql - seq: 42 + seq: 45 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses.bru index bc839a064b..462d4e81d5 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses.bru @@ -1,7 +1,7 @@ meta { name: Get Wallet Addresses type: graphql - seq: 21 + seq: 23 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Webhook Events.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Webhook Events.bru index dc35f8c68d..0f94309c0f 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Webhook Events.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Webhook Events.bru @@ -1,7 +1,7 @@ meta { name: Get Webhook Events type: graphql - seq: 41 + seq: 44 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Post Liquidity Withdrawal.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Post Liquidity Withdrawal.bru index 6fae5f0df5..6f13402ab2 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Post Liquidity Withdrawal.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Post Liquidity Withdrawal.bru @@ -1,7 +1,7 @@ meta { name: Post Liquidity Withdrawal type: graphql - seq: 16 + seq: 17 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Revoke Wallet Address Key.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Revoke Wallet Address Key.bru index ceb230c859..0f3ad95d4c 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Revoke Wallet Address Key.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Revoke Wallet Address Key.bru @@ -1,7 +1,7 @@ meta { name: Revoke Wallet Address Key type: graphql - seq: 23 + seq: 25 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Set Fee.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Set Fee.bru index 2055395df6..b65b6ede54 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Set Fee.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Set Fee.bru @@ -1,7 +1,7 @@ meta { name: Set Fee type: graphql - seq: 7 + seq: 8 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Trigger Wallet Address Events.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Trigger Wallet Address Events.bru index 3c8515ca8f..adbe714218 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Trigger Wallet Address Events.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Trigger Wallet Address Events.bru @@ -1,7 +1,7 @@ meta { name: Trigger Wallet Address Events type: graphql - seq: 40 + seq: 43 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Update Asset.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Update Asset.bru index f527f0ac9e..0e5000adf3 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Update Asset.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Update Asset.bru @@ -1,7 +1,7 @@ meta { name: Update Asset type: graphql - seq: 2 + seq: 3 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Update Peer.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Update Peer.bru index 3bc84e7cb7..e424a4c9fc 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Update Peer.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Update Peer.bru @@ -1,7 +1,7 @@ meta { name: Update Peer type: graphql - seq: 10 + seq: 11 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Update Wallet Address.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Update Wallet Address.bru index ddfc0376e2..3388bad8c2 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Update Wallet Address.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Update Wallet Address.bru @@ -1,7 +1,7 @@ meta { name: Update Wallet Address type: graphql - seq: 19 + seq: 20 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Void Liquidity Withdrawal.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Void Liquidity Withdrawal.bru index 8b8448bbe8..fcef65389e 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Void Liquidity Withdrawal.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Void Liquidity Withdrawal.bru @@ -1,7 +1,7 @@ meta { name: Void Liquidity Withdrawal type: graphql - seq: 17 + seq: 18 } post { diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Withdraw Event Liquidity.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Withdraw Event Liquidity.bru index 1dfdf8466b..b02f3b0ea6 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Withdraw Event Liquidity.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Withdraw Event Liquidity.bru @@ -1,7 +1,7 @@ meta { name: Withdraw Event Liquidity type: graphql - seq: 38 + seq: 41 } post { diff --git a/localenv/mock-account-servicing-entity/generated/graphql.ts b/localenv/mock-account-servicing-entity/generated/graphql.ts index e482374c2f..b3c34cfbc1 100644 --- a/localenv/mock-account-servicing-entity/generated/graphql.ts +++ b/localenv/mock-account-servicing-entity/generated/graphql.ts @@ -876,6 +876,12 @@ export type OutgoingPaymentEdge = { node: OutgoingPayment; }; +export type OutgoingPaymentFilter = { + receiver?: InputMaybe; + state?: InputMaybe; + walletAddressId?: InputMaybe; +}; + export type OutgoingPaymentResponse = { __typename?: 'OutgoingPaymentResponse'; payment?: Maybe; @@ -1001,6 +1007,8 @@ export type Query = { incomingPayment?: Maybe; /** Fetch an Open Payments outgoing payment */ outgoingPayment?: Maybe; + /** Fetch a page of outgoing payments by receiver */ + outgoingPayments: OutgoingPaymentConnection; /** Fetch a page of combined payments */ payments: PaymentConnection; /** Fetch a peer */ @@ -1050,6 +1058,16 @@ export type QueryOutgoingPaymentArgs = { }; +export type QueryOutgoingPaymentsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + sortOrder?: InputMaybe; +}; + + export type QueryPaymentsArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1595,6 +1613,7 @@ export type ResolversTypes = { OutgoingPayment: ResolverTypeWrapper>; OutgoingPaymentConnection: ResolverTypeWrapper>; OutgoingPaymentEdge: ResolverTypeWrapper>; + OutgoingPaymentFilter: ResolverTypeWrapper>; OutgoingPaymentResponse: ResolverTypeWrapper>; OutgoingPaymentState: ResolverTypeWrapper>; PageInfo: ResolverTypeWrapper>; @@ -1715,6 +1734,7 @@ export type ResolversParentTypes = { OutgoingPayment: Partial; OutgoingPaymentConnection: Partial; OutgoingPaymentEdge: Partial; + OutgoingPaymentFilter: Partial; OutgoingPaymentResponse: Partial; PageInfo: Partial; Payment: Partial; @@ -2088,6 +2108,7 @@ export type QueryResolvers>; incomingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayment?: Resolver, ParentType, ContextType, RequireFields>; + outgoingPayments?: Resolver>; payments?: Resolver>; peer?: Resolver, ParentType, ContextType, RequireFields>; peers?: Resolver>; diff --git a/packages/backend/src/graphql/generated/graphql.schema.json b/packages/backend/src/graphql/generated/graphql.schema.json index f0b8b64033..82a08b88a2 100644 --- a/packages/backend/src/graphql/generated/graphql.schema.json +++ b/packages/backend/src/graphql/generated/graphql.schema.json @@ -5187,6 +5187,53 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "OutgoingPaymentFilter", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "receiver", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "FilterString", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "state", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "FilterString", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "walletAddressId", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "FilterString", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "OutgoingPaymentResponse", @@ -6104,6 +6151,95 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "outgoingPayments", + "description": "Fetch a page of outgoing payments by receiver", + "args": [ + { + "name": "after", + "description": "Paginating forwards: the cursor before the requested page.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "before", + "description": "Paginating backwards: the cursor after the the requested page.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "filter", + "description": "Filter outgoing payments based on specific criteria.", + "type": { + "kind": "INPUT_OBJECT", + "name": "OutgoingPaymentFilter", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "first", + "description": "Paginating forwards: The first **n** elements from the page.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "last", + "description": "Paginating backwards: The last **n** elements from the page.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "sortOrder", + "description": "Ascending or descending order of creation.", + "type": { + "kind": "ENUM", + "name": "SortOrder", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "OutgoingPaymentConnection", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "payments", "description": "Fetch a page of combined payments", diff --git a/packages/backend/src/graphql/generated/graphql.ts b/packages/backend/src/graphql/generated/graphql.ts index e482374c2f..b3c34cfbc1 100644 --- a/packages/backend/src/graphql/generated/graphql.ts +++ b/packages/backend/src/graphql/generated/graphql.ts @@ -876,6 +876,12 @@ export type OutgoingPaymentEdge = { node: OutgoingPayment; }; +export type OutgoingPaymentFilter = { + receiver?: InputMaybe; + state?: InputMaybe; + walletAddressId?: InputMaybe; +}; + export type OutgoingPaymentResponse = { __typename?: 'OutgoingPaymentResponse'; payment?: Maybe; @@ -1001,6 +1007,8 @@ export type Query = { incomingPayment?: Maybe; /** Fetch an Open Payments outgoing payment */ outgoingPayment?: Maybe; + /** Fetch a page of outgoing payments by receiver */ + outgoingPayments: OutgoingPaymentConnection; /** Fetch a page of combined payments */ payments: PaymentConnection; /** Fetch a peer */ @@ -1050,6 +1058,16 @@ export type QueryOutgoingPaymentArgs = { }; +export type QueryOutgoingPaymentsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + sortOrder?: InputMaybe; +}; + + export type QueryPaymentsArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1595,6 +1613,7 @@ export type ResolversTypes = { OutgoingPayment: ResolverTypeWrapper>; OutgoingPaymentConnection: ResolverTypeWrapper>; OutgoingPaymentEdge: ResolverTypeWrapper>; + OutgoingPaymentFilter: ResolverTypeWrapper>; OutgoingPaymentResponse: ResolverTypeWrapper>; OutgoingPaymentState: ResolverTypeWrapper>; PageInfo: ResolverTypeWrapper>; @@ -1715,6 +1734,7 @@ export type ResolversParentTypes = { OutgoingPayment: Partial; OutgoingPaymentConnection: Partial; OutgoingPaymentEdge: Partial; + OutgoingPaymentFilter: Partial; OutgoingPaymentResponse: Partial; PageInfo: Partial; Payment: Partial; @@ -2088,6 +2108,7 @@ export type QueryResolvers>; incomingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayment?: Resolver, ParentType, ContextType, RequireFields>; + outgoingPayments?: Resolver>; payments?: Resolver>; peer?: Resolver, ParentType, ContextType, RequireFields>; peers?: Resolver>; diff --git a/packages/backend/src/graphql/resolvers/index.ts b/packages/backend/src/graphql/resolvers/index.ts index c4f5c6f5ec..f74bb802de 100644 --- a/packages/backend/src/graphql/resolvers/index.ts +++ b/packages/backend/src/graphql/resolvers/index.ts @@ -24,6 +24,7 @@ import { import { getQuote, createQuote, getWalletAddressQuotes } from './quote' import { getOutgoingPayment, + getOutgoingPayments, createOutgoingPayment, getWalletAddressOutgoingPayments, createOutgoingPaymentFromIncomingPayment, @@ -84,6 +85,7 @@ export const resolvers: Resolvers = { asset: getAsset, assets: getAssets, outgoingPayment: getOutgoingPayment, + outgoingPayments: getOutgoingPayments, incomingPayment: getIncomingPayment, peer: getPeer, peers: getPeers, diff --git a/packages/backend/src/graphql/resolvers/outgoing_payment.test.ts b/packages/backend/src/graphql/resolvers/outgoing_payment.test.ts index 71c51affcc..3db5ad6573 100644 --- a/packages/backend/src/graphql/resolvers/outgoing_payment.test.ts +++ b/packages/backend/src/graphql/resolvers/outgoing_payment.test.ts @@ -10,6 +10,7 @@ import { AppServices } from '../../app' import { initIocContainer } from '../..' import { Config } from '../../config/app' import { createAsset } from '../../tests/asset' +import { createIncomingPayment } from '../../tests/incomingPayment' import { createOutgoingPayment, CreateTestQuoteAndOutgoingPaymentOptions @@ -31,7 +32,8 @@ import { Asset } from '../../asset/model' import { OutgoingPayment, OutgoingPaymentResponse, - OutgoingPaymentState as SchemaPaymentState + OutgoingPaymentState as SchemaPaymentState, + OutgoingPaymentConnection } from '../generated/graphql' import { faker } from '@faker-js/faker' import { GraphQLErrorCode } from '../errors' @@ -92,6 +94,232 @@ describe('OutgoingPayment Resolvers', (): void => { describe('Query.outgoingPayment', (): void => { let payment: OutgoingPaymentModel + let walletAddressId: string + + beforeEach(async (): Promise => { + walletAddressId = ( + await createWalletAddress(deps, { + assetId: asset.id + }) + ).id + }) + + getPageTests({ + getClient: () => appContainer.apolloClient, + createModel: () => + createPayment({ + walletAddressId + }), + pagedQuery: 'outgoingPayments' + }) + + describe('page filter tests', () => { + let receiver: string + let firstOutgoingPayment: OutgoingPaymentModel + let secondOutgoingPayment: OutgoingPaymentModel + + const pageQuery = gql` + query OutgoingPayments($filter: OutgoingPaymentFilter) { + outgoingPayments(filter: $filter) { + edges { + node { + id + walletAddressId + state + receiver + } + } + } + } + ` + + beforeEach(async (): Promise => { + const firstReceiverWalletAddress = await createWalletAddress(deps, { + assetId: asset.id + }) + const secondWalletAddress = await createWalletAddress(deps, { + assetId: asset.id + }) + + const secondReceiverWalletAddress = await createWalletAddress(deps, { + assetId: asset.id + }) + + const baseOutgoingPayment = { + debitAmount: { + value: BigInt(10), + assetCode: asset.code, + assetScale: asset.scale + }, + validDestination: true, + client: 'test-client' + } + + const incomingPayment = await createIncomingPayment(deps, { + walletAddressId: firstReceiverWalletAddress.id + }) + receiver = incomingPayment.getUrl(firstReceiverWalletAddress) + firstOutgoingPayment = await createOutgoingPayment(deps, { + walletAddressId, + receiver, + method: 'ilp', + ...baseOutgoingPayment + }) + + const secondIncomingPayment = await createIncomingPayment(deps, { + walletAddressId: secondReceiverWalletAddress.id + }) + const secondReceiver = secondIncomingPayment.getUrl(secondWalletAddress) + secondOutgoingPayment = await createOutgoingPayment(deps, { + walletAddressId: secondWalletAddress.id, + receiver: secondReceiver, + method: 'ilp', + ...baseOutgoingPayment + }) + }) + + test('can filter payments by receiver', async (): Promise => { + const query = await appContainer.apolloClient + .query({ + query: pageQuery, + variables: { + filter: { + receiver: { + in: [receiver] + } + } + } + }) + .then((query): OutgoingPaymentConnection => { + if (query.data) { + return query.data.outgoingPayments + } else { + throw new Error('Data was empty') + } + }) + + expect(query.edges).toHaveLength(1) + expect(query.edges[0].node).toMatchObject( + expect.objectContaining({ + id: firstOutgoingPayment.id, + receiver + }) + ) + expect(query.edges).not.toContainEqual( + expect.objectContaining({ + id: secondOutgoingPayment.id + }) + ) + }) + + test('can filter payments by wallet address id', async (): Promise => { + const query = await appContainer.apolloClient + .query({ + query: pageQuery, + variables: { + filter: { + walletAddressId: { + in: [walletAddressId] + } + } + } + }) + .then((query): OutgoingPaymentConnection => { + if (query.data) { + return query.data.outgoingPayments + } else { + throw new Error('Data was empty') + } + }) + + expect(query.edges).toHaveLength(1) + expect(query.edges[0].node).toMatchObject( + expect.objectContaining({ + id: firstOutgoingPayment.id, + walletAddressId + }) + ) + expect(query.edges).not.toContainEqual( + expect.objectContaining({ + id: secondOutgoingPayment.id + }) + ) + }) + + test('can filter payments by wallet address id', async (): Promise => { + const query = await appContainer.apolloClient + .query({ + query: pageQuery, + variables: { + filter: { + walletAddressId: { + in: [walletAddressId] + } + } + } + }) + .then((query): OutgoingPaymentConnection => { + if (query.data) { + return query.data.outgoingPayments + } else { + throw new Error('Data was empty') + } + }) + + expect(query.edges).toHaveLength(1) + expect(query.edges[0].node).toMatchObject( + expect.objectContaining({ + id: firstOutgoingPayment.id, + walletAddressId + }) + ) + expect(query.edges).not.toContainEqual( + expect.objectContaining({ + id: secondOutgoingPayment.id + }) + ) + }) + + test('can filter payments by state', async (): Promise => { + await OutgoingPaymentModel.query().patchAndFetchById( + firstOutgoingPayment.id, + { + state: OutgoingPaymentState.Completed + } + ) + const query = await appContainer.apolloClient + .query({ + query: pageQuery, + variables: { + filter: { + state: { + in: [OutgoingPaymentState.Completed] + } + } + } + }) + .then((query): OutgoingPaymentConnection => { + if (query.data) { + return query.data.outgoingPayments + } else { + throw new Error('Data was empty') + } + }) + + expect(query.edges).toHaveLength(1) + expect(query.edges[0].node).toMatchObject( + expect.objectContaining({ + id: firstOutgoingPayment.id, + state: OutgoingPaymentState.Completed + }) + ) + expect(query.edges).not.toContainEqual( + expect.objectContaining({ + id: secondOutgoingPayment.id + }) + ) + }) + }) describe('grantId', (): void => { it('should return grantId', async (): Promise => { diff --git a/packages/backend/src/graphql/resolvers/outgoing_payment.ts b/packages/backend/src/graphql/resolvers/outgoing_payment.ts index 699357de60..a9cdcb4403 100644 --- a/packages/backend/src/graphql/resolvers/outgoing_payment.ts +++ b/packages/backend/src/graphql/resolvers/outgoing_payment.ts @@ -36,6 +36,41 @@ export const getOutgoingPayment: QueryResolvers['outgoingPayment' return paymentToGraphql(payment) } +export const getOutgoingPayments: QueryResolvers['outgoingPayments'] = + async ( + parent, + args, + ctx + ): Promise => { + const outgoingPaymentService = await ctx.container.use( + 'outgoingPaymentService' + ) + const { filter, sortOrder, ...pagination } = args + const order = sortOrder === 'ASC' ? SortOrder.Asc : SortOrder.Desc + const getPageFn = (pagination_: Pagination, sortOrder_?: SortOrder) => + outgoingPaymentService.getPage({ + pagination: pagination_, + filter, + sortOrder: sortOrder_ + }) + + const outgoingPayments = await getPageFn(pagination, order) + const pageInfo = await getPageInfo({ + getPage: (pagination_: Pagination, sortOrder_?: SortOrder) => + getPageFn(pagination_, sortOrder_), + page: outgoingPayments, + sortOrder: order + }) + + return { + pageInfo, + edges: outgoingPayments.map((outgoingPayment: OutgoingPayment) => ({ + cursor: outgoingPayment.id, + node: paymentToGraphql(outgoingPayment) + })) + } + } + export const cancelOutgoingPayment: MutationResolvers['cancelOutgoingPayment'] = async ( parent, diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index dcf104c049..5dac1477b0 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -56,6 +56,22 @@ type Query { "Fetch an Open Payments outgoing payment" outgoingPayment(id: String!): OutgoingPayment + "Fetch a page of outgoing payments by receiver" + outgoingPayments( + "Paginating forwards: the cursor before the requested page." + after: String + "Paginating backwards: the cursor after the the requested page." + before: String + "Paginating forwards: The first **n** elements from the page." + first: Int + "Paginating backwards: The last **n** elements from the page." + last: Int + "Ascending or descending order of creation." + sortOrder: SortOrder + "Filter outgoing payments based on specific criteria." + filter: OutgoingPaymentFilter + ): OutgoingPaymentConnection! + "Fetch an Open Payments incoming payment" incomingPayment(id: String!): IncomingPayment @@ -822,6 +838,12 @@ type OutgoingPaymentEdge { cursor: String! } +input OutgoingPaymentFilter { + receiver: FilterString + walletAddressId: FilterString + state: FilterString +} + type OutgoingPayment implements BasePayment & Model { "Outgoing payment id" id: ID! 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 d2b09e516d..7bd9e459f3 100644 --- a/packages/backend/src/open_payments/payment/outgoing/service.test.ts +++ b/packages/backend/src/open_payments/payment/outgoing/service.test.ts @@ -51,6 +51,8 @@ import { UnionOmit } from '../../../shared/utils' import { QuoteError } from '../../quote/errors' import { withConfigOverride } from '../../../tests/helpers' import { TelemetryService } from '../../../telemetry/service' +import { getPageTests } from '../../../shared/baseModel.test' +import { Pagination, SortOrder } from '../../../shared/baseModel' describe('OutgoingPaymentService', (): void => { let deps: IocContract @@ -61,6 +63,7 @@ describe('OutgoingPaymentService', (): void => { let quoteService: QuoteService let telemetryService: TelemetryService let knex: Knex + let assetId: string let walletAddressId: string let incomingPayment: IncomingPayment let receiverWalletAddress: MockWalletAddress @@ -262,6 +265,7 @@ describe('OutgoingPaymentService', (): void => { beforeEach(async (): Promise => { const { id: sendAssetId } = await createAsset(deps, asset) + assetId = sendAssetId const walletAddress = await createWalletAddress(deps, { assetId: sendAssetId }) @@ -357,6 +361,125 @@ describe('OutgoingPaymentService', (): void => { }) }) + describe('getPage', (): void => { + getPageTests({ + createModel: () => + createOutgoingPayment(deps, { + walletAddressId, + client, + receiver, + debitAmount: { + assetCode: asset.code, + assetScale: asset.scale, + value: BigInt(10) + }, + validDestination: true, + method: 'ilp' + }), + getPage: (pagination?: Pagination, sortOrder?: SortOrder) => + outgoingPaymentService.getPage({ pagination, sortOrder }) + }) + + describe('filters', () => { + let otherSenderWalletAddress: WalletAddress + let otherReceiverWalletAddress: WalletAddress + let otherReceiver: string + let outgoingPayment: OutgoingPayment + let otherOutgoingPayment: OutgoingPayment + beforeEach(async (): Promise => { + otherSenderWalletAddress = await createWalletAddress(deps, { assetId }) + otherReceiverWalletAddress = await createWalletAddress(deps, { + assetId + }) + const incomingPayment = await createIncomingPayment(deps, { + walletAddressId: receiverWalletAddress.id + }) + otherReceiver = incomingPayment.getUrl(otherReceiverWalletAddress) + + outgoingPayment = await createOutgoingPayment(deps, { + walletAddressId, + client, + receiver, + debitAmount: { + assetCode: asset.code, + assetScale: asset.scale, + value: BigInt(10) + }, + validDestination: true, + method: 'ilp' + }) + + otherOutgoingPayment = await createOutgoingPayment(deps, { + walletAddressId: otherSenderWalletAddress.id, + client, + receiver: otherReceiver, + debitAmount: { + assetCode: asset.code, + assetScale: asset.scale, + value: BigInt(10) + }, + validDestination: true, + method: 'ilp' + }) + }) + + test('can filter by receiver', async (): Promise => { + const page = await outgoingPaymentService.getPage({ + filter: { + receiver: { in: [receiver] } + } + }) + + expect(page).toContainEqual( + expect.objectContaining({ id: outgoingPayment.id }) + ) + expect(page).not.toContainEqual( + expect.objectContaining({ + id: otherOutgoingPayment.id, + receiver: otherReceiver + }) + ) + }) + + test('can filter by wallet address', async (): Promise => { + const page = await outgoingPaymentService.getPage({ + filter: { + walletAddressId: { in: [walletAddressId] } + } + }) + + expect(page).toContainEqual( + expect.objectContaining({ id: outgoingPayment.id, walletAddressId }) + ) + expect(page).not.toContainEqual( + expect.objectContaining({ id: otherOutgoingPayment.id }) + ) + }) + + test('can filter by state', async (): Promise => { + await OutgoingPayment.query(trx).patchAndFetchById(outgoingPayment.id, { + state: OutgoingPaymentState.Completed + }) + + const page = await outgoingPaymentService.getPage({ + filter: { + state: { in: [OutgoingPaymentState.Completed] } + } + }) + + expect(page).toContainEqual( + expect.objectContaining({ + id: outgoingPayment.id, + state: OutgoingPaymentState.Completed + }) + ) + expect(page).not.toContainEqual( + expect.objectContaining({ id: otherOutgoingPayment.id }) + ) + }) + }) + }) + describe('getWalletAddressPage', (): void => { test('throws error if cannot find liquidity account for SENDING payment', async () => { const quote = await createQuote(deps, { diff --git a/packages/backend/src/open_payments/payment/outgoing/service.ts b/packages/backend/src/open_payments/payment/outgoing/service.ts index b47a4ed3e9..050700aca1 100644 --- a/packages/backend/src/open_payments/payment/outgoing/service.ts +++ b/packages/backend/src/open_payments/payment/outgoing/service.ts @@ -40,9 +40,12 @@ import { TelemetryService } from '../../../telemetry/service' import { Amount } from '../../amount' import { QuoteService } from '../../quote/service' import { isQuoteError } from '../../quote/errors' +import { Pagination, SortOrder } from '../../../shared/baseModel' +import { FilterString } from '../../../shared/filters' export interface OutgoingPaymentService extends WalletAddressSubresourceService { + getPage(options?: GetPageOptions): Promise create( options: CreateOutgoingPaymentOptions ): Promise @@ -75,6 +78,7 @@ export async function createOutgoingPaymentService( } return { get: (options) => getOutgoingPayment(deps, options), + getPage: (options) => getOutgoingPaymentsPage(deps, options), create: (options) => createOutgoingPayment(deps, options), cancel: (options) => cancelOutgoingPayment(deps, options), fund: (options) => fundPayment(deps, options), @@ -83,6 +87,57 @@ export async function createOutgoingPaymentService( } } +interface OutgoingPaymentFilter { + receiver?: FilterString + walletAddressId?: FilterString + state?: FilterString +} + +interface GetPageOptions { + pagination?: Pagination + filter?: OutgoingPaymentFilter + sortOrder?: SortOrder +} + +async function getOutgoingPaymentsPage( + deps: ServiceDependencies, + options?: GetPageOptions +): Promise { + const { filter, pagination, sortOrder } = options ?? {} + + const query = OutgoingPayment.query(deps.knex).withGraphFetched( + '[quote.asset, walletAddress]' + ) + + if (filter?.receiver?.in && filter.receiver.in.length) { + query + .innerJoin('quotes', 'quotes.id', 'outgoingPayments.id') + .whereIn('quotes.receiver', filter.receiver.in) + } + + if (filter?.walletAddressId?.in && filter.walletAddressId.in.length) { + query.whereIn('walletAddressId', filter.walletAddressId.in) + } + + if (filter?.state?.in && filter.state.in.length) { + query.whereIn('state', filter.state.in) + } + + const page = await query.getPage(pagination, sortOrder) + const amounts = await deps.accountingService.getAccountsTotalSent( + page.map((payment: OutgoingPayment) => payment.id) + ) + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + return page.map((payment: OutgoingPayment, i: number) => { + payment.sentAmount = { + value: validateSentAmount(deps, payment, amounts[i]), + assetCode: payment.asset.code, + assetScale: payment.asset.scale + } + return payment + }) +} + async function getOutgoingPayment( deps: ServiceDependencies, options: GetOptions diff --git a/packages/frontend/app/generated/graphql.ts b/packages/frontend/app/generated/graphql.ts index 10e878774f..bbb9082971 100644 --- a/packages/frontend/app/generated/graphql.ts +++ b/packages/frontend/app/generated/graphql.ts @@ -876,6 +876,12 @@ export type OutgoingPaymentEdge = { node: OutgoingPayment; }; +export type OutgoingPaymentFilter = { + receiver?: InputMaybe; + state?: InputMaybe; + walletAddressId?: InputMaybe; +}; + export type OutgoingPaymentResponse = { __typename?: 'OutgoingPaymentResponse'; payment?: Maybe; @@ -1001,6 +1007,8 @@ export type Query = { incomingPayment?: Maybe; /** Fetch an Open Payments outgoing payment */ outgoingPayment?: Maybe; + /** Fetch a page of outgoing payments by receiver */ + outgoingPayments: OutgoingPaymentConnection; /** Fetch a page of combined payments */ payments: PaymentConnection; /** Fetch a peer */ @@ -1050,6 +1058,16 @@ export type QueryOutgoingPaymentArgs = { }; +export type QueryOutgoingPaymentsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + sortOrder?: InputMaybe; +}; + + export type QueryPaymentsArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1595,6 +1613,7 @@ export type ResolversTypes = { OutgoingPayment: ResolverTypeWrapper>; OutgoingPaymentConnection: ResolverTypeWrapper>; OutgoingPaymentEdge: ResolverTypeWrapper>; + OutgoingPaymentFilter: ResolverTypeWrapper>; OutgoingPaymentResponse: ResolverTypeWrapper>; OutgoingPaymentState: ResolverTypeWrapper>; PageInfo: ResolverTypeWrapper>; @@ -1715,6 +1734,7 @@ export type ResolversParentTypes = { OutgoingPayment: Partial; OutgoingPaymentConnection: Partial; OutgoingPaymentEdge: Partial; + OutgoingPaymentFilter: Partial; OutgoingPaymentResponse: Partial; PageInfo: Partial; Payment: Partial; @@ -2088,6 +2108,7 @@ export type QueryResolvers>; incomingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayment?: Resolver, ParentType, ContextType, RequireFields>; + outgoingPayments?: Resolver>; payments?: Resolver>; peer?: Resolver, ParentType, ContextType, RequireFields>; peers?: Resolver>; diff --git a/packages/mock-account-service-lib/src/generated/graphql.ts b/packages/mock-account-service-lib/src/generated/graphql.ts index e482374c2f..b3c34cfbc1 100644 --- a/packages/mock-account-service-lib/src/generated/graphql.ts +++ b/packages/mock-account-service-lib/src/generated/graphql.ts @@ -876,6 +876,12 @@ export type OutgoingPaymentEdge = { node: OutgoingPayment; }; +export type OutgoingPaymentFilter = { + receiver?: InputMaybe; + state?: InputMaybe; + walletAddressId?: InputMaybe; +}; + export type OutgoingPaymentResponse = { __typename?: 'OutgoingPaymentResponse'; payment?: Maybe; @@ -1001,6 +1007,8 @@ export type Query = { incomingPayment?: Maybe; /** Fetch an Open Payments outgoing payment */ outgoingPayment?: Maybe; + /** Fetch a page of outgoing payments by receiver */ + outgoingPayments: OutgoingPaymentConnection; /** Fetch a page of combined payments */ payments: PaymentConnection; /** Fetch a peer */ @@ -1050,6 +1058,16 @@ export type QueryOutgoingPaymentArgs = { }; +export type QueryOutgoingPaymentsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + sortOrder?: InputMaybe; +}; + + export type QueryPaymentsArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1595,6 +1613,7 @@ export type ResolversTypes = { OutgoingPayment: ResolverTypeWrapper>; OutgoingPaymentConnection: ResolverTypeWrapper>; OutgoingPaymentEdge: ResolverTypeWrapper>; + OutgoingPaymentFilter: ResolverTypeWrapper>; OutgoingPaymentResponse: ResolverTypeWrapper>; OutgoingPaymentState: ResolverTypeWrapper>; PageInfo: ResolverTypeWrapper>; @@ -1715,6 +1734,7 @@ export type ResolversParentTypes = { OutgoingPayment: Partial; OutgoingPaymentConnection: Partial; OutgoingPaymentEdge: Partial; + OutgoingPaymentFilter: Partial; OutgoingPaymentResponse: Partial; PageInfo: Partial; Payment: Partial; @@ -2088,6 +2108,7 @@ export type QueryResolvers>; incomingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayment?: Resolver, ParentType, ContextType, RequireFields>; + outgoingPayments?: Resolver>; payments?: Resolver>; peer?: Resolver, ParentType, ContextType, RequireFields>; peers?: Resolver>; diff --git a/test/integration/lib/generated/graphql.ts b/test/integration/lib/generated/graphql.ts index e482374c2f..b3c34cfbc1 100644 --- a/test/integration/lib/generated/graphql.ts +++ b/test/integration/lib/generated/graphql.ts @@ -876,6 +876,12 @@ export type OutgoingPaymentEdge = { node: OutgoingPayment; }; +export type OutgoingPaymentFilter = { + receiver?: InputMaybe; + state?: InputMaybe; + walletAddressId?: InputMaybe; +}; + export type OutgoingPaymentResponse = { __typename?: 'OutgoingPaymentResponse'; payment?: Maybe; @@ -1001,6 +1007,8 @@ export type Query = { incomingPayment?: Maybe; /** Fetch an Open Payments outgoing payment */ outgoingPayment?: Maybe; + /** Fetch a page of outgoing payments by receiver */ + outgoingPayments: OutgoingPaymentConnection; /** Fetch a page of combined payments */ payments: PaymentConnection; /** Fetch a peer */ @@ -1050,6 +1058,16 @@ export type QueryOutgoingPaymentArgs = { }; +export type QueryOutgoingPaymentsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + sortOrder?: InputMaybe; +}; + + export type QueryPaymentsArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1595,6 +1613,7 @@ export type ResolversTypes = { OutgoingPayment: ResolverTypeWrapper>; OutgoingPaymentConnection: ResolverTypeWrapper>; OutgoingPaymentEdge: ResolverTypeWrapper>; + OutgoingPaymentFilter: ResolverTypeWrapper>; OutgoingPaymentResponse: ResolverTypeWrapper>; OutgoingPaymentState: ResolverTypeWrapper>; PageInfo: ResolverTypeWrapper>; @@ -1715,6 +1734,7 @@ export type ResolversParentTypes = { OutgoingPayment: Partial; OutgoingPaymentConnection: Partial; OutgoingPaymentEdge: Partial; + OutgoingPaymentFilter: Partial; OutgoingPaymentResponse: Partial; PageInfo: Partial; Payment: Partial; @@ -2088,6 +2108,7 @@ export type QueryResolvers>; incomingPayment?: Resolver, ParentType, ContextType, RequireFields>; outgoingPayment?: Resolver, ParentType, ContextType, RequireFields>; + outgoingPayments?: Resolver>; payments?: Resolver>; peer?: Resolver, ParentType, ContextType, RequireFields>; peers?: Resolver>;