From 5f60db615ac8e87f09e6ccfefe748ce2cd2a4897 Mon Sep 17 00:00:00 2001 From: Stefan Adrian Danaita <me@dsa.io> Date: Wed, 31 Jan 2024 15:18:23 +0100 Subject: [PATCH 01/11] [PHP-70] Support for setting the user's address and date of birth --- config/bindings.php | 5 +- src/Entities/Address.php | 185 ++++++++++++++++++ src/Entities/Entity.php | 2 +- src/Entities/User.php | 89 ++++++++- src/Interfaces/AddressInterface.php | 80 ++++++++ src/Interfaces/UserInterface.php | 35 ++++ tests/acceptance/Payment/CreatePayment.php | 20 ++ ...erchantAccountPaymentAuthorizationTest.php | 47 +++++ tests/integration/Mocks/CreatePayment.php | 21 ++ tests/integration/PaymentCreateTest.php | 56 ++++++ 10 files changed, 532 insertions(+), 8 deletions(-) create mode 100644 src/Entities/Address.php create mode 100644 src/Interfaces/AddressInterface.php diff --git a/config/bindings.php b/config/bindings.php index d439a85a..4fac259f 100644 --- a/config/bindings.php +++ b/config/bindings.php @@ -3,13 +3,14 @@ declare(strict_types=1); use TrueLayer\Entities; -use TrueLayer\Entities\User; use TrueLayer\Interfaces; return [ - Interfaces\UserInterface::class => User::class, Interfaces\HppInterface::class => 'makeHpp', + Interfaces\AddressInterface::class => Entities\Address::class, + Interfaces\UserInterface::class => Entities\User::class, + Interfaces\Beneficiary\BeneficiaryBuilderInterface::class => Entities\Beneficiary\BeneficiaryBuilder::class, Interfaces\Beneficiary\MerchantBeneficiaryInterface::class => Entities\Beneficiary\MerchantBeneficiary::class, Interfaces\Beneficiary\ExternalAccountBeneficiaryInterface::class => Entities\Beneficiary\ExternalAccountBeneficiary::class, diff --git a/src/Entities/Address.php b/src/Entities/Address.php new file mode 100644 index 00000000..e902561a --- /dev/null +++ b/src/Entities/Address.php @@ -0,0 +1,185 @@ +<?php + +namespace TrueLayer\Entities; + +use TrueLayer\Interfaces\AddressInterface; + +class Address extends Entity implements AddressInterface +{ + /** + * @var string + */ + protected string $addressLine1; + + /** + * @var string + */ + protected string $addressLine2; + + /** + * @var string + */ + protected string $city; + + /** + * @var string + */ + protected string $state; + + /** + * @var string + */ + protected string $zip; + + /** + * @var string + */ + protected string $countryCode; + + /** + * @var string[] + */ + protected array $arrayFields = [ + 'address_line1', + 'address_line2', + 'city', + 'state', + 'zip', + 'country_code', + ]; + + /** + * @return array + */ + protected function rules(): array + { + return [ + 'address_line1' => 'string|required', + 'address_line2' => 'string|nullable', + 'city' => 'string|required', + 'state' => 'string|required', + 'zip' => 'string|required', + 'country_code' => 'string|required', + ]; + } + + /** + * @return string + */ + public function getAddressLine1(): string + { + return $this->addressLine1; + } + + /** + * @param string $addressLine1 + * + * @return AddressInterface + */ + public function addressLine1(string $addressLine1): AddressInterface + { + $this->addressLine1 = $addressLine1; + + return $this; + } + + /** + * @return string|null + */ + public function getAddressLine2(): ?string + { + return $this->addressLine2 ?? null; + } + + /** + * @param string $addressLine2 + * + * @return AddressInterface + */ + public function addressLine2(string $addressLine2): AddressInterface + { + $this->addressLine2 = $addressLine2; + + return $this; + } + + /** + * @return string + */ + public function getCity(): string + { + return $this->city; + } + + /** + * @param string $city + * + * @return AddressInterface + */ + public function city(string $city): AddressInterface + { + $this->city = $city; + + return $this; + } + + /** + * @return string + */ + public function getState(): string + { + return $this->state; + } + + /** + * @param string $state + * + * @return AddressInterface + */ + public function state(string $state): AddressInterface + { + $this->state = $state; + + return $this; + } + + /** + * @return string + */ + public function getZip(): string + { + return $this->zip; + } + + /** + * @param string $zip + * + * @return AddressInterface + */ + public function zip(string $zip): AddressInterface + { + $this->zip = $zip; + + return $this; + } + + /** + * @return string + */ + public function getCountryCode(): string + { + return $this->countryCode; + } + + /** + * @param string $countryCode + * + * @return AddressInterface + */ + public function countryCode(string $countryCode): AddressInterface + { + $this->countryCode = $countryCode; + + return $this; + } +} diff --git a/src/Entities/Entity.php b/src/Entities/Entity.php index 1977f8fe..6d5c0afe 100644 --- a/src/Entities/Entity.php +++ b/src/Entities/Entity.php @@ -23,7 +23,7 @@ abstract class Entity implements ArrayableInterface, HasAttributesInterface /** * @var EntityFactoryInterface */ - private EntityFactoryInterface $entityFactory; + protected EntityFactoryInterface $entityFactory; /** * @param ValidatorFactory $validatorFactory diff --git a/src/Entities/User.php b/src/Entities/User.php index 31bd31d5..a39b3e8a 100644 --- a/src/Entities/User.php +++ b/src/Entities/User.php @@ -4,7 +4,11 @@ namespace TrueLayer\Entities; +use TrueLayer\Exceptions\InvalidArgumentException; +use TrueLayer\Exceptions\ValidationException; +use TrueLayer\Interfaces\AddressInterface; use TrueLayer\Interfaces\UserInterface; +use TrueLayer\Validation\ValidType; final class User extends Entity implements UserInterface { @@ -28,6 +32,16 @@ final class User extends Entity implements UserInterface */ protected string $phone; + /** + * @var AddressInterface + */ + protected AddressInterface $address; + + /** + * @var string + */ + protected string $dateOfBirth; + /** * @var string[] */ @@ -36,18 +50,32 @@ final class User extends Entity implements UserInterface 'name', 'email', 'phone', + 'address', + 'date_of_birth', ]; /** * @var string[] */ - protected array $rules = [ - 'id' => 'string|nullable', - 'name' => 'string|nullable|required_without:id', - 'email' => 'string|nullable|email|required_without_all:phone,id', - 'phone' => 'string|nullable|required_without_all:email,id', + protected array $casts = [ + 'address' => AddressInterface::class, ]; + /** + * @return array + */ + protected function rules(): array + { + return [ + 'id' => 'string|nullable', + 'name' => 'string|nullable|required_without:id', + 'email' => 'string|nullable|email|required_without_all:phone,id', + 'phone' => 'string|nullable|required_without_all:email,id', + 'address' => ['nullable', ValidType::of(AddressInterface::class)], + 'date_of_birth' => 'string|nullable|date', + ]; + } + /** * @return string|null */ @@ -127,4 +155,55 @@ public function phone(string $phone): UserInterface return $this; } + + /** + * @return AddressInterface|null + */ + public function getAddress(): ?AddressInterface + { + return $this->address ?? null; + } + + /** + * @param AddressInterface $address + * + * @return UserInterface + */ + public function address(AddressInterface $address): UserInterface + { + $this->address = $address; + + return $this; + } + + /** + * @throws ValidationException + * @throws InvalidArgumentException + * + * @return AddressInterface + */ + public function addressBuilder(): AddressInterface + { + return $this->entityFactory->make(AddressInterface::class); + } + + /** + * @return string|null + */ + public function getDateOfBirth(): ?string + { + return $this->dateOfBirth ?? null; + } + + /** + * @param string $dateOfBirth + * + * @return UserInterface + */ + public function dateOfBirth(string $dateOfBirth): UserInterface + { + $this->dateOfBirth = $dateOfBirth; + + return $this; + } } diff --git a/src/Interfaces/AddressInterface.php b/src/Interfaces/AddressInterface.php new file mode 100644 index 00000000..0efab398 --- /dev/null +++ b/src/Interfaces/AddressInterface.php @@ -0,0 +1,80 @@ +<?php + +declare(strict_types=1); + +namespace TrueLayer\Interfaces; + +interface AddressInterface extends ArrayableInterface, HasAttributesInterface +{ + /** + * @return string + */ + public function getAddressLine1(): string; + + /** + * @param string $addressLine1 + * + * @return AddressInterface + */ + public function addressLine1(string $addressLine1): AddressInterface; + + /** + * @return string|null + */ + public function getAddressLine2(): ?string; + + /** + * @param string $addressLine2 + * + * @return AddressInterface + */ + public function addressLine2(string $addressLine2): AddressInterface; + + /** + * @return string + */ + public function getCity(): string; + + /** + * @param string $city + * + * @return AddressInterface + */ + public function city(string $city): AddressInterface; + + /** + * @return string + */ + public function getState(): string; + + /** + * @param string $state + * + * @return AddressInterface + */ + public function state(string $state): AddressInterface; + + /** + * @return string + */ + public function getZip(): string; + + /** + * @param string $zip + * + * @return AddressInterface + */ + public function zip(string $zip): AddressInterface; + + /** + * @return string + */ + public function getCountryCode(): string; + + /** + * @param string $countryCode + * + * @return AddressInterface + */ + public function countryCode(string $countryCode): AddressInterface; +} diff --git a/src/Interfaces/UserInterface.php b/src/Interfaces/UserInterface.php index 6a22c924..276e569c 100644 --- a/src/Interfaces/UserInterface.php +++ b/src/Interfaces/UserInterface.php @@ -4,6 +4,9 @@ namespace TrueLayer\Interfaces; +use TrueLayer\Exceptions\InvalidArgumentException; +use TrueLayer\Exceptions\ValidationException; + interface UserInterface extends ArrayableInterface, HasAttributesInterface { /** @@ -53,4 +56,36 @@ public function getPhone(): ?string; * @return UserInterface */ public function phone(string $phone): UserInterface; + + /** + * @return AddressInterface|null + */ + public function getAddress(): ?AddressInterface; + + /** + * @param AddressInterface $address + * + * @return UserInterface + */ + public function address(AddressInterface $address): UserInterface; + + /** + * @throws ValidationException + * @throws InvalidArgumentException + * + * @return AddressInterface + */ + public function addressBuilder(): AddressInterface; + + /** + * @return string|null + */ + public function getDateOfBirth(): ?string; + + /** + * @param string $dateOfBirth + * + * @return UserInterface + */ + public function dateOfBirth(string $dateOfBirth): UserInterface; } diff --git a/tests/acceptance/Payment/CreatePayment.php b/tests/acceptance/Payment/CreatePayment.php index 57a23d6c..961b39a8 100644 --- a/tests/acceptance/Payment/CreatePayment.php +++ b/tests/acceptance/Payment/CreatePayment.php @@ -69,6 +69,26 @@ public function user(): UserInterface ->email('alice@truelayer.com'); } + public function userWithAddress(): UserInterface + { + $user = $this->user(); + $address = $user->addressBuilder() + ->addressLine1("The Gilbert") + ->city("London") + ->state("London") + ->zip("EC2A 1PX") + ->countryCode("GB"); + + return $this->user() + ->address($address); + } + + public function userWithDateOfBirth(string $date): UserInterface + { + return $this->user() + ->dateOfBirth($date); + } + /** * @param BeneficiaryInterface $beneficiary * diff --git a/tests/acceptance/Payment/MerchantAccountPaymentAuthorizationTest.php b/tests/acceptance/Payment/MerchantAccountPaymentAuthorizationTest.php index 42d48ad7..ec85d2d5 100644 --- a/tests/acceptance/Payment/MerchantAccountPaymentAuthorizationTest.php +++ b/tests/acceptance/Payment/MerchantAccountPaymentAuthorizationTest.php @@ -134,6 +134,53 @@ \expect($fetched->getId())->toBeString(); }); +\it('creates payment with user address', function () { + $helper = \paymentHelper(); + + $payment = $helper->client()->payment() + ->paymentMethod($helper->bankTransferMethod($helper->sortCodeBeneficiary())) + ->amountInMinor(10) + ->currency('GBP') + ->user($helper->userWithAddress()) + ->create(); + + $fetched = $payment->getDetails(); + + \expect($payment)->toBeInstanceOf(PaymentCreatedInterface::class); + \expect($payment->getId())->toBeString(); + \expect($fetched)->toBeInstanceOf(PaymentRetrievedInterface::class); + \expect($fetched->getId())->toBeString(); +}); + +\it('creates payment with valid user date of birth', function () { + $helper = \paymentHelper(); + + $payment = $helper->client()->payment() + ->paymentMethod($helper->bankTransferMethod($helper->sortCodeBeneficiary())) + ->amountInMinor(10) + ->currency('GBP') + ->user($helper->userWithDateOfBirth('2024-01-01')) + ->create(); + + $fetched = $payment->getDetails(); + + \expect($payment)->toBeInstanceOf(PaymentCreatedInterface::class); + \expect($payment->getId())->toBeString(); + \expect($fetched)->toBeInstanceOf(PaymentRetrievedInterface::class); + \expect($fetched->getId())->toBeString(); +}); + +\it('throws exception when creating payment with invalid user date of birth', function () { + $helper = \paymentHelper(); + + $helper->client()->payment() + ->paymentMethod($helper->bankTransferMethod($helper->sortCodeBeneficiary())) + ->amountInMinor(10) + ->currency('GBP') + ->user($helper->userWithDateOfBirth('invalid date')) + ->create(); +})->throws(TrueLayer\Exceptions\ValidationException::class); + \it('creates payment with idempotency key', function () { $helper = \paymentHelper(); diff --git a/tests/integration/Mocks/CreatePayment.php b/tests/integration/Mocks/CreatePayment.php index 5426a7e8..377b1f0c 100644 --- a/tests/integration/Mocks/CreatePayment.php +++ b/tests/integration/Mocks/CreatePayment.php @@ -57,6 +57,27 @@ public function newUser(): UserInterface ->email('alice@truelayer.com'); } + public function newUserWithAddress(): UserInterface + { + $address = $this->client + ->user() + ->addressBuilder() + ->addressLine1("The Gilbert") + ->city("London") + ->state("London") + ->zip("EC2A 1PX") + ->countryCode("GB"); + + return $this->newUser() + ->address($address); + } + + public function newUserWithDateOfBirth(string $date) + { + return $this->newUser() + ->dateOfBirth($date); + } + /** * @return UserInterface */ diff --git a/tests/integration/PaymentCreateTest.php b/tests/integration/PaymentCreateTest.php index f27cc1c5..34714f95 100644 --- a/tests/integration/PaymentCreateTest.php +++ b/tests/integration/PaymentCreateTest.php @@ -67,6 +67,8 @@ 'name' => 'Alice', 'phone' => '+447837485713', 'email' => 'alice@truelayer.com', + 'address' => null, + 'date_of_birth' => null, ], ]); @@ -76,10 +78,64 @@ 'name' => null, 'phone' => null, 'email' => null, + 'address' => null, + 'date_of_birth' => null, ], ]); }); +\it('sends the right user address', function () { + $factory = CreatePayment::responses([ + PaymentResponse::created(), + ]); + $factory->payment($factory->newUserWithAddress(), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create(); + + \expect(\getRequestPayload(1))->toMatchArray([ + 'user' => [ + 'id' => null, + 'name' => 'Alice', + 'phone' => '+447837485713', + 'email' => 'alice@truelayer.com', + 'address' => [ + 'address_line1' => 'The Gilbert', + 'address_line2' => null, + 'city' => 'London', + 'state' => 'London', + 'zip' => 'EC2A 1PX', + 'country_code' => 'GB', + ], + 'date_of_birth' => null, + ], + ]); +}); + +\it('sends the right date of birth', function () { + $factory = CreatePayment::responses([ + PaymentResponse::created(), + ]); + + $factory->payment($factory->newUserWithDateOfBirth("2024-01-01"), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create(); + + \expect(\getRequestPayload(1))->toMatchArray([ + 'user' => [ + 'id' => null, + 'name' => 'Alice', + 'phone' => '+447837485713', + 'email' => 'alice@truelayer.com', + 'address' => null, + 'date_of_birth' => "2024-01-01", + ], + ]); +}); + +\it('should throw when sending an invalid user date of birth', function () { + $factory = CreatePayment::responses([ + PaymentResponse::created(), + ]); + + $factory->payment($factory->newUserWithDateOfBirth("invalid data"), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create(); +})->throws(TrueLayer\Exceptions\ValidationException::class); + \it('parses payment creation response correctly', function () { $factory = CreatePayment::responses([PaymentResponse::created()]); $payment = $factory->payment($factory->newUser(), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create(); From 2f18b1b0e142777d4641e04c5b51249b4a717b94 Mon Sep 17 00:00:00 2001 From: Stefan Adrian Danaita <me@dsa.io> Date: Wed, 31 Jan 2024 15:20:00 +0100 Subject: [PATCH 02/11] [PHP-70] Strict types --- src/Entities/Address.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Entities/Address.php b/src/Entities/Address.php index e902561a..d64ffc77 100644 --- a/src/Entities/Address.php +++ b/src/Entities/Address.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace TrueLayer\Entities; use TrueLayer\Interfaces\AddressInterface; From 0e78bb240666e63b7e9d79d1c88f5047b84a6b18 Mon Sep 17 00:00:00 2001 From: Stefan Adrian Danaita <me@dsa.io> Date: Wed, 31 Jan 2024 19:27:32 +0100 Subject: [PATCH 03/11] [PHP-70] Simplify the address setter --- src/Entities/User.php | 19 +++++-------------- src/Interfaces/UserInterface.php | 12 ++---------- tests/integration/Mocks/CreatePayment.php | 11 +++++------ 3 files changed, 12 insertions(+), 30 deletions(-) diff --git a/src/Entities/User.php b/src/Entities/User.php index a39b3e8a..08e64695 100644 --- a/src/Entities/User.php +++ b/src/Entities/User.php @@ -165,28 +165,19 @@ public function getAddress(): ?AddressInterface } /** - * @param AddressInterface $address + * @param AddressInterface|null $address * - * @return UserInterface - */ - public function address(AddressInterface $address): UserInterface - { - $this->address = $address; - - return $this; - } - - /** * @throws ValidationException * @throws InvalidArgumentException * * @return AddressInterface */ - public function addressBuilder(): AddressInterface + public function address(?AddressInterface $address = null): AddressInterface { - return $this->entityFactory->make(AddressInterface::class); - } + $this->address = $address ?: $this->entityFactory->make(AddressInterface::class); + return $this->getAddress(); + } /** * @return string|null */ diff --git a/src/Interfaces/UserInterface.php b/src/Interfaces/UserInterface.php index 276e569c..a3254e91 100644 --- a/src/Interfaces/UserInterface.php +++ b/src/Interfaces/UserInterface.php @@ -63,19 +63,11 @@ public function phone(string $phone): UserInterface; public function getAddress(): ?AddressInterface; /** - * @param AddressInterface $address - * - * @return UserInterface - */ - public function address(AddressInterface $address): UserInterface; - - /** - * @throws ValidationException - * @throws InvalidArgumentException + * @param AddressInterface|null $address * * @return AddressInterface */ - public function addressBuilder(): AddressInterface; + public function address(?AddressInterface $address): AddressInterface; /** * @return string|null diff --git a/tests/integration/Mocks/CreatePayment.php b/tests/integration/Mocks/CreatePayment.php index 377b1f0c..3b2006f3 100644 --- a/tests/integration/Mocks/CreatePayment.php +++ b/tests/integration/Mocks/CreatePayment.php @@ -59,20 +59,19 @@ public function newUser(): UserInterface public function newUserWithAddress(): UserInterface { - $address = $this->client - ->user() - ->addressBuilder() + $user = $this->newUser(); + + $user->address() ->addressLine1("The Gilbert") ->city("London") ->state("London") ->zip("EC2A 1PX") ->countryCode("GB"); - return $this->newUser() - ->address($address); + return $user; } - public function newUserWithDateOfBirth(string $date) + public function newUserWithDateOfBirth(string $date): UserInterface { return $this->newUser() ->dateOfBirth($date); From 092afed0f8081dba9ae7a506dcc7a5d11ee265f4 Mon Sep 17 00:00:00 2001 From: Stefan Adrian Danaita <me@dsa.io> Date: Wed, 31 Jan 2024 19:33:08 +0100 Subject: [PATCH 04/11] [PHP-70] PHPStan errors --- src/Entities/Address.php | 21 +++++++++------------ src/Entities/User.php | 4 ++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Entities/Address.php b/src/Entities/Address.php index d64ffc77..8a9d8efd 100644 --- a/src/Entities/Address.php +++ b/src/Entities/Address.php @@ -51,19 +51,16 @@ class Address extends Entity implements AddressInterface ]; /** - * @return array + * @var string[] */ - protected function rules(): array - { - return [ - 'address_line1' => 'string|required', - 'address_line2' => 'string|nullable', - 'city' => 'string|required', - 'state' => 'string|required', - 'zip' => 'string|required', - 'country_code' => 'string|required', - ]; - } + protected array $rules = [ + 'address_line1' => 'string|required', + 'address_line2' => 'string|nullable', + 'city' => 'string|required', + 'state' => 'string|required', + 'zip' => 'string|required', + 'country_code' => 'string|required', + ]; /** * @return string diff --git a/src/Entities/User.php b/src/Entities/User.php index 08e64695..33725541 100644 --- a/src/Entities/User.php +++ b/src/Entities/User.php @@ -62,7 +62,7 @@ final class User extends Entity implements UserInterface ]; /** - * @return array + * @return mixed[] */ protected function rules(): array { @@ -176,7 +176,7 @@ public function address(?AddressInterface $address = null): AddressInterface { $this->address = $address ?: $this->entityFactory->make(AddressInterface::class); - return $this->getAddress(); + return $this->address; } /** * @return string|null From 55032c11842372d2d2b0c73e676b0ed38c201e45 Mon Sep 17 00:00:00 2001 From: Stefan Adrian Danaita <me@dsa.io> Date: Wed, 31 Jan 2024 19:36:45 +0100 Subject: [PATCH 05/11] [PHP-70] Add PHP 8.2 and 8.3 to the code quality check matrix --- .github/workflows/php.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index d6cab5a1..8bf1d30c 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -23,6 +23,8 @@ jobs: - "7.4" - "8.0" - "8.1" + - "8.2" + - "8.3" steps: - name: Checkout uses: actions/checkout@v2 From fe2536be7607c74765cbfb78c13f8877ad6a4a4a Mon Sep 17 00:00:00 2001 From: Stefan Adrian Danaita <me@dsa.io> Date: Wed, 31 Jan 2024 19:43:12 +0100 Subject: [PATCH 06/11] Revert "[PHP-70] Add PHP 8.2 and 8.3 to the code quality check matrix" This reverts commit 55032c11842372d2d2b0c73e676b0ed38c201e45. --- .github/workflows/php.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 8bf1d30c..d6cab5a1 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -23,8 +23,6 @@ jobs: - "7.4" - "8.0" - "8.1" - - "8.2" - - "8.3" steps: - name: Checkout uses: actions/checkout@v2 From 304c1b10a64228ff05c34382e5581180df4ca2eb Mon Sep 17 00:00:00 2001 From: Stefan Adrian Danaita <me@dsa.io> Date: Tue, 6 Feb 2024 16:23:13 +0000 Subject: [PATCH 07/11] [PHP-70] Make address fields optional and fix tests method --- src/Entities/Address.php | 30 +++++++++++----------- src/Entities/User.php | 1 + src/Interfaces/AddressInterface.php | 20 +++++++-------- src/Interfaces/UserInterface.php | 3 --- tests/acceptance/Payment/CreatePayment.php | 17 ++++++------ tests/integration/Mocks/CreatePayment.php | 10 ++++---- tests/integration/PaymentCreateTest.php | 6 ++--- 7 files changed, 42 insertions(+), 45 deletions(-) diff --git a/src/Entities/Address.php b/src/Entities/Address.php index 8a9d8efd..80dfaf61 100644 --- a/src/Entities/Address.php +++ b/src/Entities/Address.php @@ -63,11 +63,11 @@ class Address extends Entity implements AddressInterface ]; /** - * @return string + * @return string|null */ - public function getAddressLine1(): string + public function getAddressLine1(): ?string { - return $this->addressLine1; + return $this->addressLine1 ?? null; } /** @@ -103,11 +103,11 @@ public function addressLine2(string $addressLine2): AddressInterface } /** - * @return string + * @return string|null */ - public function getCity(): string + public function getCity(): ?string { - return $this->city; + return $this->city ?? null; } /** @@ -123,11 +123,11 @@ public function city(string $city): AddressInterface } /** - * @return string + * @return string|null */ - public function getState(): string + public function getState(): ?string { - return $this->state; + return $this->state ?? null; } /** @@ -143,11 +143,11 @@ public function state(string $state): AddressInterface } /** - * @return string + * @return string|null */ - public function getZip(): string + public function getZip(): ?string { - return $this->zip; + return $this->zip ?? null; } /** @@ -163,11 +163,11 @@ public function zip(string $zip): AddressInterface } /** - * @return string + * @return string|null */ - public function getCountryCode(): string + public function getCountryCode(): ?string { - return $this->countryCode; + return $this->countryCode ?? null; } /** diff --git a/src/Entities/User.php b/src/Entities/User.php index 33725541..5f19b71c 100644 --- a/src/Entities/User.php +++ b/src/Entities/User.php @@ -178,6 +178,7 @@ public function address(?AddressInterface $address = null): AddressInterface return $this->address; } + /** * @return string|null */ diff --git a/src/Interfaces/AddressInterface.php b/src/Interfaces/AddressInterface.php index 0efab398..dfc301da 100644 --- a/src/Interfaces/AddressInterface.php +++ b/src/Interfaces/AddressInterface.php @@ -7,9 +7,9 @@ interface AddressInterface extends ArrayableInterface, HasAttributesInterface { /** - * @return string + * @return string|null */ - public function getAddressLine1(): string; + public function getAddressLine1(): ?string; /** * @param string $addressLine1 @@ -31,9 +31,9 @@ public function getAddressLine2(): ?string; public function addressLine2(string $addressLine2): AddressInterface; /** - * @return string + * @return string|null */ - public function getCity(): string; + public function getCity(): ?string; /** * @param string $city @@ -43,9 +43,9 @@ public function getCity(): string; public function city(string $city): AddressInterface; /** - * @return string + * @return string|null */ - public function getState(): string; + public function getState(): ?string; /** * @param string $state @@ -55,9 +55,9 @@ public function getState(): string; public function state(string $state): AddressInterface; /** - * @return string + * @return string|null */ - public function getZip(): string; + public function getZip(): ?string; /** * @param string $zip @@ -67,9 +67,9 @@ public function getZip(): string; public function zip(string $zip): AddressInterface; /** - * @return string + * @return string|null */ - public function getCountryCode(): string; + public function getCountryCode(): ?string; /** * @param string $countryCode diff --git a/src/Interfaces/UserInterface.php b/src/Interfaces/UserInterface.php index a3254e91..5fad0f74 100644 --- a/src/Interfaces/UserInterface.php +++ b/src/Interfaces/UserInterface.php @@ -4,9 +4,6 @@ namespace TrueLayer\Interfaces; -use TrueLayer\Exceptions\InvalidArgumentException; -use TrueLayer\Exceptions\ValidationException; - interface UserInterface extends ArrayableInterface, HasAttributesInterface { /** diff --git a/tests/acceptance/Payment/CreatePayment.php b/tests/acceptance/Payment/CreatePayment.php index 961b39a8..db72e38f 100644 --- a/tests/acceptance/Payment/CreatePayment.php +++ b/tests/acceptance/Payment/CreatePayment.php @@ -72,15 +72,14 @@ public function user(): UserInterface public function userWithAddress(): UserInterface { $user = $this->user(); - $address = $user->addressBuilder() - ->addressLine1("The Gilbert") - ->city("London") - ->state("London") - ->zip("EC2A 1PX") - ->countryCode("GB"); - - return $this->user() - ->address($address); + $user->address() + ->addressLine1('The Gilbert') + ->city('London') + ->state('London') + ->zip('EC2A 1PX') + ->countryCode('GB'); + + return $user; } public function userWithDateOfBirth(string $date): UserInterface diff --git a/tests/integration/Mocks/CreatePayment.php b/tests/integration/Mocks/CreatePayment.php index 3b2006f3..1a8c3e54 100644 --- a/tests/integration/Mocks/CreatePayment.php +++ b/tests/integration/Mocks/CreatePayment.php @@ -62,11 +62,11 @@ public function newUserWithAddress(): UserInterface $user = $this->newUser(); $user->address() - ->addressLine1("The Gilbert") - ->city("London") - ->state("London") - ->zip("EC2A 1PX") - ->countryCode("GB"); + ->addressLine1('The Gilbert') + ->city('London') + ->state('London') + ->zip('EC2A 1PX') + ->countryCode('GB'); return $user; } diff --git a/tests/integration/PaymentCreateTest.php b/tests/integration/PaymentCreateTest.php index 34714f95..66de72dc 100644 --- a/tests/integration/PaymentCreateTest.php +++ b/tests/integration/PaymentCreateTest.php @@ -114,7 +114,7 @@ PaymentResponse::created(), ]); - $factory->payment($factory->newUserWithDateOfBirth("2024-01-01"), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create(); + $factory->payment($factory->newUserWithDateOfBirth('2024-01-01'), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create(); \expect(\getRequestPayload(1))->toMatchArray([ 'user' => [ @@ -123,7 +123,7 @@ 'phone' => '+447837485713', 'email' => 'alice@truelayer.com', 'address' => null, - 'date_of_birth' => "2024-01-01", + 'date_of_birth' => '2024-01-01', ], ]); }); @@ -133,7 +133,7 @@ PaymentResponse::created(), ]); - $factory->payment($factory->newUserWithDateOfBirth("invalid data"), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create(); + $factory->payment($factory->newUserWithDateOfBirth('invalid data'), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create(); })->throws(TrueLayer\Exceptions\ValidationException::class); \it('parses payment creation response correctly', function () { From 0df43fcd2ad411fef86c4e84a00973bcd4c91f9a Mon Sep 17 00:00:00 2001 From: Stefan Adrian Danaita <me@dsa.io> Date: Fri, 9 Feb 2024 14:01:14 +0000 Subject: [PATCH 08/11] [PHP-70] Fix tests --- .../Payment/ExternalAccountPaymentAuthorizationTest.php | 8 ++++---- tests/integration/Mocks/CreatePayment.php | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/acceptance/Payment/ExternalAccountPaymentAuthorizationTest.php b/tests/acceptance/Payment/ExternalAccountPaymentAuthorizationTest.php index d36d0e5a..a6fbea6c 100644 --- a/tests/acceptance/Payment/ExternalAccountPaymentAuthorizationTest.php +++ b/tests/acceptance/Payment/ExternalAccountPaymentAuthorizationTest.php @@ -66,7 +66,7 @@ \expect($config['redirect']['return_uri'])->toBe('https://console.truelayer.com/redirect-page'); return $created; -})->only(); +}); \it('starts payment authorization', function () { $created = \paymentHelper()->create(); @@ -90,7 +90,7 @@ \expect($config['form']['input_types'])->toBeEmpty(); return $created; -})->only(); +}); \it('starts payment authorization with hpp capabilities', function () { $created = \paymentHelper()->create(); @@ -108,7 +108,7 @@ \expect($config['redirect']['return_uri'])->toBe('https://console.truelayer.com/redirect-page'); \expect($config['redirect'])->not->toHaveKey('direct_return_uri'); \expect($config['form']['input_types'])->toContain(FormInputTypes::SELECT, FormInputTypes::TEXT, FormInputTypes::TEXT_WITH_IMAGE); -})->only(); +}); \it('starts payment authorization with all capabilities', function () { $created = \paymentHelper()->create(); @@ -130,7 +130,7 @@ \expect($config['redirect']['return_uri'])->toBe('https://console.truelayer.com/redirect-page'); \expect($config['redirect']['direct_return_uri'])->toBe('https://console.truelayer.com/direct-return-page'); \expect($config['form']['input_types'])->toContain(FormInputTypes::TEXT); -})->only(); +}); \it('retrieves payment as authorizing - provider selection', function (PaymentCreatedInterface $created) { /** @var PaymentAuthorizingInterface $payment */ diff --git a/tests/integration/Mocks/CreatePayment.php b/tests/integration/Mocks/CreatePayment.php index 1a8c3e54..508a3e98 100644 --- a/tests/integration/Mocks/CreatePayment.php +++ b/tests/integration/Mocks/CreatePayment.php @@ -60,7 +60,6 @@ public function newUser(): UserInterface public function newUserWithAddress(): UserInterface { $user = $this->newUser(); - $user->address() ->addressLine1('The Gilbert') ->city('London') From 8a0e1cea2e933328f7412445cc4150c20fe53e46 Mon Sep 17 00:00:00 2001 From: Stefan Adrian Danaita <me@dsa.io> Date: Fri, 9 Feb 2024 15:25:20 +0000 Subject: [PATCH 09/11] [PHP-70] User address's State should be optional --- src/Entities/Address.php | 2 +- tests/integration/Mocks/CreatePayment.php | 21 ++++--- tests/integration/PaymentCreateTest.php | 77 ++++++++++++++++++++++- 3 files changed, 90 insertions(+), 10 deletions(-) diff --git a/src/Entities/Address.php b/src/Entities/Address.php index 80dfaf61..00afc20f 100644 --- a/src/Entities/Address.php +++ b/src/Entities/Address.php @@ -57,7 +57,7 @@ class Address extends Entity implements AddressInterface 'address_line1' => 'string|required', 'address_line2' => 'string|nullable', 'city' => 'string|required', - 'state' => 'string|required', + 'state' => 'string|nullable', 'zip' => 'string|required', 'country_code' => 'string|required', ]; diff --git a/tests/integration/Mocks/CreatePayment.php b/tests/integration/Mocks/CreatePayment.php index 508a3e98..6e53a2a2 100644 --- a/tests/integration/Mocks/CreatePayment.php +++ b/tests/integration/Mocks/CreatePayment.php @@ -57,15 +57,22 @@ public function newUser(): UserInterface ->email('alice@truelayer.com'); } - public function newUserWithAddress(): UserInterface + public function newUserWithAddress($a): UserInterface { $user = $this->newUser(); - $user->address() - ->addressLine1('The Gilbert') - ->city('London') - ->state('London') - ->zip('EC2A 1PX') - ->countryCode('GB'); + $address = $user->address() + ->addressLine1($a['addressLine1']) + ->city($a['city']) + ->zip($a['zip']) + ->countryCode($a['countryCode']); + + if (array_key_exists('addressLine2', $a)) { + $address->addressLine2($a['addressLine2']); + } + + if (array_key_exists('state', $a)) { + $address->state($a['state']); + } return $user; } diff --git a/tests/integration/PaymentCreateTest.php b/tests/integration/PaymentCreateTest.php index 66de72dc..dbcd07e0 100644 --- a/tests/integration/PaymentCreateTest.php +++ b/tests/integration/PaymentCreateTest.php @@ -88,7 +88,80 @@ $factory = CreatePayment::responses([ PaymentResponse::created(), ]); - $factory->payment($factory->newUserWithAddress(), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create(); + + $address = [ + 'addressLine1' => 'The Gilbert', + 'addressLine2' => 'City of', + 'city' => 'London', + 'state' => 'Greater London', + 'zip' => 'EC2A 1PX', + 'countryCode' => 'GB', + ]; + $factory->payment($factory->newUserWithAddress($address), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create(); + + \expect(\getRequestPayload(1))->toMatchArray([ + 'user' => [ + 'id' => null, + 'name' => 'Alice', + 'phone' => '+447837485713', + 'email' => 'alice@truelayer.com', + 'address' => [ + 'address_line1' => 'The Gilbert', + 'address_line2' => 'City of', + 'city' => 'London', + 'state' => 'Greater London', + 'zip' => 'EC2A 1PX', + 'country_code' => 'GB', + ], + 'date_of_birth' => null, + ], + ]); +}); + +\it('ensures user address state is optional', function () { + $factory = CreatePayment::responses([ + PaymentResponse::created(), + ]); + $address = [ + 'addressLine1' => 'The Gilbert', + 'addressLine2' => 'City of', + 'city' => 'London', + 'zip' => 'EC2A 1PX', + 'countryCode' => 'GB', + ]; + $factory->payment($factory->newUserWithAddress($address), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create(); + + \expect(\getRequestPayload(1))->toMatchArray([ + 'user' => [ + 'id' => null, + 'name' => 'Alice', + 'phone' => '+447837485713', + 'email' => 'alice@truelayer.com', + 'address' => [ + 'address_line1' => 'The Gilbert', + 'address_line2' => 'City of', + 'city' => 'London', + 'state' => null, + 'zip' => 'EC2A 1PX', + 'country_code' => 'GB', + ], + 'date_of_birth' => null, + ], + ]); +}); + +\it('ensures user address addressLine2 is optional', function () { + $factory = CreatePayment::responses([ + PaymentResponse::created(), + ]); + $address = [ + 'addressLine1' => 'The Gilbert', + 'city' => 'London', + 'state' => 'Greater London', + 'zip' => 'EC2A 1PX', + 'countryCode' => 'GB', + ]; + $factory->payment($factory->newUserWithAddress($address), $factory->bankTransferMethod($factory->sortCodeBeneficiary()))->create(); \expect(\getRequestPayload(1))->toMatchArray([ 'user' => [ @@ -100,7 +173,7 @@ 'address_line1' => 'The Gilbert', 'address_line2' => null, 'city' => 'London', - 'state' => 'London', + 'state' => 'Greater London', 'zip' => 'EC2A 1PX', 'country_code' => 'GB', ], From ada24550b2cb97345291d380fdd723585270896b Mon Sep 17 00:00:00 2001 From: Stefan Adrian Danaita <me@dsa.io> Date: Fri, 9 Feb 2024 16:05:10 +0000 Subject: [PATCH 10/11] [PHP-70] Update readme for user address and dob support --- README.md | 16 +++++++++++++++- tests/integration/Mocks/CreatePayment.php | 6 +++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4edd4993..984fbc49 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,21 @@ $beneficiary = $client->beneficiary()->externalAccount() $user = $client->user() ->name('Jane Doe') ->phone('+44123456789') - ->email('jane.doe@truelayer.com'); + ->email('jane.doe@truelayer.com') + ->dateOfBirth('2024-01-01'); +``` + +You are also able to set the user's address: + +```php +$address = $client->user() + ->address() + ->addressLine1('The Gilbert') + ->addressLine2('City of') + ->city('London') + ->state('London') + ->zip('EC2A 1PX') + ->countryCode('GB'); ``` <a name="creating-a-payment-method"></a> diff --git a/tests/integration/Mocks/CreatePayment.php b/tests/integration/Mocks/CreatePayment.php index 6e53a2a2..20e6b7a1 100644 --- a/tests/integration/Mocks/CreatePayment.php +++ b/tests/integration/Mocks/CreatePayment.php @@ -66,12 +66,12 @@ public function newUserWithAddress($a): UserInterface ->zip($a['zip']) ->countryCode($a['countryCode']); - if (array_key_exists('addressLine2', $a)) { + if (\array_key_exists('addressLine2', $a)) { $address->addressLine2($a['addressLine2']); } - if (array_key_exists('state', $a)) { - $address->state($a['state']); + if (\array_key_exists('state', $a)) { + $address->state($a['state']); } return $user; From 8c1d9f2e5ed0ed15ca8412a67f00bfa65526d150 Mon Sep 17 00:00:00 2001 From: Stefan Adrian Danaita <me@dsa.io> Date: Fri, 9 Feb 2024 16:07:46 +0000 Subject: [PATCH 11/11] [PHP-70] Update changelog with the new version changes --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eba4e317..dedeb0ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.6.0] - 2024-02-09 + +### Added + +- Support for setting the user's address using `$client->user()->address()` +- Support for setting the user's date of birth using `$client->user()->dateOfBirth()` + ## [1.5.0] - 2024-01-12 ### Added