Skip to content

Commit 59e39ec

Browse files
authored
[ACL-133] Add support for retry object on payment method (#56)
1 parent d6bfc89 commit 59e39ec

File tree

9 files changed

+117
-6
lines changed

9 files changed

+117
-6
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
66
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.2.0] - 2024-07-15
9+
10+
### Added
11+
12+
- Support for `retry` object on bank transfer payment method
13+
814
## [2.1.0] - 2024-05-16
915

1016
### Added

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,14 @@ $paymentMethod = $client->paymentMethod()->bankTransfer()
266266
->providerSelection($providerSelection);
267267
```
268268

269+
You can also enable payment retries, but make sure you can handle the `attempt_failed` payment status beforehand:
270+
271+
```php
272+
$paymentMethod = $client->paymentMethod()->bankTransfer()
273+
->enablePaymentRetry()
274+
->beneficiary($beneficiary);
275+
```
276+
269277
<a name="creating-the-payment"></a>
270278

271279
### 4. Creating the payment

src/Entities/Payment/PaymentMethod/BankTransferPaymentMethod.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace TrueLayer\Entities\Payment\PaymentMethod;
66

7+
use stdClass;
78
use TrueLayer\Constants\PaymentMethods;
89
use TrueLayer\Entities\Entity;
910
use TrueLayer\Exceptions\InvalidArgumentException;
@@ -29,12 +30,18 @@ class BankTransferPaymentMethod extends Entity implements BankTransferPaymentMet
2930
*/
3031
protected ProviderSelectionInterface $providerSelection;
3132

33+
/**
34+
* @var stdClass|null
35+
*/
36+
protected ?stdClass $retry;
37+
3238
/**
3339
* @var string[]
3440
*/
3541
protected array $casts = [
3642
'provider_selection' => ProviderSelectionInterface::class,
3743
'beneficiary' => BeneficiaryInterface::class,
44+
'retry' => stdClass::class,
3845
];
3946

4047
/**
@@ -44,6 +51,7 @@ class BankTransferPaymentMethod extends Entity implements BankTransferPaymentMet
4451
'type',
4552
'beneficiary',
4653
'provider_selection',
54+
'retry',
4755
];
4856

4957
/**
@@ -79,15 +87,32 @@ public function providerSelection(ProviderSelectionInterface $providerSelection)
7987
}
8088

8189
/**
90+
* @return ProviderSelectionInterface
8291
* @throws InvalidArgumentException
8392
*
84-
* @return ProviderSelectionInterface
8593
*/
8694
public function getProviderSelection(): ProviderSelectionInterface
8795
{
8896
return $this->providerSelection ?? $this->make(UserSelectedProviderSelectionInterface::class);
8997
}
9098

99+
/**
100+
* @return BankTransferPaymentMethodInterface
101+
*/
102+
public function enablePaymentRetry(): BankTransferPaymentMethodInterface
103+
{
104+
$this->retry = new stdClass();
105+
106+
return $this;
107+
}
108+
109+
public function isPaymentRetryEnabled(): bool
110+
{
111+
$retry = $this->retry ?? null;
112+
113+
return !!$retry;
114+
}
115+
91116
/**
92117
* @return string
93118
*/

src/Interfaces/PaymentMethod/BankTransferPaymentMethodInterface.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,14 @@ public function providerSelection(ProviderSelectionInterface $providerSelection)
3232
* @return ProviderSelectionInterface
3333
*/
3434
public function getProviderSelection(): ProviderSelectionInterface;
35+
36+
/**
37+
* @return BankTransferPaymentMethodInterface
38+
*/
39+
public function enablePaymentRetry(): BankTransferPaymentMethodInterface;
40+
41+
/**
42+
* @return bool
43+
*/
44+
public function isPaymentRetryEnabled(): bool;
3545
}

src/Traits/CastsAttributes.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ protected function casts(): array
2626
}
2727

2828
/**
29-
* @param mixed[] $data
29+
* @param mixed[] $data
3030
* @param mixed[]|null $casts
3131
*
32+
* @return mixed[]
3233
* @throws InvalidArgumentException
3334
*
34-
* @return mixed[]
3535
*/
3636
protected function castData(array $data, array $casts = null): array
3737
{
@@ -74,6 +74,8 @@ protected function castData(array $data, array $casts = null): array
7474
if (\is_string($partData)) {
7575
$partData = $this->toDateTime($partData);
7676
}
77+
} elseif ($abstract === \stdClass::class) {
78+
$partData = (object)$partData;
7779
} elseif (\is_array($partData) && \is_string($abstract) && (\interface_exists($abstract) || \class_exists($abstract))) {
7880
// @phpstan-ignore-next-line
7981
$partData = $this->make($abstract, $partData);
@@ -108,11 +110,11 @@ protected function toDateTime(string $dateTime): ?\DateTimeInterface
108110
* @template T
109111
*
110112
* @param class-string<T> $abstract
111-
* @param mixed[]|null $data
113+
* @param mixed[]|null $data
112114
*
115+
* @return T
113116
* @throws InvalidArgumentException
114117
*
115-
* @return T
116118
*/
117119
abstract protected function make(string $abstract, array $data = null);
118120
}

tests/acceptance/Payment/MerchantAccountPaymentAuthorizationTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,36 @@
3737
\expect($created->getResourceToken())->toBeString();
3838
\expect($created->getUserId())->toBeString();
3939

40+
/** @var BankTransferPaymentMethodInterface $paymentMethod */
4041
$paymentMethod = $created->getDetails()->getPaymentMethod();
4142
\expect($paymentMethod)->toBeInstanceOf(BankTransferPaymentMethodInterface::class);
4243
\expect($paymentMethod->getBeneficiary()->getReference())->toBe('TEST');
44+
\expect($paymentMethod->isPaymentRetryEnabled())->toBe(false);
4345

4446
return $created;
4547
});
4648

49+
\it('creates a merchant payment with retry enabled', function () {
50+
$helper = \paymentHelper();
51+
52+
$account = Arr::first(
53+
$helper->client()->getMerchantAccounts(),
54+
fn(MerchantAccountInterface $account) => $account->getCurrency() === 'GBP'
55+
);
56+
57+
$merchantBeneficiary = $helper->merchantBeneficiary($account);
58+
59+
$created = $helper->create(
60+
$helper->bankTransferMethod($merchantBeneficiary)->enablePaymentRetry(), $helper->user(), $account->getCurrency()
61+
);
62+
63+
\expect($created)->toBeInstanceOf(PaymentCreatedInterface::class);
64+
65+
/** @var BankTransferPaymentMethodInterface $paymentMethod */
66+
$paymentMethod = $created->getDetails()->getPaymentMethod();
67+
\expect($paymentMethod->isPaymentRetryEnabled())->toBe(true);
68+
});
69+
4770
\it('starts payment authorization', function (PaymentCreatedInterface $created) {
4871
$response = \client()->startPaymentAuthorization($created, 'https://console.truelayer.com/redirect-page');
4972

tests/integration/Mocks/PaymentResponse.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ public static function authorizationRequired(): Response
2929
return new Response(200, [], '{"id":"56bbff85-9504-4cba-a63b-c781745ad3ed","amount_in_minor":1,"metadata":{"metadata_key_1":"metadata_value_1","metadata_key_2":"metadata_value_2","metadata_key_3":"metadata_value_3"},"currency":"GBP","user":{"id":"c4e754fd-8b0d-40fe-bd61-36622b7477a4"},"payment_method":{"type":"bank_transfer","beneficiary":{"type":"external_account","account_identifier":{"type":"sort_code_account_number","sort_code":"010203","account_number":"12345678"},"account_holder_name":"Bob","reference":"TEST"},"provider_selection":{"type":"user_selected"}},"created_at":"2022-02-04T13:40:23.798415Z","status":"authorization_required"}');
3030
}
3131

32+
public static function authorizationRequiredWithRetryField(): Response
33+
{
34+
return new Response(200, [], '{"id":"56bbff85-9504-4cba-a63b-c781745ad3ed","amount_in_minor":1,"metadata":{"metadata_key_1":"metadata_value_1","metadata_key_2":"metadata_value_2","metadata_key_3":"metadata_value_3"},"currency":"GBP","user":{"id":"c4e754fd-8b0d-40fe-bd61-36622b7477a4"},"payment_method":{"type":"bank_transfer","retry":{},"beneficiary":{"type":"external_account","account_identifier":{"type":"sort_code_account_number","sort_code":"010203","account_number":"12345678"},"account_holder_name":"Bob","reference":"TEST"},"provider_selection":{"type":"user_selected"}},"created_at":"2022-02-04T13:40:23.798415Z","status":"authorization_required"}');
35+
}
36+
3237
public static function authorizingProviderSelection(): Response
3338
{
3439
return new Response(200, [], '{"id":"56bbff85-9504-4cba-a63b-c781745ad3ed","amount_in_minor":1,"metadata":{"metadata_key_1":"metadata_value_1","metadata_key_2":"metadata_value_2","metadata_key_3":"metadata_value_3"},"currency":"GBP","user":{"id":"c4e754fd-8b0d-40fe-bd61-36622b7477a4"},"payment_method":{"type":"bank_transfer","beneficiary":{"type":"external_account","account_identifier":{"type":"sort_code_account_number","sort_code":"010203","account_number":"12345678"},"account_holder_name":"Bob","reference":"TEST"},"provider_selection":{"type":"user_selected"}},"created_at":"2022-02-04T13:40:23.798415Z","status":"authorizing","authorization_flow":{"actions":{"next":{"type":"provider_selection","providers":[{"id":"mock-payments-gb-redirect","display_name":"Mock UK Payments - Redirect Flow","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/icons/mock-payments-gb-redirect.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/logos/mock-payments-gb-redirect.svg","bg_color":"#FFFFFF","country_code":"GB"},{"id":"oauth-starling","display_name":"Starling","icon_uri":"https://truelayer-client-logos.s3-eu-west-1.amazonaws.com/banks/banks-icons/oauth-starling-icon.svg","logo_uri":"https://truelayer-client-logos.s3-eu-west-1.amazonaws.com/banks/oauth-starling.svg","bg_color":"#007EB6","country_code":"GB"},{"id":"ob-barclays","display_name":"Barclays","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/icons/barclays.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/logos/barclays.svg","bg_color":"#007EB6","country_code":"GB"},{"id":"ob-boi","display_name":"Bank of Ireland UK","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/icons/boi.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/logos/boi.svg","bg_color":"#125B84","country_code":"GB"},{"id":"ob-bos","display_name":"Bank of Scotland","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/icons/bos.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/logos/bos.svg","bg_color":"#05286a","country_code":"GB"},{"id":"ob-danske","display_name":"Danske Bank","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/icons/danske.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/logos/danske.svg","bg_color":"#003755","country_code":"GB"},{"id":"ob-first-direct","display_name":"first direct","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/icons/first-direct.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/logos/first-direct.svg","bg_color":"#626268","country_code":"GB"},{"id":"ob-halifax","display_name":"Halifax","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/icons/halifax.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/logos/halifax.svg","bg_color":"#0040bb","country_code":"GB"},{"id":"ob-hsbc","display_name":"HSBC","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/icons/hsbc.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/logos/hsbc.svg","bg_color":"#515358","country_code":"GB"},{"id":"ob-lloyds","display_name":"Lloyds","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/icons/lloyds.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/logos/lloyds.svg","bg_color":"#00553e","country_code":"GB"},{"id":"ob-monzo","display_name":"Monzo","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/icons/monzo.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/logos/monzo.svg","bg_color":"#15233c","country_code":"GB"},{"id":"ob-nationwide","display_name":"Nationwide","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/icons/nationwide.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/logos/nationwide.svg","bg_color":"#002878","country_code":"GB"},{"id":"ob-natwest","display_name":"NatWest","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/icons/natwest.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/logos/natwest.svg","bg_color":"#42145f","country_code":"GB"},{"id":"ob-rbs","display_name":"Royal Bank of Scotland","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/icons/rbs.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/logos/rbs.svg","bg_color":"#0A2F64","country_code":"GB"},{"id":"ob-revolut","display_name":"Revolut","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/icons/revolut.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/logos/revolut.svg","bg_color":"#0067EA","country_code":"GB"},{"id":"ob-santander","display_name":"Santander","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/icons/santander.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/logos/santander.svg","bg_color":"#EC0000","country_code":"GB"},{"id":"ob-tesco","display_name":"Tesco Bank","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/icons/tesco.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/logos/tesco.svg","bg_color":"#1b3160","country_code":"GB"},{"id":"ob-tsb","display_name":"TSB","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/icons/tsb.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/uk/logos/tsb.svg","bg_color":"#007BC3","country_code":"GB"},{"id":"ob-uki-mock-bank","display_name":"UKI Mock Bank","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/icons/generic.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/logos/generic.svg","bg_color":"","country_code":"GB"},{"id":"ob-ulster","display_name":"Ulster Bank","icon_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/icons/ulster.svg","logo_uri":"https://truelayer-provider-assets.s3.amazonaws.com/global/logos/ulster.svg","bg_color":"#0a2f64","country_code":"GB"}]}},"configuration":{"provider_selection":{},"redirect":{"return_uri":"https://penny.t7r.dev/redirect/v3"}}}}');

tests/integration/PaymentCreateTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
],
5555
'scheme_selection' => null,
5656
],
57+
'retry' => null,
5758
],
5859
]);
5960
});
@@ -203,6 +204,24 @@
203204
]);
204205
});
205206

207+
\it('does not send retry field', function () {
208+
$factory = CreatePayment::responses([PaymentResponse::created()]);
209+
$factory->payment($factory->newUser(), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create();
210+
211+
$payload = json_decode(\getRequestPayload(1, false), false);
212+
expect($payload->payment_method->retry)->toBeNull();
213+
});
214+
215+
\it('sends retry field', function () {
216+
$factory = CreatePayment::responses([PaymentResponse::created()]);
217+
$method = $factory->bankTransferMethod($factory->sortCodeBeneficiary())->enablePaymentRetry();
218+
$factory->payment($factory->newUser(), $method)->create();
219+
220+
$payload = json_decode(\getRequestPayload(1, false), false);
221+
222+
expect($payload->payment_method->retry)->toBeInstanceOf(stdClass::class);
223+
});
224+
206225
\it('parses payment creation response correctly', function () {
207226
$factory = CreatePayment::responses([PaymentResponse::created()]);
208227
$payment = $factory->payment($factory->newUser(), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create();

tests/integration/PaymentRetrievalTest.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,9 @@ function assertPaymentCommon(PaymentRetrievedInterface $payment)
282282
$providerSelection = $method->getProviderSelection();
283283
$schemeSelection = $providerSelection->getSchemeSelection();
284284

285-
286285
\expect($schemeSelection)->toBeInstanceOf($expectedType);
287286
\expect($schemeSelection->getType())->toBe($expectedTypeValue);
287+
288288
if ($expectedAllowedRemitterFee !== null) {
289289
/** @var InstantSchemeSelectionInterface $selection */
290290
$selection = $schemeSelection;
@@ -309,3 +309,16 @@ function assertPaymentCommon(PaymentRetrievedInterface $payment)
309309
'expectedAllowedRemitterFee' => true,
310310
]
311311
]);
312+
313+
it('handles retry field correctly - empty object serialisation', function () {
314+
$payment1 = \client(PaymentResponse::authorizationRequiredWithRetryField())->getPayment('1');
315+
/** @var BankTransferPaymentMethodInterface $method */
316+
$method1 = $payment1->getPaymentMethod();
317+
318+
$payment2 = \client(PaymentResponse::authorizationRequired())->getPayment('1');
319+
/** @var BankTransferPaymentMethodInterface $method */
320+
$method2 = $payment2->getPaymentMethod();
321+
322+
expect($method1->isPaymentRetryEnabled())->toBe(true);
323+
expect($method2->isPaymentRetryEnabled())->toBe(false);
324+
});

0 commit comments

Comments
 (0)