diff --git a/CHANGELOG.md b/CHANGELOG.md index cb47febc..53fdc904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added +- Support for retrieving merchant account transactions ([#81](https://github.com/TrueLayer/truelayer-php/pull/81)) - Support for `creditable_at` in Authorised, Executed, Settled, Failed payments ([#82](https://github.com/TrueLayer/truelayer-php/pull/82)) - Enabled PHP 8.4 in CI test matrix. Excluded the lowest dependencies versions to overcome incompatibility issues ([#83](https://github.com/TrueLayer/truelayer-php/pull/83)) diff --git a/README.md b/README.md index e18a2586..f818d158 100644 --- a/README.md +++ b/README.md @@ -1020,6 +1020,22 @@ foreach ($merchantAccount->getAccountIdentifiers() as $accountIdentifier) { } ``` +Retrieving a merchant account's list of transactions: + +```php +$account = $client->getMerchantAccount('a2dcee6d-7a00-414d-a1e6-8a2b23169e00'); + +$from = (new DateTime())->sub(new DateInterval("PT6H")); +$to = new DateTime(); + +$transactions = $account->getTransactions($from, $to); + +foreach ($transactions as $transaction) { + // See MerchantAccountRetrievedTransactionInterface and + // the interfaces that extend it for available methods. +} +``` + # SignUp Plus diff --git a/config/bindings.php b/config/bindings.php index 9af12f58..6e8c9146 100644 --- a/config/bindings.php +++ b/config/bindings.php @@ -92,6 +92,14 @@ Interfaces\Remitter\RemitterVerification\AutomatedRemitterVerificationInterface::class => Entities\Remitter\RemitterVerification\AutomatedRemitterVerification::class, Interfaces\MerchantAccount\MerchantAccountInterface::class => Entities\MerchantAccount\MerchantAccount::class, + Interfaces\MerchantAccount\Transactions\MerchantAccountTransactionRetrievedInterface::class => Entities\MerchantAccount\Transactions\MerchantAccountTransactionRetrieved::class, + Interfaces\MerchantAccount\Transactions\MerchantAccountPaymentInterface::class => Entities\MerchantAccount\Transactions\MerchantAccountPayment::class, + Interfaces\MerchantAccount\Transactions\MerchantAccountExternalPaymentInterface::class => Entities\MerchantAccount\Transactions\MerchantAccountExternalPayment::class, + Interfaces\MerchantAccount\Transactions\MerchantAccountPendingPayoutInterface::class => Entities\MerchantAccount\Transactions\MerchantAccountPendingPayout::class, + Interfaces\MerchantAccount\Transactions\MerchantAccountExecutedPayoutInterface::class => Entities\MerchantAccount\Transactions\MerchantAccountExecutedPayout::class, + Interfaces\MerchantAccount\Transactions\MerchantAccountRefundInterface::class => Entities\MerchantAccount\Transactions\MerchantAccountRefund::class, + Interfaces\MerchantAccount\Transactions\Returns\MerchantAccountExternalPaymentReturnIdentifiedInterface::class => Entities\MerchantAccount\Transactions\Returns\MerchantAccountExternalPaymentReturnIdentified::class, + Interfaces\MerchantAccount\Transactions\Returns\MerchantAccountExternalPaymentReturnUnknownInterface::class => Entities\MerchantAccount\Transactions\Returns\MerchantAccountExternalPaymentReturnUnknown::class, Interfaces\Webhook\PaymentAuthorizedEventInterface::class => Entities\Webhook\PaymentAuthorizedEvent::class, Interfaces\Webhook\PaymentExecutedEventInterface::class => Entities\Webhook\PaymentExecutedEvent::class, diff --git a/config/discriminations.php b/config/discriminations.php index f5cc23ce..14955c48 100644 --- a/config/discriminations.php +++ b/config/discriminations.php @@ -6,6 +6,8 @@ use TrueLayer\Constants\AuthorizationFlowActionTypes; use TrueLayer\Constants\AuthorizationFlowStatusTypes; use TrueLayer\Constants\BeneficiaryTypes; +use TrueLayer\Constants\MerchantAccountPayoutStatuses; +use TrueLayer\Constants\MerchantAccountTransactionTypes; use TrueLayer\Constants\PaymentMethods; use TrueLayer\Constants\PaymentStatus; use TrueLayer\Constants\PayoutStatus; @@ -119,4 +121,16 @@ 'discriminate_on' => 'type', RemitterVerificationTypes::AUTOMATED => Interfaces\Remitter\RemitterVerification\AutomatedRemitterVerificationInterface::class, ], + Interfaces\MerchantAccount\Transactions\MerchantAccountTransactionRetrievedInterface::class => [ + 'discriminate_on' => 'type', + MerchantAccountTransactionTypes::EXTERNAL_PAYMENT => Interfaces\MerchantAccount\Transactions\MerchantAccountExternalPaymentInterface::class, + MerchantAccountTransactionTypes::PAYOUT => Interfaces\MerchantAccount\Transactions\MerchantAccountPayoutInterface::class, + MerchantAccountTransactionTypes::REFUND => Interfaces\MerchantAccount\Transactions\MerchantAccountRefundInterface::class, + MerchantAccountTransactionTypes::MERCHANT_ACCOUNT_PAYMENT => Interfaces\MerchantAccount\Transactions\MerchantAccountPaymentInterface::class, + ], + Interfaces\MerchantAccount\Transactions\MerchantAccountPayoutInterface::class => [ + 'discriminate_on' => 'status', + MerchantAccountPayoutStatuses::EXECUTED_PAYOUT => Interfaces\MerchantAccount\Transactions\MerchantAccountExecutedPayoutInterface::class, + MerchantAccountPayoutStatuses::PENDING_PAYOUT => Interfaces\MerchantAccount\Transactions\MerchantAccountPendingPayoutInterface::class, + ], ]; diff --git a/src/Constants/MerchantAccountPayoutStatuses.php b/src/Constants/MerchantAccountPayoutStatuses.php new file mode 100644 index 00000000..c243f2c0 --- /dev/null +++ b/src/Constants/MerchantAccountPayoutStatuses.php @@ -0,0 +1,11 @@ +accountHolderName; } + + /** + * @param \DateTimeInterface $from + * @param \DateTimeInterface $to + * + * @return array|MerchantAccountTransactionRetrievedInterface[] + * @throws InvalidArgumentException + */ + public function getTransactions(\DateTimeInterface $from, \DateTimeInterface $to): array { + $data = $this->getApiFactory()->merchantAccountsApi() + ->listTransactions($this->id, $from, $to); + + return $this->entityFactory->makeMany(MerchantAccountTransactionRetrievedInterface::class, $data); + } } diff --git a/src/Entities/MerchantAccount/Transactions/MerchantAccountExecutedPayout.php b/src/Entities/MerchantAccount/Transactions/MerchantAccountExecutedPayout.php new file mode 100644 index 00000000..aa1aa27f --- /dev/null +++ b/src/Entities/MerchantAccount/Transactions/MerchantAccountExecutedPayout.php @@ -0,0 +1,47 @@ +returnedBy ?? null; + } + + /** + * @return string|null + */ + public function getSchemeId(): ?string + { + return $this->schemeId ?? null; + } +} diff --git a/src/Entities/MerchantAccount/Transactions/MerchantAccountExternalPayment.php b/src/Entities/MerchantAccount/Transactions/MerchantAccountExternalPayment.php new file mode 100644 index 00000000..778be2ad --- /dev/null +++ b/src/Entities/MerchantAccount/Transactions/MerchantAccountExternalPayment.php @@ -0,0 +1,55 @@ + \DateTimeInterface::class, + ]; + + /** + * @return string[] + */ + protected function arrayFields(): array + { + return \array_merge(parent::arrayFields(), [ + 'settled_at', + 'returned_for', + ]); + } + + /** + * @return \DateTimeInterface + */ + public function getSettledAt(): \DateTimeInterface + { + return $this->settledAt; + } + + /** + * @return MerchantAccountExternalPaymentReturnInterface|null + */ + public function getRefundFor(): ?MerchantAccountExternalPaymentReturnInterface + { + return $this->returnedFor ?? null; + } +} diff --git a/src/Entities/MerchantAccount/Transactions/MerchantAccountPayment.php b/src/Entities/MerchantAccount/Transactions/MerchantAccountPayment.php new file mode 100644 index 00000000..d0dca903 --- /dev/null +++ b/src/Entities/MerchantAccount/Transactions/MerchantAccountPayment.php @@ -0,0 +1,84 @@ + + */ + protected array $metadata; + + /** + * @var array|string[] + */ + protected array $casts = [ + 'settled_at' => \DateTimeInterface::class, + 'payment_source' => PaymentSourceBeneficiaryInterface::class, + ]; + + /** + * @return string[] + */ + protected function arrayFields(): array + { + return \array_merge(parent::arrayFields(), [ + 'settled_at', + 'payment_id', + 'payment_source', + 'metadata', + ]); + } + + /** + * @return \DateTimeInterface + */ + public function getSettledAt(): \DateTimeInterface + { + return $this->settledAt; + } + + /** + * @return PaymentSourceBeneficiaryInterface + */ + public function getPaymentSource(): PaymentSourceBeneficiaryInterface + { + return $this->paymentSource; + } + + /** + * @return string + */ + public function getPaymentId(): string + { + return $this->paymentId; + } + + /** + * @return string[] + */ + public function getMetadata(): array + { + return $this->metadata ?? []; + } +} diff --git a/src/Entities/MerchantAccount/Transactions/MerchantAccountPayout.php b/src/Entities/MerchantAccount/Transactions/MerchantAccountPayout.php new file mode 100644 index 00000000..48dd8b17 --- /dev/null +++ b/src/Entities/MerchantAccount/Transactions/MerchantAccountPayout.php @@ -0,0 +1,98 @@ + + */ + protected array $metadata; + + /** + * @var array|string[] + */ + protected array $casts = [ + 'created_at' => \DateTimeInterface::class, + 'beneficiary' => BeneficiaryInterface::class, + ]; + + /** + * @return string[] + */ + protected function arrayFields(): array + { + return \array_merge(parent::arrayFields(), [ + 'created_at', + 'context_code', + 'payout_id', + 'beneficiary', + 'metadata', + ]); + } + + /** + * @return \DateTimeInterface + */ + public function getCreatedAt(): \DateTimeInterface + { + return $this->createdAt; + } + + /** + * @return string + */ + public function getContextCode(): string + { + return $this->contextCode; + } + + /** + * @return string + */ + public function getPayoutId(): string + { + return $this->payoutId; + } + + /** + * @return BeneficiaryInterface + */ + public function getBeneficiary(): BeneficiaryInterface + { + return $this->beneficiary; + } + + /** + * @return array + */ + public function getMetadata(): array + { + return $this->metadata ?? []; + } +} diff --git a/src/Entities/MerchantAccount/Transactions/MerchantAccountPendingPayout.php b/src/Entities/MerchantAccount/Transactions/MerchantAccountPendingPayout.php new file mode 100644 index 00000000..3d647232 --- /dev/null +++ b/src/Entities/MerchantAccount/Transactions/MerchantAccountPendingPayout.php @@ -0,0 +1,12 @@ + + */ + protected array $metadata; + + /** + * @var array|string[] + */ + protected array $casts = [ + 'created_at' => \DateTimeInterface::class, + 'executed_at' => \DateTimeInterface::class, + 'beneficiary' => PaymentSourceBeneficiaryInterface::class, + ]; + + /** + * @return string[] + */ + protected function arrayFields(): array + { + return \array_merge(parent::arrayFields(), [ + 'created_at', + 'executed_at', + 'beneficiary', + 'context_code', + 'refund_id', + 'payment_id', + 'returned_by', + 'scheme_id', + 'metadata', + ]); + } + + /** + * @return \DateTimeInterface + */ + public function getCreatedAt(): \DateTimeInterface + { + return $this->createdAt; + } + + /** + * @return \DateTimeInterface|null + */ + public function getExecutedAt(): ?\DateTimeInterface + { + return $this->executedAt ?? null; + } + + /** + * @return PaymentSourceBeneficiaryInterface + */ + public function getBeneficiary(): PaymentSourceBeneficiaryInterface + { + return $this->beneficiary; + } + + /** + * @return string + */ + public function getContextCode(): string + { + return $this->contextCode; + } + + /** + * @return string + */ + public function getRefundId(): string + { + return $this->refundId; + } + + /** + * @return string + */ + public function getPaymentId(): string + { + return $this->paymentId; + } + + /** + * @return string|null + */ + public function getReturnedBy(): ?string + { + return $this->returnedBy ?? null; + } + + /** + * @return string|null + */ + public function getSchemeId(): ?string + { + return $this->schemeId ?? null; + } + + /** + * @return array + */ + public function getMetadata(): array + { + return $this->metadata ?? []; + } +} diff --git a/src/Entities/MerchantAccount/Transactions/MerchantAccountTransactionRetrieved.php b/src/Entities/MerchantAccount/Transactions/MerchantAccountTransactionRetrieved.php new file mode 100644 index 00000000..140d7864 --- /dev/null +++ b/src/Entities/MerchantAccount/Transactions/MerchantAccountTransactionRetrieved.php @@ -0,0 +1,87 @@ + + */ + protected array $arrayFields = [ + 'type', + 'id', + 'amount_in_minor', + 'currency', + 'status', + ]; + + /** + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string + */ + public function getId(): string + { + return $this->id; + } + + /** + * @return int + */ + public function getAmountInMinor(): int + { + return $this->amountInMinor; + } + + /** + * @return string + */ + public function getCurrency(): string + { + return $this->currency; + } + + /** + * @return string + */ + public function getStatus(): string + { + return $this->status; + } +} diff --git a/src/Entities/MerchantAccount/Transactions/Returns/MerchantAccountExternalPaymentReturn.php b/src/Entities/MerchantAccount/Transactions/Returns/MerchantAccountExternalPaymentReturn.php new file mode 100644 index 00000000..03346351 --- /dev/null +++ b/src/Entities/MerchantAccount/Transactions/Returns/MerchantAccountExternalPaymentReturn.php @@ -0,0 +1,30 @@ +type; + } +} diff --git a/src/Entities/MerchantAccount/Transactions/Returns/MerchantAccountExternalPaymentReturnIdentified.php b/src/Entities/MerchantAccount/Transactions/Returns/MerchantAccountExternalPaymentReturnIdentified.php new file mode 100644 index 00000000..db099a60 --- /dev/null +++ b/src/Entities/MerchantAccount/Transactions/Returns/MerchantAccountExternalPaymentReturnIdentified.php @@ -0,0 +1,33 @@ +returnId; + } +} diff --git a/src/Entities/MerchantAccount/Transactions/Returns/MerchantAccountExternalPaymentReturnUnknown.php b/src/Entities/MerchantAccount/Transactions/Returns/MerchantAccountExternalPaymentReturnUnknown.php new file mode 100644 index 00000000..7d867edd --- /dev/null +++ b/src/Entities/MerchantAccount/Transactions/Returns/MerchantAccountExternalPaymentReturnUnknown.php @@ -0,0 +1,12 @@ + + */ + public function getMetadata(): array; +} diff --git a/src/Interfaces/MerchantAccount/Transactions/MerchantAccountPayoutInterface.php b/src/Interfaces/MerchantAccount/Transactions/MerchantAccountPayoutInterface.php new file mode 100644 index 00000000..c53b4797 --- /dev/null +++ b/src/Interfaces/MerchantAccount/Transactions/MerchantAccountPayoutInterface.php @@ -0,0 +1,35 @@ + + */ + public function getMetadata(): array; +} diff --git a/src/Interfaces/MerchantAccount/Transactions/MerchantAccountPendingPayoutInterface.php b/src/Interfaces/MerchantAccount/Transactions/MerchantAccountPendingPayoutInterface.php new file mode 100644 index 00000000..76e47924 --- /dev/null +++ b/src/Interfaces/MerchantAccount/Transactions/MerchantAccountPendingPayoutInterface.php @@ -0,0 +1,10 @@ + + */ + public function getMetadata(): array; +} diff --git a/src/Interfaces/MerchantAccount/Transactions/MerchantAccountTransactionRetrievedInterface.php b/src/Interfaces/MerchantAccount/Transactions/MerchantAccountTransactionRetrievedInterface.php new file mode 100644 index 00000000..30177d93 --- /dev/null +++ b/src/Interfaces/MerchantAccount/Transactions/MerchantAccountTransactionRetrievedInterface.php @@ -0,0 +1,33 @@ +uri(Endpoints::MERCHANT_ACCOUNTS . '/' . $id) ->get(); } + + /** + * @param string $merchantAccountId + * @param \DateTimeInterface $from + * @param \DateTimeInterface $to + * + * @return MerchantAccountTransactionRetrievedInterface[] + * + * @throws ApiResponseUnsuccessfulException + * @throws SignerException + * @throws ApiRequestJsonSerializationException + */ + public function listTransactions(string $merchantAccountId, \DateTimeInterface $from, \DateTimeInterface $to): array + { + $uri = "{$merchantAccountId}/transactions?from={$from->format(\DateTimeInterface::ATOM)}&to={$to->format(\DateTimeInterface::ATOM)}"; + + $response = (array) $this->request() + ->uri(Endpoints::MERCHANT_ACCOUNTS . '/' . $uri) + ->get(); + + return isset($response['items']) && \is_array($response['items']) + ? $response['items'] + : []; + } } diff --git a/src/Services/Client/Client.php b/src/Services/Client/Client.php index 5d80644e..f451cb00 100644 --- a/src/Services/Client/Client.php +++ b/src/Services/Client/Client.php @@ -373,7 +373,6 @@ public function getPayout(string $id): PayoutRetrievedInterface } /** - * @throws InvalidArgumentException * @throws InvalidArgumentException * @throws SignerException * @throws ApiRequestJsonSerializationException diff --git a/tests/acceptance/MerchantAccountsTest.php b/tests/acceptance/MerchantAccountsTest.php index 824d0b1d..1aeadae3 100644 --- a/tests/acceptance/MerchantAccountsTest.php +++ b/tests/acceptance/MerchantAccountsTest.php @@ -35,3 +35,16 @@ \expect(\count($account->getAccountIdentifiers()))->toBeGreaterThan(0); \expect($account->getAccountIdentifiers()[0])->toBeInstanceOf(AccountIdentifierInterface::class); }); + +\it('retrieves merchant account transactions', function () { + $accounts = \client()->getMerchantAccounts(); + $account = \client()->getMerchantAccount($accounts[0]->getId()); + + $from = (new DateTime())->sub(new DateInterval("PT5H")); + $to = new DateTime(); + + $transactions = $account->getTransactions($from, $to); + \expect($transactions)->toBeArray(); + \expect(count($transactions))->toBeGreaterThan(0); + \expect($transactions[0])->toBeInstanceOf(\TrueLayer\Interfaces\MerchantAccount\Transactions\MerchantAccountTransactionRetrievedInterface::class); +}); diff --git a/tests/integration/MerchantAccountTest.php b/tests/integration/MerchantAccountTest.php index 77b52e74..3e618d8c 100644 --- a/tests/integration/MerchantAccountTest.php +++ b/tests/integration/MerchantAccountTest.php @@ -46,3 +46,21 @@ \expect($account->getAccountHolderName())->toBe('John Doe'); \expect($account->getAccountIdentifiers()[0])->toBeInstanceOf(IbanDetailsInterface::class); }); + +\it('retrieves a merchant account\'s transactions', function () { + $client = \client([MerchantAccountResponse::account(), MerchantAccountResponse::transactions()]); + + $account = $client->getMerchantAccount('1'); + + $from = DateTime::createFromFormat(DateTime::ATOM, '2025-05-01T00:00:00Z'); + $to = DateTime::createFromFormat(DateTime::ATOM, '2025-05-30T00:00:00Z'); + + $transactions = $account->getTransactions($from, $to); + + \expect(count($transactions))->toBe(5); + \expect($transactions[0])->toBeInstanceOf(\TrueLayer\Interfaces\MerchantAccount\Transactions\MerchantAccountPaymentInterface::class); + \expect($transactions[1])->toBeInstanceOf(\TrueLayer\Interfaces\MerchantAccount\Transactions\MerchantAccountExternalPaymentInterface::class); + \expect($transactions[2])->toBeInstanceOf(\TrueLayer\Interfaces\MerchantAccount\Transactions\MerchantAccountPendingPayoutInterface::class); + \expect($transactions[3])->toBeInstanceOf(\TrueLayer\Interfaces\MerchantAccount\Transactions\MerchantAccountExecutedPayoutInterface::class); + \expect($transactions[4])->toBeInstanceOf(\TrueLayer\Interfaces\MerchantAccount\Transactions\MerchantAccountRefundInterface::class); +}); diff --git a/tests/integration/Mocks/MerchantAccountResponse.php b/tests/integration/Mocks/MerchantAccountResponse.php index d097b6f8..8a5be3ad 100644 --- a/tests/integration/Mocks/MerchantAccountResponse.php +++ b/tests/integration/Mocks/MerchantAccountResponse.php @@ -17,4 +17,190 @@ public static function account(): Response { return new Response(200, [], '{"id":"a2dcee6d-7a00-414d-a1e6-8a2b23169e00","currency":"EUR","account_identifiers":[{"type":"iban","iban":"GB38CLRB04066200003769"}],"available_balance_in_minor":100000,"current_balance_in_minor":100000,"account_holder_name":"John Doe"}'); } + + public static function transactions(): Response + { + return new Response(200, [], '{ + "items": [ + { + "type": "merchant_account_payment", + "id": "string", + "currency": "GBP", + "amount_in_minor": 0, + "status": "settled", + "settled_at": "2025-05-03T10:23:00Z", + "payment_source": { + "id": "e2b41c9d-176k-67aa-b2da-fe1a2b97253c", + "account_holder_name": "string", + "account_identifiers": [ + { + "type": "sort_code_account_number", + "sort_code": 560029, + "account_number": 26207729 + }, + { + "type": "iban", + "iban": "GB32CLRB04066800012315" + }, + { + "type": "nrb", + "nrb": "string" + } + ] + }, + "payment_id": "0afd1f6a-f611-48ce-9488-321129bb3a70", + "metadata": { + "prop1": "value1", + "prop2": "value2" + } + }, + { + "type": "external_payment", + "id": "string", + "currency": "GBP", + "amount_in_minor": 0, + "status": "settled", + "settled_at": "2025-05-03T14:37:00Z", + "remitter": { + "account_holder_name": "string", + "reference": "string", + "account_identifiers": [ + { + "type": "sort_code_account_number", + "sort_code": 560029, + "account_number": 26207729 + }, + { + "type": "iban", + "iban": "GB32CLRB04066800012315" + }, + { + "type": "nrb", + "nrb": "string" + } + ] + }, + "return_for": { + "type": "identified", + "returned_id": "0cd1b0f7-71bc-4d24-b209-95259dadcc20" + } + }, + { + "type": "payout", + "id": "string", + "currency": "GBP", + "amount_in_minor": 0, + "status": "pending", + "created_at": "2025-05-02T11:11:11Z", + "beneficiary": { + "type": "external_account", + "reference": "string", + "account_holder_name": "string", + "account_identifiers": [ + { + "type": "sort_code_account_number", + "sort_code": 560029, + "account_number": 26207729 + }, + { + "type": "iban", + "iban": "GB32CLRB04066800012315" + }, + { + "type": "nrb", + "nrb": "string" + } + ] + }, + "context_code": "withdrawal", + "payout_id": "0cd1b0f7-71bc-4d24-b209-95259dadcc20", + "metadata": { + "prop1": "value1", + "prop2": "value2" + } + }, + { + "type": "payout", + "id": "string", + "currency": "GBP", + "amount_in_minor": 0, + "status": "executed", + "created_at": "2025-05-10T12:32:12Z", + "executed_at": "2025-05-10T13:32:12Z", + "beneficiary": { + "type": "external_account", + "reference": "string", + "account_holder_name": "string", + "account_identifiers": [ + { + "type": "sort_code_account_number", + "sort_code": 560029, + "account_number": 26207729 + }, + { + "type": "iban", + "iban": "GB32CLRB04066800012315" + }, + { + "type": "nrb", + "nrb": "string" + } + ] + }, + "context_code": "withdrawal", + "payout_id": "0cd1b0f7-71bc-4d24-b209-95259dadcc20", + "returned_by": "0cd1b0f7-71bc-4d24-b209-95259dadcc20", + "scheme_id": "faster_payments_service", + "metadata": { + "prop1": "value1", + "prop2": "value2" + } + }, + { + "type": "refund", + "id": "string", + "currency": "GBP", + "amount_in_minor": 0, + "status": "pending", + "created_at": "2025-05-14T12:32:12Z", + "executed_at": "2025-05-15T10:32:12Z", + "beneficiary": { + "type": "payment_source", + "payment_source_id": "string", + "user_id": "string", + "reference": "string", + "account_holder_name": "string", + "account_identifiers": [ + { + "type": "sort_code_account_number", + "sort_code": 560029, + "account_number": 26207729 + }, + { + "type": "iban", + "iban": "GB32CLRB04066800012315" + }, + { + "type": "nrb", + "nrb": "string" + } + ] + }, + "context_code": "withdrawal", + "refund_id": "43d12d0f-d775-410f-aaff-482200c17017", + "payment_id": "0afd1f6a-f611-48ce-9488-321129bb3a70", + "returned_by": "0cd1b0f7-71bc-4d24-b209-95259dadcc20", + "scheme_id": "faster_payments_service", + "metadata": { + "prop1": "value1", + "prop2": "value2" + } + } + ], + "pagination": { + "next_cursor": "bWFuZGF0ZXM6MmUwNDk0MTMK" + } + }' + ); + } }