From 3f84c7bdc7b4875a29b26b083f1af5d873aee0a9 Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Wed, 5 Nov 2025 12:01:59 +0100 Subject: [PATCH 01/19] implement AffiliateSeller API endpoints --- .../IndexAffiliateSellersTest.php | 103 ++++++++++ .../Mock/AffiliateSellerIndexMockClient.php | 33 ++++ .../Mock/AffiliateSellerShowMockClient.php | 111 +++++++++++ .../ShowAffiliateSellerTest.php | 172 ++++++++++++++++ src/Director/BodyTo/BodyToAffiliateSeller.php | 51 +++++ .../BodyTo/BodyToSellerPayoutOptions.php | 17 ++ src/Director/BodyTo/BodyToSellerProfile.php | 18 ++ .../BodyTo/BodyToSellerStatistics.php | 17 ++ src/Entity/AffiliateSeller.php | 183 ++++++++++++++++++ src/Entity/AffiliateSellerInternal.php | 59 ++++++ src/Entity/SellerPayoutOptions.php | 14 ++ src/Entity/SellerPayoutOptionsInternal.php | 10 + src/Entity/SellerProfile.php | 19 ++ src/Entity/SellerProfileInternal.php | 15 ++ src/Entity/SellerStatistics.php | 14 ++ src/Entity/SellerStatisticsInternal.php | 10 + src/Enum/AffiliateSellerIncludes.php | 14 ++ src/Enum/SellerSortType.php | 17 ++ src/Enum/SellerStatus.php | 12 ++ src/Filters/AffiliateSellerFilter.php | 76 ++++++++ src/Service/AffiliateSellerService.php | 61 ++++++ 21 files changed, 1026 insertions(+) create mode 100644 Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php create mode 100644 Tests/Feature/AffiliateSeller/Mock/AffiliateSellerIndexMockClient.php create mode 100644 Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php create mode 100644 Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php create mode 100644 src/Director/BodyTo/BodyToAffiliateSeller.php create mode 100644 src/Director/BodyTo/BodyToSellerPayoutOptions.php create mode 100644 src/Director/BodyTo/BodyToSellerProfile.php create mode 100644 src/Director/BodyTo/BodyToSellerStatistics.php create mode 100644 src/Entity/AffiliateSeller.php create mode 100644 src/Entity/AffiliateSellerInternal.php create mode 100644 src/Entity/SellerPayoutOptions.php create mode 100644 src/Entity/SellerPayoutOptionsInternal.php create mode 100644 src/Entity/SellerProfile.php create mode 100644 src/Entity/SellerProfileInternal.php create mode 100644 src/Entity/SellerStatistics.php create mode 100644 src/Entity/SellerStatisticsInternal.php create mode 100644 src/Enum/AffiliateSellerIncludes.php create mode 100644 src/Enum/SellerSortType.php create mode 100644 src/Enum/SellerStatus.php create mode 100644 src/Filters/AffiliateSellerFilter.php create mode 100644 src/Service/AffiliateSellerService.php diff --git a/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php b/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php new file mode 100644 index 0000000..58d66df --- /dev/null +++ b/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php @@ -0,0 +1,103 @@ +include(AffiliateSellerIncludes::PROFILE)->get(); + + static::assertSame(1, $sellers[0]->id()); + static::assertSame('/v2/affiliates/sellers?include=profile', $client->path()); + } + + /** + * @dataProvider sellerFilterDataProvider + * @test + */ + public function index_sellers_with_filter(string $method, mixed $value, string $queryKey, string $queryValue): void + { + $client = (new AffiliateSellerIndexMockClient()); + $service = new AffiliateSellerService($client); + + $filter = (new AffiliateSellerFilter())->$method($value); + $service->get($filter); + + static::assertSame("/v2/affiliates/sellers?$queryKey=$queryValue", $client->path()); + } + + /** + * Data provider for index_sellers_with_filter. + */ + public static function sellerFilterDataProvider(): array + { + return [ + [ + 'method' => 'limit', + 'value' => 10, + 'queryKey' => 'limit', + 'queryValue' => '10', + ], + [ + 'method' => 'sort', + 'value' => SellerSortType::NAME, + 'queryKey' => 'sort', + 'queryValue' => 'name', + ], + [ + 'method' => 'direction', + 'value' => Direction::DESC, + 'queryKey' => 'direction', + 'queryValue' => 'desc', + ], + [ + 'method' => 'query', + 'value' => 'John', + 'queryKey' => 'q', + 'queryValue' => 'John', + ], + [ + 'method' => 'status', + 'value' => SellerStatus::ACCEPTED, + 'queryKey' => 'status', + 'queryValue' => 'accepted', + ], + [ + 'method' => 'eligibleForPayout', + 'value' => true, + 'queryKey' => 'eligible_for_payout', + 'queryValue' => '1', + ], + [ + 'method' => 'since', + 'value' => new DateTime('2023-01-01'), + 'queryKey' => 'since', + 'queryValue' => '2023-01-01', + ], + [ + 'method' => 'until', + 'value' => new DateTime('2023-12-31'), + 'queryKey' => 'until', + 'queryValue' => '2023-12-31', + ], + ]; + } +} \ No newline at end of file diff --git a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerIndexMockClient.php b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerIndexMockClient.php new file mode 100644 index 0000000..0a714c0 --- /dev/null +++ b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerIndexMockClient.php @@ -0,0 +1,33 @@ +responseBody[] = $sellerData + AffiliateSellerShowMockClient::BASIC_SELLER; + } + } + + public function get(string $path): Response + { + $this->path = $path; + + return new Response(Response::HTTP_OK, ['data' => $this->responseBody]); + } + + public function path(): string + { + return $this->path; + } +} \ No newline at end of file diff --git a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php new file mode 100644 index 0000000..9b1fcdf --- /dev/null +++ b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php @@ -0,0 +1,111 @@ + 1, + 'name' => 'John Doe', + 'email' => 'john@example.com', + 'decline_reason' => '', + 'profile_id' => 1, + 'status' => 'accepted', + 'payout_methods' => null, + ]; + protected string $path; + + /** @noinspection PhpMissingParentConstructorInspection */ + public function __construct(array $data = []) + { + $this->responseBody = ['data' => $data + self::BASIC_SELLER]; + } + + public function address(array $data = []): self + { + $this->responseBody['data']['address'] = $data + [ + 'city' => 'New York', + 'country' => 'US', + 'street' => 'Main St', + 'housenumber' => '123', + 'zipcode' => '10001', + ]; + + return $this; + } + + public function contact(array $data = []): self + { + $this->responseBody['data']['contact'] = $data + [ + 'company' => 'ACME Corp', + 'email' => 'john@example.com', + 'firstname' => 'John', + 'lastname' => 'Doe', + 'telephone' => '+1234567890', + 'website' => 'https://example.com', + 'vat_id_number' => 'US123456789', + 'tax_exempt' => 'none', + ]; + + return $this; + } + + /** + * @throws NotFoundException + * @throws ValidationException + * @throws JsonException + */ + public function get(string $path): Response + { + $this->path = $path; + $response = new Response(Response::HTTP_OK, $this->responseBody); + + $exception = ExceptionFactory::create($response->status(), json_encode($response->body(), JSON_THROW_ON_ERROR)); + if ($exception) { + throw $exception; + } + + return $response; + } + + public function path(): string + { + return $this->path; + } + + public function profile(array $data = []): self + { + $this->responseBody['data']['profile'] = $data + [ + 'id' => 1, + ]; + + return $this; + } + + public function statistics(array $data = []): self + { + $this->responseBody['data']['statistics'] = $data + [ + // Statistics data would go here + ]; + + return $this; + } + + public function payoutOptions(array $data = []): self + { + $this->responseBody['data']['payout_options'] = $data + [ + // Payout options data would go here + ]; + + return $this; + } +} \ No newline at end of file diff --git a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php new file mode 100644 index 0000000..59392f6 --- /dev/null +++ b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php @@ -0,0 +1,172 @@ + 1]); + $service = new AffiliateSellerService($client); + + $seller = $service->find(1); + + static::assertSame(1, $seller->id()); + static::assertSame('John Doe', $seller->name()); + static::assertSame('john@example.com', $seller->email()); + static::assertSame('', $seller->declineReason()); + static::assertSame(1, $seller->profileId()); + static::assertSame(SellerStatus::ACCEPTED, $seller->status()); + static::assertSame('/v2/affiliates/sellers/1', $client->path()); + } + + /** + * @test + * @dataProvider relationsProvider + */ + public function show_none_loaded_relationships(string $relation): void + { + $exception = null; + + try { + (new \PlugAndPay\Sdk\Entity\AffiliateSeller(false))->{$relation}(); + } catch (\PlugAndPay\Sdk\Exception\RelationNotLoadedException $exception) { + } + + static::assertInstanceOf(\PlugAndPay\Sdk\Exception\RelationNotLoadedException::class, $exception); + } + + /** + * Data provider for show_none_loaded_relationships. + */ + public static function relationsProvider(): array + { + return [ + 'address' => ['address'], + 'contact' => ['contact'], + 'profile' => ['profile'], + 'statistics' => ['statistics'], + 'payoutOptions' => ['payoutOptions'], + ]; + } + + /** @test */ + public function show_not_existing_seller(): void + { + $client = new ClientMock(Response::HTTP_NOT_FOUND); + $service = new AffiliateSellerService($client); + $exception = null; + + try { + $service->find(999); + } catch (NotFoundException $exception) { + } + + static::assertEquals('Not found', $exception->getMessage()); + } + + /** @test */ + public function show_unauthorized_seller(): void + { + $client = new ClientMock(Response::HTTP_UNAUTHORIZED); + $service = new AffiliateSellerService($client); + $exception = null; + + try { + $service->find(999); + } catch (UnauthenticatedException $exception) { + } + + static::assertEquals('Unable to connect with Plug&Pay. Request is unauthenticated.', $exception->getMessage()); + } + + /** @test */ + public function show_seller_with_address(): void + { + $client = (new AffiliateSellerShowMockClient())->address(); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::ADDRESS)->find(1); + + $address = $seller->address(); + static::assertSame('New York', $address->city()); + static::assertSame(CountryCode::US, $address->country()); + static::assertSame('Main St', $address->street()); + static::assertSame('123', $address->houseNumber()); + static::assertSame('10001', $address->zipcode()); + static::assertSame('/v2/affiliates/sellers/1?include=address', $client->path()); + } + + /** @test */ + public function show_seller_with_contact(): void + { + $client = (new AffiliateSellerShowMockClient())->contact(); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::CONTACT)->find(1); + + $contact = $seller->contact(); + static::assertSame('ACME Corp', $contact->company()); + static::assertSame('john@example.com', $contact->email()); + static::assertSame('John', $contact->firstName()); + static::assertSame('Doe', $contact->lastName()); + static::assertSame('+1234567890', $contact->telephone()); + static::assertSame('https://example.com', $contact->website()); + static::assertSame('/v2/affiliates/sellers/1?include=contact', $client->path()); + } + + /** @test */ + public function show_seller_with_profile(): void + { + $client = (new AffiliateSellerShowMockClient())->profile(); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::PROFILE)->find(1); + + $profile = $seller->profile(); + static::assertSame(1, $profile->id()); + static::assertSame('/v2/affiliates/sellers/1?include=profile', $client->path()); + } + + /** @test */ + public function show_seller_with_statistics(): void + { + $client = (new AffiliateSellerShowMockClient())->statistics(); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::STATISTICS)->find(1); + + $statistics = $seller->statistics(); + static::assertInstanceOf(\PlugAndPay\Sdk\Entity\SellerStatistics::class, $statistics); + static::assertSame('/v2/affiliates/sellers/1?include=statistics', $client->path()); + } + + /** @test */ + public function show_seller_with_payout_options(): void + { + $client = (new AffiliateSellerShowMockClient())->payoutOptions(); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::PAYOUT_OPTIONS)->find(1); + + $payoutOptions = $seller->payoutOptions(); + static::assertInstanceOf(\PlugAndPay\Sdk\Entity\SellerPayoutOptions::class, $payoutOptions); + static::assertSame('/v2/affiliates/sellers/1?include=payout_options', $client->path()); + } +} \ No newline at end of file diff --git a/src/Director/BodyTo/BodyToAffiliateSeller.php b/src/Director/BodyTo/BodyToAffiliateSeller.php new file mode 100644 index 0000000..9557020 --- /dev/null +++ b/src/Director/BodyTo/BodyToAffiliateSeller.php @@ -0,0 +1,51 @@ +setId($data['id']) + ->setName($data['name']) + ->setEmail($data['email']) + ->setDeclineReason($data['decline_reason']) + ->setProfileId($data['profile_id']) + ->setStatus(SellerStatus::from($data['status'])) + ->setPayoutMethods($data['payout_methods'] ?? []); + + if (isset($data['address'])) { + $seller->setAddress(BodyToAddress::build($data['address'])); + } + + if (isset($data['contact'])) { + $seller->setContact(BodyToContact::build($data['contact'])); + } + + if (isset($data['profile'])) { + $seller->setProfile(BodyToSellerProfile::build($data['profile'])); + } + + if (isset($data['statistics'])) { + $seller->setStatistics(BodyToSellerStatistics::build($data['statistics'])); + } + + if (isset($data['payout_options'])) { + $seller->setPayoutOptions(BodyToSellerPayoutOptions::build($data['payout_options'])); + } + + return $seller; + } +} \ No newline at end of file diff --git a/src/Director/BodyTo/BodyToSellerPayoutOptions.php b/src/Director/BodyTo/BodyToSellerPayoutOptions.php new file mode 100644 index 0000000..0a8d30b --- /dev/null +++ b/src/Director/BodyTo/BodyToSellerPayoutOptions.php @@ -0,0 +1,17 @@ +setId($data['id']); + } +} \ No newline at end of file diff --git a/src/Director/BodyTo/BodyToSellerStatistics.php b/src/Director/BodyTo/BodyToSellerStatistics.php new file mode 100644 index 0000000..f568b57 --- /dev/null +++ b/src/Director/BodyTo/BodyToSellerStatistics.php @@ -0,0 +1,17 @@ +allowEmptyRelations = $allowEmptyRelations; + } + + /** + * @throws RelationNotLoadedException + */ + public function address(): Address + { + if (!isset($this->address)) { + if ($this->allowEmptyRelations) { + $this->address = new Address($this->allowEmptyRelations); + } else { + throw new RelationNotLoadedException('address'); + } + } + + return $this->address; + } + + public function setAddress(Address $address): self + { + $this->address = $address; + + return $this; + } + + /** + * @throws RelationNotLoadedException + */ + public function contact(): Contact + { + if (!isset($this->contact)) { + if ($this->allowEmptyRelations) { + $this->contact = new Contact($this->allowEmptyRelations); + } else { + throw new RelationNotLoadedException('contact'); + } + } + + return $this->contact; + } + + public function setContact(Contact $contact): self + { + $this->contact = $contact; + + return $this; + } + + public function declineReason(): string + { + return $this->declineReason; + } + + public function email(): string + { + return $this->email; + } + + public function id(): int + { + return $this->id; + } + + public function name(): string + { + return $this->name; + } + + /** + * @throws RelationNotLoadedException + */ + public function profile(): SellerProfile + { + if (!isset($this->profile)) { + if ($this->allowEmptyRelations) { + $this->profile = new SellerProfile($this->allowEmptyRelations); + } else { + throw new RelationNotLoadedException('profile'); + } + } + + return $this->profile; + } + + public function setProfile(SellerProfile $profile): self + { + $this->profile = $profile; + + return $this; + } + + public function profileId(): int + { + return $this->profileId; + } + + /** + * @throws RelationNotLoadedException + */ + public function statistics(): SellerStatistics + { + if (!isset($this->statistics)) { + if ($this->allowEmptyRelations) { + $this->statistics = new SellerStatistics($this->allowEmptyRelations); + } else { + throw new RelationNotLoadedException('statistics'); + } + } + + return $this->statistics; + } + + public function setStatistics(SellerStatistics $statistics): self + { + $this->statistics = $statistics; + + return $this; + } + + public function status(): SellerStatus + { + return $this->status; + } + + /** + * @throws RelationNotLoadedException + */ + public function payoutOptions(): SellerPayoutOptions + { + if (!isset($this->payoutOptions)) { + if ($this->allowEmptyRelations) { + $this->payoutOptions = new SellerPayoutOptions($this->allowEmptyRelations); + } else { + throw new RelationNotLoadedException('payoutOptions'); + } + } + + return $this->payoutOptions; + } + + public function setPayoutOptions(SellerPayoutOptions $payoutOptions): self + { + $this->payoutOptions = $payoutOptions; + + return $this; + } + + public function payoutMethods(): array + { + return $this->payoutMethods; + } +} \ No newline at end of file diff --git a/src/Entity/AffiliateSellerInternal.php b/src/Entity/AffiliateSellerInternal.php new file mode 100644 index 0000000..534f27b --- /dev/null +++ b/src/Entity/AffiliateSellerInternal.php @@ -0,0 +1,59 @@ +declineReason = $declineReason; + + return $this; + } + + public function setEmail(string $email): self + { + $this->email = $email; + + return $this; + } + + public function setId(int $id): self + { + $this->id = $id; + + return $this; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function setProfileId(int $profileId): self + { + $this->profileId = $profileId; + + return $this; + } + + public function setStatus(SellerStatus $status): self + { + $this->status = $status; + + return $this; + } + + public function setPayoutMethods(array $payoutMethods): self + { + $this->payoutMethods = $payoutMethods; + + return $this; + } +} \ No newline at end of file diff --git a/src/Entity/SellerPayoutOptions.php b/src/Entity/SellerPayoutOptions.php new file mode 100644 index 0000000..30e8f9a --- /dev/null +++ b/src/Entity/SellerPayoutOptions.php @@ -0,0 +1,14 @@ +id; + } +} \ No newline at end of file diff --git a/src/Entity/SellerProfileInternal.php b/src/Entity/SellerProfileInternal.php new file mode 100644 index 0000000..851f40a --- /dev/null +++ b/src/Entity/SellerProfileInternal.php @@ -0,0 +1,15 @@ +id = $id; + + return $this; + } +} \ No newline at end of file diff --git a/src/Entity/SellerStatistics.php b/src/Entity/SellerStatistics.php new file mode 100644 index 0000000..6949037 --- /dev/null +++ b/src/Entity/SellerStatistics.php @@ -0,0 +1,14 @@ +parameters['direction'] = $direction->value; + + return $this; + } + + public function eligibleForPayout(bool $value): self + { + $this->parameters['eligible_for_payout'] = $value; + + return $this; + } + + public function limit(int $value): self + { + $this->parameters['limit'] = $value; + + return $this; + } + + public function parameters(): array + { + return $this->parameters; + } + + public function query(string $value): self + { + $this->parameters['q'] = $value; + + return $this; + } + + public function since(DateTimeInterface $value): self + { + $this->parameters['since'] = $value->format('Y-m-d'); + + return $this; + } + + public function sort(SellerSortType $value): self + { + $this->parameters['sort'] = $value->value; + + return $this; + } + + public function status(SellerStatus ...$status): self + { + $this->parameters['status'] = $status; + + return $this; + } + + public function until(DateTimeInterface $value): self + { + $this->parameters['until'] = $value->format('Y-m-d'); + + return $this; + } +} \ No newline at end of file diff --git a/src/Service/AffiliateSellerService.php b/src/Service/AffiliateSellerService.php new file mode 100644 index 0000000..72fc04b --- /dev/null +++ b/src/Service/AffiliateSellerService.php @@ -0,0 +1,61 @@ +client = $client; + } + + public function include(AffiliateSellerIncludes ...$includes): self + { + $this->includes = $includes; + + return $this; + } + + /** + * @throws DecodeResponseException + */ + public function find(int $id): AffiliateSeller + { + $query = Parameters::toString(['include' => $this->includes]); + $response = $this->client->get("/v2/affiliates/sellers/$id$query"); + + return BodyToAffiliateSeller::build($response->body()['data']); + } + + /** + * @return AffiliateSeller[] + * @throws DecodeResponseException + */ + public function get(?AffiliateSellerFilter $filter = null): array + { + $parameters = $filter ? $filter->parameters() : []; + if (!empty($this->includes)) { + $parameters['include'] = $this->includes; + } + $query = Parameters::toString($parameters); + + $response = $this->client->get("/v2/affiliates/sellers$query"); + + return BodyToAffiliateSeller::buildMulti($response->body()['data']); + } +} \ No newline at end of file From 10e2052b4275c17613f5236703f5c5811f331baa Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Wed, 5 Nov 2025 12:13:30 +0100 Subject: [PATCH 02/19] Make setDeclineReason($declineReason) nullables --- .../IndexAffiliateSellersTest.php | 23 +++++++++++++++++++ src/Director/BodyTo/BodyToAffiliateSeller.php | 2 +- src/Entity/AffiliateSeller.php | 4 ++-- src/Entity/AffiliateSellerInternal.php | 2 +- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php b/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php index 58d66df..a936bde 100644 --- a/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php +++ b/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php @@ -44,6 +44,29 @@ public function index_sellers_with_filter(string $method, mixed $value, string $ static::assertSame("/v2/affiliates/sellers?$queryKey=$queryValue", $client->path()); } + /** + * @test + */ + public function index_sellers_with_null_decline_reason(): void + { + $client = (new AffiliateSellerIndexMockClient([ + [ + 'id' => 1, + 'name' => 'John Doe', + 'email' => 'john@example.com', + 'decline_reason' => null, + 'profile_id' => 1, + 'status' => 'accepted', + 'payout_methods' => null, + ] + ])); + $service = new AffiliateSellerService($client); + $sellers = $service->get(); + + static::assertSame(1, $sellers[0]->id()); + static::assertNull($sellers[0]->declineReason()); + } + /** * Data provider for index_sellers_with_filter. */ diff --git a/src/Director/BodyTo/BodyToAffiliateSeller.php b/src/Director/BodyTo/BodyToAffiliateSeller.php index 9557020..c78bd54 100644 --- a/src/Director/BodyTo/BodyToAffiliateSeller.php +++ b/src/Director/BodyTo/BodyToAffiliateSeller.php @@ -21,7 +21,7 @@ public static function build(array $data): AffiliateSeller ->setId($data['id']) ->setName($data['name']) ->setEmail($data['email']) - ->setDeclineReason($data['decline_reason']) + ->setDeclineReason($data['decline_reason'] ?? null) ->setProfileId($data['profile_id']) ->setStatus(SellerStatus::from($data['status'])) ->setPayoutMethods($data['payout_methods'] ?? []); diff --git a/src/Entity/AffiliateSeller.php b/src/Entity/AffiliateSeller.php index 8ae319e..90cb77b 100644 --- a/src/Entity/AffiliateSeller.php +++ b/src/Entity/AffiliateSeller.php @@ -15,7 +15,7 @@ class AffiliateSeller extends AbstractEntity protected bool $allowEmptyRelations; protected Address $address; protected Contact $contact; - protected string $declineReason; + protected ?string $declineReason; protected int $id; protected string $name; protected string $email; @@ -77,7 +77,7 @@ public function setContact(Contact $contact): self return $this; } - public function declineReason(): string + public function declineReason(): ?string { return $this->declineReason; } diff --git a/src/Entity/AffiliateSellerInternal.php b/src/Entity/AffiliateSellerInternal.php index 534f27b..39a3c05 100644 --- a/src/Entity/AffiliateSellerInternal.php +++ b/src/Entity/AffiliateSellerInternal.php @@ -8,7 +8,7 @@ class AffiliateSellerInternal extends AffiliateSeller { - public function setDeclineReason(string $declineReason): self + public function setDeclineReason(?string $declineReason): self { $this->declineReason = $declineReason; From eb61f238eebbe393c6726485334ee237500b24a1 Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Wed, 5 Nov 2025 13:06:36 +0000 Subject: [PATCH 03/19] Apply php-cs-fixer changes --- .../IndexAffiliateSellersTest.php | 4 ++-- .../Mock/AffiliateSellerIndexMockClient.php | 2 +- .../Mock/AffiliateSellerShowMockClient.php | 2 +- .../AffiliateSeller/ShowAffiliateSellerTest.php | 4 ++-- src/Director/BodyTo/BodyToAffiliateSeller.php | 2 +- .../BodyTo/BodyToSellerPayoutOptions.php | 2 +- src/Director/BodyTo/BodyToSellerProfile.php | 2 +- src/Director/BodyTo/BodyToSellerStatistics.php | 2 +- src/Entity/AffiliateSeller.php | 2 +- src/Entity/AffiliateSellerInternal.php | 2 +- src/Entity/SellerPayoutOptions.php | 2 +- src/Entity/SellerPayoutOptionsInternal.php | 2 +- src/Entity/SellerProfile.php | 2 +- src/Entity/SellerProfileInternal.php | 2 +- src/Entity/SellerStatistics.php | 2 +- src/Entity/SellerStatisticsInternal.php | 2 +- src/Enum/AffiliateSellerIncludes.php | 2 +- src/Enum/SellerSortType.php | 16 ++++++++-------- src/Enum/SellerStatus.php | 2 +- src/Filters/AffiliateSellerFilter.php | 2 +- src/Service/AffiliateSellerService.php | 2 +- 21 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php b/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php index a936bde..ff24ed3 100644 --- a/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php +++ b/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php @@ -58,7 +58,7 @@ public function index_sellers_with_null_decline_reason(): void 'profile_id' => 1, 'status' => 'accepted', 'payout_methods' => null, - ] + ], ])); $service = new AffiliateSellerService($client); $sellers = $service->get(); @@ -123,4 +123,4 @@ public static function sellerFilterDataProvider(): array ], ]; } -} \ No newline at end of file +} diff --git a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerIndexMockClient.php b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerIndexMockClient.php index 0a714c0..2cc1fc8 100644 --- a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerIndexMockClient.php +++ b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerIndexMockClient.php @@ -30,4 +30,4 @@ public function path(): string { return $this->path; } -} \ No newline at end of file +} diff --git a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php index 9b1fcdf..8939470 100644 --- a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php +++ b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php @@ -108,4 +108,4 @@ public function payoutOptions(array $data = []): self return $this; } -} \ No newline at end of file +} diff --git a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php index 59392f6..c3a151c 100644 --- a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php +++ b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php @@ -14,8 +14,8 @@ use PlugAndPay\Sdk\Exception\NotFoundException; use PlugAndPay\Sdk\Exception\UnauthenticatedException; use PlugAndPay\Sdk\Service\AffiliateSellerService; -use PlugAndPay\Sdk\Tests\Feature\ClientMock; use PlugAndPay\Sdk\Tests\Feature\AffiliateSeller\Mock\AffiliateSellerShowMockClient; +use PlugAndPay\Sdk\Tests\Feature\ClientMock; class ShowAffiliateSellerTest extends TestCase { @@ -169,4 +169,4 @@ public function show_seller_with_payout_options(): void static::assertInstanceOf(\PlugAndPay\Sdk\Entity\SellerPayoutOptions::class, $payoutOptions); static::assertSame('/v2/affiliates/sellers/1?include=payout_options', $client->path()); } -} \ No newline at end of file +} diff --git a/src/Director/BodyTo/BodyToAffiliateSeller.php b/src/Director/BodyTo/BodyToAffiliateSeller.php index c78bd54..3522eaf 100644 --- a/src/Director/BodyTo/BodyToAffiliateSeller.php +++ b/src/Director/BodyTo/BodyToAffiliateSeller.php @@ -48,4 +48,4 @@ public static function build(array $data): AffiliateSeller return $seller; } -} \ No newline at end of file +} diff --git a/src/Director/BodyTo/BodyToSellerPayoutOptions.php b/src/Director/BodyTo/BodyToSellerPayoutOptions.php index 0a8d30b..24d541a 100644 --- a/src/Director/BodyTo/BodyToSellerPayoutOptions.php +++ b/src/Director/BodyTo/BodyToSellerPayoutOptions.php @@ -14,4 +14,4 @@ public static function build(array $data): SellerPayoutOptions { return new SellerPayoutOptionsInternal(); } -} \ No newline at end of file +} diff --git a/src/Director/BodyTo/BodyToSellerProfile.php b/src/Director/BodyTo/BodyToSellerProfile.php index bc17510..1509c68 100644 --- a/src/Director/BodyTo/BodyToSellerProfile.php +++ b/src/Director/BodyTo/BodyToSellerProfile.php @@ -15,4 +15,4 @@ public static function build(array $data): SellerProfile return (new SellerProfileInternal()) ->setId($data['id']); } -} \ No newline at end of file +} diff --git a/src/Director/BodyTo/BodyToSellerStatistics.php b/src/Director/BodyTo/BodyToSellerStatistics.php index f568b57..4310e8c 100644 --- a/src/Director/BodyTo/BodyToSellerStatistics.php +++ b/src/Director/BodyTo/BodyToSellerStatistics.php @@ -14,4 +14,4 @@ public static function build(array $data): SellerStatistics { return new SellerStatisticsInternal(); } -} \ No newline at end of file +} diff --git a/src/Entity/AffiliateSeller.php b/src/Entity/AffiliateSeller.php index 90cb77b..1336bf5 100644 --- a/src/Entity/AffiliateSeller.php +++ b/src/Entity/AffiliateSeller.php @@ -180,4 +180,4 @@ public function payoutMethods(): array { return $this->payoutMethods; } -} \ No newline at end of file +} diff --git a/src/Entity/AffiliateSellerInternal.php b/src/Entity/AffiliateSellerInternal.php index 39a3c05..0aed705 100644 --- a/src/Entity/AffiliateSellerInternal.php +++ b/src/Entity/AffiliateSellerInternal.php @@ -56,4 +56,4 @@ public function setPayoutMethods(array $payoutMethods): self return $this; } -} \ No newline at end of file +} diff --git a/src/Entity/SellerPayoutOptions.php b/src/Entity/SellerPayoutOptions.php index 30e8f9a..4f5cdfe 100644 --- a/src/Entity/SellerPayoutOptions.php +++ b/src/Entity/SellerPayoutOptions.php @@ -11,4 +11,4 @@ class SellerPayoutOptions extends AbstractEntity use HasDynamicFields; // Based on API spec, payout_options can be null, so we'll use dynamic fields for now -} \ No newline at end of file +} diff --git a/src/Entity/SellerPayoutOptionsInternal.php b/src/Entity/SellerPayoutOptionsInternal.php index 900cb73..ea180c3 100644 --- a/src/Entity/SellerPayoutOptionsInternal.php +++ b/src/Entity/SellerPayoutOptionsInternal.php @@ -7,4 +7,4 @@ class SellerPayoutOptionsInternal extends SellerPayoutOptions { // Internal entity for building from API response -} \ No newline at end of file +} diff --git a/src/Entity/SellerProfile.php b/src/Entity/SellerProfile.php index 08fa798..1ddd47d 100644 --- a/src/Entity/SellerProfile.php +++ b/src/Entity/SellerProfile.php @@ -16,4 +16,4 @@ public function id(): int { return $this->id; } -} \ No newline at end of file +} diff --git a/src/Entity/SellerProfileInternal.php b/src/Entity/SellerProfileInternal.php index 851f40a..c504866 100644 --- a/src/Entity/SellerProfileInternal.php +++ b/src/Entity/SellerProfileInternal.php @@ -12,4 +12,4 @@ public function setId(int $id): self return $this; } -} \ No newline at end of file +} diff --git a/src/Entity/SellerStatistics.php b/src/Entity/SellerStatistics.php index 6949037..e42d8d0 100644 --- a/src/Entity/SellerStatistics.php +++ b/src/Entity/SellerStatistics.php @@ -11,4 +11,4 @@ class SellerStatistics extends AbstractEntity use HasDynamicFields; // Based on API spec, statistics can be null, so we'll use dynamic fields for now -} \ No newline at end of file +} diff --git a/src/Entity/SellerStatisticsInternal.php b/src/Entity/SellerStatisticsInternal.php index 0eb5cf6..0813af6 100644 --- a/src/Entity/SellerStatisticsInternal.php +++ b/src/Entity/SellerStatisticsInternal.php @@ -7,4 +7,4 @@ class SellerStatisticsInternal extends SellerStatistics { // Internal entity for building from API response -} \ No newline at end of file +} diff --git a/src/Enum/AffiliateSellerIncludes.php b/src/Enum/AffiliateSellerIncludes.php index 91aaba2..990aad9 100644 --- a/src/Enum/AffiliateSellerIncludes.php +++ b/src/Enum/AffiliateSellerIncludes.php @@ -11,4 +11,4 @@ enum AffiliateSellerIncludes: string case PROFILE = 'profile'; case STATISTICS = 'statistics'; case PAYOUT_OPTIONS = 'payout_options'; -} \ No newline at end of file +} diff --git a/src/Enum/SellerSortType.php b/src/Enum/SellerSortType.php index e89913b..55c6fec 100644 --- a/src/Enum/SellerSortType.php +++ b/src/Enum/SellerSortType.php @@ -6,12 +6,12 @@ enum SellerSortType: string { - case NAME = 'name'; - case ORDERS = 'orders'; - case SALES = 'sales'; - case PENDING = 'pending'; - case LOCKED = 'locked'; - case PAIDOUT = 'paidout'; - case VALUE = 'value'; + case NAME = 'name'; + case ORDERS = 'orders'; + case SALES = 'sales'; + case PENDING = 'pending'; + case LOCKED = 'locked'; + case PAIDOUT = 'paidout'; + case VALUE = 'value'; case CREATED_AT = 'created_at'; -} \ No newline at end of file +} diff --git a/src/Enum/SellerStatus.php b/src/Enum/SellerStatus.php index 9de0f21..73af3b4 100644 --- a/src/Enum/SellerStatus.php +++ b/src/Enum/SellerStatus.php @@ -9,4 +9,4 @@ enum SellerStatus: string case ACCEPTED = 'accepted'; case PENDING = 'pending'; case DECLINED = 'declined'; -} \ No newline at end of file +} diff --git a/src/Filters/AffiliateSellerFilter.php b/src/Filters/AffiliateSellerFilter.php index 0987d4d..70e9307 100644 --- a/src/Filters/AffiliateSellerFilter.php +++ b/src/Filters/AffiliateSellerFilter.php @@ -73,4 +73,4 @@ public function until(DateTimeInterface $value): self return $this; } -} \ No newline at end of file +} diff --git a/src/Service/AffiliateSellerService.php b/src/Service/AffiliateSellerService.php index 72fc04b..b4059ab 100644 --- a/src/Service/AffiliateSellerService.php +++ b/src/Service/AffiliateSellerService.php @@ -58,4 +58,4 @@ public function get(?AffiliateSellerFilter $filter = null): array return BodyToAffiliateSeller::buildMulti($response->body()['data']); } -} \ No newline at end of file +} From d9171a482f063e0437db17dd6d80f1f243307aef Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Thu, 6 Nov 2025 13:11:29 +0100 Subject: [PATCH 04/19] Fix AffiliateSeller mock: use null instead of empty string for decline_reason - Change 'decline_reason' => '' to 'decline_reason' => null in AffiliateSellerShowMockClient - Improves type consistency with nullable ?string property in AffiliateSeller entity - Aligns with semantic meaning of 'no decline reason' being null rather than empty string --- .../AffiliateSeller/Mock/AffiliateSellerShowMockClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php index 8939470..cdca654 100644 --- a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php +++ b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php @@ -17,7 +17,7 @@ class AffiliateSellerShowMockClient extends ClientMock 'id' => 1, 'name' => 'John Doe', 'email' => 'john@example.com', - 'decline_reason' => '', + 'decline_reason' => null, 'profile_id' => 1, 'status' => 'accepted', 'payout_methods' => null, From ceac64595831091b2307bece250bcf33e9cae4a7 Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Thu, 6 Nov 2025 16:10:13 +0100 Subject: [PATCH 05/19] Add seller statistics --- .../Mock/AffiliateSellerShowMockClient.php | 12 +- .../ShowAffiliateSellerTest.php | 115 ++++++++++++++++++ .../BodyTo/BodyToSellerStatistics.php | 11 +- src/Entity/SellerStatistics.php | 109 ++++++++++++++++- 4 files changed, 243 insertions(+), 4 deletions(-) diff --git a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php index cdca654..f6ae0eb 100644 --- a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php +++ b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php @@ -17,7 +17,7 @@ class AffiliateSellerShowMockClient extends ClientMock 'id' => 1, 'name' => 'John Doe', 'email' => 'john@example.com', - 'decline_reason' => null, + 'decline_reason' => '', 'profile_id' => 1, 'status' => 'accepted', 'payout_methods' => null, @@ -94,7 +94,15 @@ public function profile(array $data = []): self public function statistics(array $data = []): self { $this->responseBody['data']['statistics'] = $data + [ - // Statistics data would go here + 'clicks' => null, + 'commission' => 0, + 'locked' => 0, + 'orders' => 0, + 'paidout' => 0, + 'pending' => 0, + 'recurring' => 0, + 'sales' => 0, + 'value' => 0, ]; return $this; diff --git a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php index c3a151c..8204a6e 100644 --- a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php +++ b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php @@ -169,4 +169,119 @@ public function show_seller_with_payout_options(): void static::assertInstanceOf(\PlugAndPay\Sdk\Entity\SellerPayoutOptions::class, $payoutOptions); static::assertSame('/v2/affiliates/sellers/1?include=payout_options', $client->path()); } + + /** @test */ + public function show_seller_statistics_with_values(): void + { + $client = (new AffiliateSellerShowMockClient())->statistics([ + 'clicks' => 150, + 'commission' => 2500, + 'locked' => 750, + 'orders' => 42, + 'paidout' => 10000, + 'pending' => 1250, + 'recurring' => 3500, + 'sales' => 55, + 'value' => 15000, + ]); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::STATISTICS)->find(1); + + $statistics = $seller->statistics(); + static::assertSame(150, $statistics->clicks()); + static::assertSame(2500, $statistics->commission()); + static::assertSame(750, $statistics->locked()); + static::assertSame(42, $statistics->orders()); + static::assertSame(10000, $statistics->paidout()); + static::assertSame(1250, $statistics->pending()); + static::assertSame(3500, $statistics->recurring()); + static::assertSame(55, $statistics->sales()); + static::assertSame(15000, $statistics->value()); + } + + /** @test */ + public function show_seller_statistics_with_null_clicks(): void + { + $client = (new AffiliateSellerShowMockClient())->statistics([ + 'clicks' => null, + ]); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::STATISTICS)->find(1); + + $statistics = $seller->statistics(); + static::assertNull($statistics->clicks()); + } + + /** @test */ + public function show_seller_profile_with_custom_id(): void + { + $client = (new AffiliateSellerShowMockClient())->profile([ + 'id' => 42, + ]); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::PROFILE)->find(1); + + $profile = $seller->profile(); + static::assertSame(42, $profile->id()); + } + + /** @test */ + public function test_seller_statistics_isset_for_existing_property(): void + { + $client = (new AffiliateSellerShowMockClient())->statistics([ + 'clicks' => 100, + 'commission' => 500, + 'locked' => 200, + 'orders' => 10, + 'paidout' => 1000, + 'pending' => 300, + 'recurring' => 400, + 'sales' => 15, + 'value' => 2000, + ]); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::STATISTICS)->find(1); + + $statistics = $seller->statistics(); + static::assertTrue($statistics->isset('clicks')); + static::assertTrue($statistics->isset('commission')); + static::assertTrue($statistics->isset('locked')); + static::assertTrue($statistics->isset('orders')); + static::assertTrue($statistics->isset('paidout')); + static::assertTrue($statistics->isset('pending')); + static::assertTrue($statistics->isset('recurring')); + static::assertTrue($statistics->isset('sales')); + static::assertTrue($statistics->isset('value')); + } + + /** @test */ + public function test_seller_payout_options_isset(): void + { + $client = (new AffiliateSellerShowMockClient())->payoutOptions(); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::PAYOUT_OPTIONS)->find(1); + + $payoutOptions = $seller->payoutOptions(); + static::assertInstanceOf(\PlugAndPay\Sdk\Entity\SellerPayoutOptions::class, $payoutOptions); + } + + /** @test */ + public function test_isset_with_non_existent_field_throws_exception(): void + { + $client = (new AffiliateSellerShowMockClient())->statistics(); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::STATISTICS)->find(1); + + $statistics = $seller->statistics(); + + $this->expectException(\BadFunctionCallException::class); + $this->expectExceptionMessage("Field 'nonExistentField' does not exists"); + $statistics->isset('nonExistentField'); + } } diff --git a/src/Director/BodyTo/BodyToSellerStatistics.php b/src/Director/BodyTo/BodyToSellerStatistics.php index 4310e8c..63d7ffe 100644 --- a/src/Director/BodyTo/BodyToSellerStatistics.php +++ b/src/Director/BodyTo/BodyToSellerStatistics.php @@ -12,6 +12,15 @@ class BodyToSellerStatistics implements BuildObjectInterface { public static function build(array $data): SellerStatistics { - return new SellerStatisticsInternal(); + return (new SellerStatisticsInternal()) + ->setClicks($data['clicks'] ?? null) + ->setCommission($data['commission'] ?? 0) + ->setLocked($data['locked'] ?? 0) + ->setOrders($data['orders'] ?? 0) + ->setPaidout($data['paidout'] ?? 0) + ->setPending($data['pending'] ?? 0) + ->setRecurring($data['recurring'] ?? 0) + ->setSales($data['sales'] ?? 0) + ->setValue($data['value'] ?? 0); } } diff --git a/src/Entity/SellerStatistics.php b/src/Entity/SellerStatistics.php index e42d8d0..eae12a6 100644 --- a/src/Entity/SellerStatistics.php +++ b/src/Entity/SellerStatistics.php @@ -10,5 +10,112 @@ class SellerStatistics extends AbstractEntity { use HasDynamicFields; - // Based on API spec, statistics can be null, so we'll use dynamic fields for now + protected ?int $clicks; + protected int $commission; + protected int $locked; + protected int $orders; + protected int $paidout; + protected int $pending; + protected int $recurring; + protected int $sales; + protected int $value; + + public function clicks(): ?int + { + return $this->clicks; + } + + public function setClicks(?int $clicks): self + { + $this->clicks = $clicks; + return $this; + } + + public function commission(): int + { + return $this->commission; + } + + public function setCommission(int $commission): self + { + $this->commission = $commission; + return $this; + } + + public function locked(): int + { + return $this->locked; + } + + public function setLocked(int $locked): self + { + $this->locked = $locked; + return $this; + } + + public function orders(): int + { + return $this->orders; + } + + public function setOrders(int $orders): self + { + $this->orders = $orders; + return $this; + } + + public function paidout(): int + { + return $this->paidout; + } + + public function setPaidout(int $paidout): self + { + $this->paidout = $paidout; + return $this; + } + + public function pending(): int + { + return $this->pending; + } + + public function setPending(int $pending): self + { + $this->pending = $pending; + return $this; + } + + public function recurring(): int + { + return $this->recurring; + } + + public function setRecurring(int $recurring): self + { + $this->recurring = $recurring; + return $this; + } + + public function sales(): int + { + return $this->sales; + } + + public function setSales(int $sales): self + { + $this->sales = $sales; + return $this; + } + + public function value(): int + { + return $this->value; + } + + public function setValue(int $value): self + { + $this->value = $value; + return $this; + } } From f82e7e2760aa8645bad17c327b6dcd97d2576e95 Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Thu, 6 Nov 2025 16:38:45 +0100 Subject: [PATCH 06/19] Add seller payout options --- .../Mock/AffiliateSellerShowMockClient.php | 7 +- .../ShowAffiliateSellerTest.php | 67 +++++++++++++++++++ .../BodyTo/BodyToSellerPayoutOptions.php | 4 +- src/Entity/SellerPayoutOptions.php | 25 ++++++- 4 files changed, 100 insertions(+), 3 deletions(-) diff --git a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php index f6ae0eb..72e991b 100644 --- a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php +++ b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php @@ -111,7 +111,12 @@ public function statistics(array $data = []): self public function payoutOptions(array $data = []): self { $this->responseBody['data']['payout_options'] = $data + [ - // Payout options data would go here + 'method' => 'paypal', + 'settings' => [ + 'email' => 'oramcharan@example.com', + 'phone' => '+3177 4223366', + 'paypal_me_link' => 'https://paypal.me/lopes.jennifer', + ], ]; return $this; diff --git a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php index 8204a6e..9878cd8 100644 --- a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php +++ b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php @@ -270,6 +270,73 @@ public function test_seller_payout_options_isset(): void static::assertInstanceOf(\PlugAndPay\Sdk\Entity\SellerPayoutOptions::class, $payoutOptions); } + /** @test */ + public function show_seller_payout_options_with_default_data(): void + { + $client = (new AffiliateSellerShowMockClient())->payoutOptions(); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::PAYOUT_OPTIONS)->find(1); + + $payoutOptions = $seller->payoutOptions(); + static::assertSame('paypal', $payoutOptions->method()); + + $settings = $payoutOptions->settings(); + static::assertIsArray($settings); + static::assertSame('oramcharan@example.com', $settings['email']); + static::assertSame('+3177 4223366', $settings['phone']); + static::assertSame('https://paypal.me/lopes.jennifer', $settings['paypal_me_link']); + } + + /** @test */ + public function show_seller_payout_options_with_custom_method(): void + { + $client = (new AffiliateSellerShowMockClient())->payoutOptions([ + 'method' => 'bank_transfer', + ]); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::PAYOUT_OPTIONS)->find(1); + + $payoutOptions = $seller->payoutOptions(); + static::assertSame('bank_transfer', $payoutOptions->method()); + } + + /** @test */ + public function show_seller_payout_options_with_custom_settings(): void + { + $client = (new AffiliateSellerShowMockClient())->payoutOptions([ + 'method' => 'paypal', + 'settings' => [ + 'email' => 'custom@example.com', + 'phone' => '+31612345678', + 'paypal_me_link' => 'https://paypal.me/custom.user', + ], + ]); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::PAYOUT_OPTIONS)->find(1); + + $payoutOptions = $seller->payoutOptions(); + $settings = $payoutOptions->settings(); + static::assertSame('custom@example.com', $settings['email']); + static::assertSame('+31612345678', $settings['phone']); + static::assertSame('https://paypal.me/custom.user', $settings['paypal_me_link']); + } + + /** @test */ + public function show_seller_payout_options_settings_fields(): void + { + $client = (new AffiliateSellerShowMockClient())->payoutOptions(); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::PAYOUT_OPTIONS)->find(1); + + $payoutOptions = $seller->payoutOptions(); + static::assertTrue($payoutOptions->isset('method')); + static::assertTrue($payoutOptions->isset('settings')); + } + /** @test */ public function test_isset_with_non_existent_field_throws_exception(): void { diff --git a/src/Director/BodyTo/BodyToSellerPayoutOptions.php b/src/Director/BodyTo/BodyToSellerPayoutOptions.php index 24d541a..281a384 100644 --- a/src/Director/BodyTo/BodyToSellerPayoutOptions.php +++ b/src/Director/BodyTo/BodyToSellerPayoutOptions.php @@ -12,6 +12,8 @@ class BodyToSellerPayoutOptions implements BuildObjectInterface { public static function build(array $data): SellerPayoutOptions { - return new SellerPayoutOptionsInternal(); + return (new SellerPayoutOptionsInternal()) + ->setMethod($data['method'] ?? null) + ->setSettings($data['settings'] ?? null); } } diff --git a/src/Entity/SellerPayoutOptions.php b/src/Entity/SellerPayoutOptions.php index 4f5cdfe..b4afe3c 100644 --- a/src/Entity/SellerPayoutOptions.php +++ b/src/Entity/SellerPayoutOptions.php @@ -10,5 +10,28 @@ class SellerPayoutOptions extends AbstractEntity { use HasDynamicFields; - // Based on API spec, payout_options can be null, so we'll use dynamic fields for now + protected ?string $method; + protected ?array $settings; + + public function method(): ?string + { + return $this->method; + } + + public function setMethod(?string $method): self + { + $this->method = $method; + return $this; + } + + public function settings(): ?array + { + return $this->settings; + } + + public function setSettings(?array $settings): self + { + $this->settings = $settings; + return $this; + } } From 1adf3203c214244bd9958509273e441a255af19b Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Thu, 6 Nov 2025 17:15:51 +0100 Subject: [PATCH 07/19] Add seller payout methods --- .../Mock/AffiliateSellerShowMockClient.php | 21 ++++ .../ShowAffiliateSellerTest.php | 118 ++++++++++++++++-- src/Director/BodyTo/BodyToAffiliateSeller.php | 9 +- src/Director/BodyTo/BodyToPayoutMethod.php | 27 ++++ src/Entity/AffiliateSeller.php | 14 +++ src/Entity/PayoutMethod.php | 41 ++++++ src/Entity/PayoutMethodInternal.php | 63 ++++++++++ 7 files changed, 280 insertions(+), 13 deletions(-) create mode 100644 src/Director/BodyTo/BodyToPayoutMethod.php create mode 100644 src/Entity/PayoutMethod.php create mode 100644 src/Entity/PayoutMethodInternal.php diff --git a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php index 72e991b..4614e2f 100644 --- a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php +++ b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php @@ -119,6 +119,27 @@ public function payoutOptions(array $data = []): self ], ]; + return $this; + } + public function payoutMethods(?array $data = null): self + { + if ($data === null) { + $data = [ + [ + 'id' => 1, + 'method' => 'bank_transfer', + 'settings' => [ + 'iban' => 'NL91ABNA0417164300', + 'bic' => 'ABNANL2A', + ], + 'created_at' => '2024-01-01T00:00:00.000000Z', + 'updated_at' => '2024-01-01T00:00:00.000000Z', + ], + ]; + } + + $this->responseBody['data']['payout_methods'] = $data; + return $this; } } diff --git a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php index 9878cd8..decc96c 100644 --- a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php +++ b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php @@ -6,12 +6,17 @@ namespace PlugAndPay\Sdk\Tests\Feature\AffiliateSeller; +use BadFunctionCallException; use PHPUnit\Framework\TestCase; +use PlugAndPay\Sdk\Entity\AffiliateSeller; use PlugAndPay\Sdk\Entity\Response; +use PlugAndPay\Sdk\Entity\SellerPayoutOptions; +use PlugAndPay\Sdk\Entity\SellerStatistics; use PlugAndPay\Sdk\Enum\AffiliateSellerIncludes; use PlugAndPay\Sdk\Enum\CountryCode; use PlugAndPay\Sdk\Enum\SellerStatus; use PlugAndPay\Sdk\Exception\NotFoundException; +use PlugAndPay\Sdk\Exception\RelationNotLoadedException; use PlugAndPay\Sdk\Exception\UnauthenticatedException; use PlugAndPay\Sdk\Service\AffiliateSellerService; use PlugAndPay\Sdk\Tests\Feature\AffiliateSeller\Mock\AffiliateSellerShowMockClient; @@ -45,11 +50,11 @@ public function show_none_loaded_relationships(string $relation): void $exception = null; try { - (new \PlugAndPay\Sdk\Entity\AffiliateSeller(false))->{$relation}(); - } catch (\PlugAndPay\Sdk\Exception\RelationNotLoadedException $exception) { + (new AffiliateSeller(false))->{$relation}(); + } catch (RelationNotLoadedException $exception) { } - static::assertInstanceOf(\PlugAndPay\Sdk\Exception\RelationNotLoadedException::class, $exception); + static::assertInstanceOf(RelationNotLoadedException::class, $exception); } /** @@ -63,6 +68,7 @@ public static function relationsProvider(): array 'profile' => ['profile'], 'statistics' => ['statistics'], 'payoutOptions' => ['payoutOptions'], + 'payoutMethods' => ['payoutMethods'], ]; } @@ -153,7 +159,7 @@ public function show_seller_with_statistics(): void $seller = $service->include(AffiliateSellerIncludes::STATISTICS)->find(1); $statistics = $seller->statistics(); - static::assertInstanceOf(\PlugAndPay\Sdk\Entity\SellerStatistics::class, $statistics); + static::assertInstanceOf(SellerStatistics::class, $statistics); static::assertSame('/v2/affiliates/sellers/1?include=statistics', $client->path()); } @@ -166,7 +172,7 @@ public function show_seller_with_payout_options(): void $seller = $service->include(AffiliateSellerIncludes::PAYOUT_OPTIONS)->find(1); $payoutOptions = $seller->payoutOptions(); - static::assertInstanceOf(\PlugAndPay\Sdk\Entity\SellerPayoutOptions::class, $payoutOptions); + static::assertInstanceOf(SellerPayoutOptions::class, $payoutOptions); static::assertSame('/v2/affiliates/sellers/1?include=payout_options', $client->path()); } @@ -267,7 +273,7 @@ public function test_seller_payout_options_isset(): void $seller = $service->include(AffiliateSellerIncludes::PAYOUT_OPTIONS)->find(1); $payoutOptions = $seller->payoutOptions(); - static::assertInstanceOf(\PlugAndPay\Sdk\Entity\SellerPayoutOptions::class, $payoutOptions); + static::assertInstanceOf(SellerPayoutOptions::class, $payoutOptions); } /** @test */ @@ -280,7 +286,7 @@ public function show_seller_payout_options_with_default_data(): void $payoutOptions = $seller->payoutOptions(); static::assertSame('paypal', $payoutOptions->method()); - + $settings = $payoutOptions->settings(); static::assertIsArray($settings); static::assertSame('oramcharan@example.com', $settings['email']); @@ -345,10 +351,102 @@ public function test_isset_with_non_existent_field_throws_exception(): void $seller = $service->include(AffiliateSellerIncludes::STATISTICS)->find(1); - $statistics = $seller->statistics(); - - $this->expectException(\BadFunctionCallException::class); + $this->expectException(BadFunctionCallException::class); $this->expectExceptionMessage("Field 'nonExistentField' does not exists"); + $statistics = $seller->statistics(); $statistics->isset('nonExistentField'); } + + /** @test */ + public function show_seller_with_payout_methods(): void + { + $client = (new AffiliateSellerShowMockClient())->payoutMethods(); + $service = new AffiliateSellerService($client); + + $seller = $service->find(1); + + $payoutMethods = $seller->payoutMethods(); + static::assertIsArray($payoutMethods); + static::assertCount(1, $payoutMethods); + static::assertSame(1, $payoutMethods[0]->id()); + static::assertSame('bank_transfer', $payoutMethods[0]->method()); + static::assertIsArray($payoutMethods[0]->settings()); + static::assertSame('NL91ABNA0417164300', $payoutMethods[0]->settings()['iban']); + static::assertSame('ABNANL2A', $payoutMethods[0]->settings()['bic']); + } + + /** @test */ + public function show_seller_payout_methods_with_bank_transfer(): void + { + $client = (new AffiliateSellerShowMockClient())->payoutMethods([ + [ + 'id' => 2, + 'method' => 'bank_transfer', + 'settings' => [ + 'iban' => 'DE89370400440532013000', + 'bic' => 'COBADEFFXXX', + ], + 'created_at' => '2024-01-01T00:00:00.000000Z', + 'updated_at' => '2024-01-01T00:00:00.000000Z', + ], + ]); + $service = new AffiliateSellerService($client); + + $seller = $service->find(1); + + $payoutMethods = $seller->payoutMethods(); + static::assertCount(1, $payoutMethods); + static::assertSame(2, $payoutMethods[0]->id()); + static::assertSame('bank_transfer', $payoutMethods[0]->method()); + static::assertSame('DE89370400440532013000', $payoutMethods[0]->settings()['iban']); + static::assertSame('COBADEFFXXX', $payoutMethods[0]->settings()['bic']); + } + + /** @test */ + public function show_seller_payout_methods_empty_array(): void + { + $client = (new AffiliateSellerShowMockClient())->payoutMethods([]); + $service = new AffiliateSellerService($client); + + $seller = $service->find(1); + + $payoutMethods = $seller->payoutMethods(); + static::assertIsArray($payoutMethods); + static::assertCount(0, $payoutMethods); + } + + /** @test */ + public function show_seller_payout_methods_multiple(): void + { + $client = (new AffiliateSellerShowMockClient())->payoutMethods([ + [ + 'id' => 1, + 'method' => 'bank_transfer', + 'settings' => [ + 'iban' => 'NL91ABNA0417164300', + 'bic' => 'ABNANL2A', + ], + 'created_at' => '2024-01-01T00:00:00.000000Z', + 'updated_at' => '2024-01-01T00:00:00.000000Z', + ], + [ + 'id' => 2, + 'method' => 'paypal', + 'settings' => [ + 'email' => 'seller@example.com', + ], + 'created_at' => '2024-01-02T00:00:00.000000Z', + 'updated_at' => '2024-01-02T00:00:00.000000Z', + ], + ]); + $service = new AffiliateSellerService($client); + + $seller = $service->find(1); + + $payoutMethods = $seller->payoutMethods(); + static::assertCount(2, $payoutMethods); + + static::assertSame(1, $payoutMethods[0]->id()); + static::assertSame(2, $payoutMethods[1]->id()); + } } diff --git a/src/Director/BodyTo/BodyToAffiliateSeller.php b/src/Director/BodyTo/BodyToAffiliateSeller.php index 3522eaf..1027f03 100644 --- a/src/Director/BodyTo/BodyToAffiliateSeller.php +++ b/src/Director/BodyTo/BodyToAffiliateSeller.php @@ -23,8 +23,7 @@ public static function build(array $data): AffiliateSeller ->setEmail($data['email']) ->setDeclineReason($data['decline_reason'] ?? null) ->setProfileId($data['profile_id']) - ->setStatus(SellerStatus::from($data['status'])) - ->setPayoutMethods($data['payout_methods'] ?? []); + ->setStatus(SellerStatus::from($data['status'])); if (isset($data['address'])) { $seller->setAddress(BodyToAddress::build($data['address'])); @@ -46,6 +45,10 @@ public static function build(array $data): AffiliateSeller $seller->setPayoutOptions(BodyToSellerPayoutOptions::build($data['payout_options'])); } + if (isset($data['payout_methods'])) { + $seller->setPayoutMethods(BodyToPayoutMethod::buildMulti($data['payout_methods'])); + } + return $seller; } -} +} \ No newline at end of file diff --git a/src/Director/BodyTo/BodyToPayoutMethod.php b/src/Director/BodyTo/BodyToPayoutMethod.php new file mode 100644 index 0000000..1c02d0e --- /dev/null +++ b/src/Director/BodyTo/BodyToPayoutMethod.php @@ -0,0 +1,27 @@ +setId($data['id']) + ->setMethod($data['method']) + ->setSettings($data['settings'] ?? null) + ->setCreatedAt(new DateTimeImmutable($data['created_at'])) + ->setUpdatedAt(new DateTimeImmutable($data['updated_at'])); + } +} \ No newline at end of file diff --git a/src/Entity/AffiliateSeller.php b/src/Entity/AffiliateSeller.php index 1336bf5..37a0585 100644 --- a/src/Entity/AffiliateSeller.php +++ b/src/Entity/AffiliateSeller.php @@ -7,6 +7,7 @@ use PlugAndPay\Sdk\Enum\SellerStatus; use PlugAndPay\Sdk\Exception\RelationNotLoadedException; use PlugAndPay\Sdk\Traits\HasDynamicFields; +use PlugAndPay\Sdk\Entity\PayoutMethod; class AffiliateSeller extends AbstractEntity { @@ -24,6 +25,7 @@ class AffiliateSeller extends AbstractEntity protected SellerStatistics $statistics; protected SellerStatus $status; protected SellerPayoutOptions $payoutOptions; + /** @var PayoutMethod[] */ protected array $payoutMethods; public function __construct(bool $allowEmptyRelations = true) @@ -176,8 +178,20 @@ public function setPayoutOptions(SellerPayoutOptions $payoutOptions): self return $this; } + /** + * @throws RelationNotLoadedException + * @return PayoutMethod[] + */ public function payoutMethods(): array { + if (!isset($this->payoutMethods)) { + if ($this->allowEmptyRelations) { + $this->payoutMethods = []; + } else { + throw new RelationNotLoadedException('payoutMethods'); + } + } + return $this->payoutMethods; } } diff --git a/src/Entity/PayoutMethod.php b/src/Entity/PayoutMethod.php new file mode 100644 index 0000000..76a7a27 --- /dev/null +++ b/src/Entity/PayoutMethod.php @@ -0,0 +1,41 @@ +createdAt; + } + + public function id(): int + { + return $this->id; + } + + public function method(): string + { + return $this->method; + } + + public function settings(): ?array + { + return $this->settings; + } + + public function updatedAt(): DateTimeImmutable + { + return $this->updatedAt; + } +} \ No newline at end of file diff --git a/src/Entity/PayoutMethodInternal.php b/src/Entity/PayoutMethodInternal.php new file mode 100644 index 0000000..cd6c8bc --- /dev/null +++ b/src/Entity/PayoutMethodInternal.php @@ -0,0 +1,63 @@ +createdAt = $createdAt; + + return $this; + } + + /** + * @internal + */ + public function setId(int $id): self + { + $this->id = $id; + + return $this; + } + + /** + * @internal + */ + public function setMethod(string $method): self + { + $this->method = $method; + + return $this; + } + + /** + * @internal + */ + public function setSettings(?array $settings): self + { + $this->settings = $settings; + + return $this; + } + + /** + * @internal + */ + public function setUpdatedAt(DateTimeImmutable $updatedAt): self + { + $this->updatedAt = $updatedAt; + + return $this; + } +} \ No newline at end of file From 0894bb4639d26cd9140607d5dc1b488951f4e23b Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Thu, 6 Nov 2025 17:34:49 +0100 Subject: [PATCH 08/19] Add seller payout methods --- .../IndexAffiliateSellersTest.php | 27 ++++++++++++++++--- .../ShowAffiliateSellerTest.php | 3 ++- src/Enum/AffiliateSellerIncludes.php | 1 + 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php b/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php index ff24ed3..1b51e97 100644 --- a/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php +++ b/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php @@ -18,15 +18,34 @@ class IndexAffiliateSellersTest extends TestCase { - /** @test */ - public function index_sellers(): void + /** + * @test + * @dataProvider relationsProvider + */ + public function index_sellers_with_relations(AffiliateSellerIncludes $include, string $includeValue): void { $client = (new AffiliateSellerIndexMockClient()); $service = new AffiliateSellerService($client); - $sellers = $service->include(AffiliateSellerIncludes::PROFILE)->get(); + + $sellers = $service->include($include)->get(); static::assertSame(1, $sellers[0]->id()); - static::assertSame('/v2/affiliates/sellers?include=profile', $client->path()); + static::assertSame("/v2/affiliates/sellers?include=$includeValue", $client->path()); + } + + /** + * Data provider for index_sellers_with_relations. + */ + public static function relationsProvider(): array + { + return [ + 'address' => [AffiliateSellerIncludes::ADDRESS, 'address'], + 'contact' => [AffiliateSellerIncludes::CONTACT, 'contact'], + 'profile' => [AffiliateSellerIncludes::PROFILE, 'profile'], + 'statistics' => [AffiliateSellerIncludes::STATISTICS, 'statistics'], + 'payoutOptions' => [AffiliateSellerIncludes::PAYOUT_OPTIONS, 'payout_options'], + 'payoutMethods' => [AffiliateSellerIncludes::PAYOUT_METHODS, 'payout_methods'], + ]; } /** diff --git a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php index decc96c..e78eb30 100644 --- a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php +++ b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php @@ -363,7 +363,7 @@ public function show_seller_with_payout_methods(): void $client = (new AffiliateSellerShowMockClient())->payoutMethods(); $service = new AffiliateSellerService($client); - $seller = $service->find(1); + $seller = $service->include(AffiliateSellerIncludes::PAYOUT_METHODS)->find(1); $payoutMethods = $seller->payoutMethods(); static::assertIsArray($payoutMethods); @@ -373,6 +373,7 @@ public function show_seller_with_payout_methods(): void static::assertIsArray($payoutMethods[0]->settings()); static::assertSame('NL91ABNA0417164300', $payoutMethods[0]->settings()['iban']); static::assertSame('ABNANL2A', $payoutMethods[0]->settings()['bic']); + static::assertSame('/v2/affiliates/sellers/1?include=payout_methods', $client->path()); } /** @test */ diff --git a/src/Enum/AffiliateSellerIncludes.php b/src/Enum/AffiliateSellerIncludes.php index 990aad9..195d67b 100644 --- a/src/Enum/AffiliateSellerIncludes.php +++ b/src/Enum/AffiliateSellerIncludes.php @@ -11,4 +11,5 @@ enum AffiliateSellerIncludes: string case PROFILE = 'profile'; case STATISTICS = 'statistics'; case PAYOUT_OPTIONS = 'payout_options'; + case PAYOUT_METHODS = 'payout_methods'; } From c150c55bb04de46f70d9af8ae0f3525812302bf2 Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Thu, 6 Nov 2025 17:37:44 +0100 Subject: [PATCH 09/19] Remove not existing allowEmptyRelations parameter --- src/Entity/AffiliateSeller.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Entity/AffiliateSeller.php b/src/Entity/AffiliateSeller.php index 37a0585..633ce59 100644 --- a/src/Entity/AffiliateSeller.php +++ b/src/Entity/AffiliateSeller.php @@ -7,7 +7,6 @@ use PlugAndPay\Sdk\Enum\SellerStatus; use PlugAndPay\Sdk\Exception\RelationNotLoadedException; use PlugAndPay\Sdk\Traits\HasDynamicFields; -use PlugAndPay\Sdk\Entity\PayoutMethod; class AffiliateSeller extends AbstractEntity { @@ -40,7 +39,7 @@ public function address(): Address { if (!isset($this->address)) { if ($this->allowEmptyRelations) { - $this->address = new Address($this->allowEmptyRelations); + $this->address = new Address(); } else { throw new RelationNotLoadedException('address'); } @@ -63,7 +62,7 @@ public function contact(): Contact { if (!isset($this->contact)) { if ($this->allowEmptyRelations) { - $this->contact = new Contact($this->allowEmptyRelations); + $this->contact = new Contact(); } else { throw new RelationNotLoadedException('contact'); } @@ -106,7 +105,7 @@ public function profile(): SellerProfile { if (!isset($this->profile)) { if ($this->allowEmptyRelations) { - $this->profile = new SellerProfile($this->allowEmptyRelations); + $this->profile = new SellerProfile(); } else { throw new RelationNotLoadedException('profile'); } @@ -134,7 +133,7 @@ public function statistics(): SellerStatistics { if (!isset($this->statistics)) { if ($this->allowEmptyRelations) { - $this->statistics = new SellerStatistics($this->allowEmptyRelations); + $this->statistics = new SellerStatistics(); } else { throw new RelationNotLoadedException('statistics'); } @@ -162,7 +161,7 @@ public function payoutOptions(): SellerPayoutOptions { if (!isset($this->payoutOptions)) { if ($this->allowEmptyRelations) { - $this->payoutOptions = new SellerPayoutOptions($this->allowEmptyRelations); + $this->payoutOptions = new SellerPayoutOptions(); } else { throw new RelationNotLoadedException('payoutOptions'); } From 0a06220123e9084251faf22919eeb3f91edf6c57 Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Thu, 6 Nov 2025 17:50:46 +0100 Subject: [PATCH 10/19] Add missing fields to seller profile entity --- .../Mock/AffiliateSellerShowMockClient.php | 9 ++- .../ShowAffiliateSellerTest.php | 62 ++++++++++++++++++- src/Director/BodyTo/BodyToSellerProfile.php | 9 ++- src/Entity/SellerProfile.php | 44 ++++++++++++- src/Entity/SellerProfileInternal.php | 51 ++++++++++++++- 5 files changed, 170 insertions(+), 5 deletions(-) diff --git a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php index 4614e2f..c4267d7 100644 --- a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php +++ b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php @@ -85,7 +85,14 @@ public function path(): string public function profile(array $data = []): self { $this->responseBody['data']['profile'] = $data + [ - 'id' => 1, + 'id' => 14, + 'default_recurring' => false, + 'default_type' => 'percentage', + 'default_value' => '50.00', + 'default_form_value' => '2.50', + 'label' => '', + 'session_lifetime' => 120, + 'tenant_id' => 3, ]; return $this; diff --git a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php index e78eb30..d89d3b0 100644 --- a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php +++ b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php @@ -146,7 +146,7 @@ public function show_seller_with_profile(): void $seller = $service->include(AffiliateSellerIncludes::PROFILE)->find(1); $profile = $seller->profile(); - static::assertSame(1, $profile->id()); + static::assertSame(14, $profile->id()); static::assertSame('/v2/affiliates/sellers/1?include=profile', $client->path()); } @@ -234,6 +234,66 @@ public function show_seller_profile_with_custom_id(): void static::assertSame(42, $profile->id()); } + /** @test */ + public function show_seller_profile_with_values(): void + { + $client = (new AffiliateSellerShowMockClient())->profile([ + 'id' => 99, + 'default_recurring' => 1, + 'default_type' => 'fixed', + 'default_value' => '100.00', + 'default_form_value' => '5.00', + 'label' => 'Premium Seller', + 'session_lifetime' => 300, + 'tenant_id' => 7, + ]); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::PROFILE)->find(1); + + $profile = $seller->profile(); + static::assertSame(99, $profile->id()); + static::assertTrue($profile->defaultRecurring()); + static::assertSame('fixed', $profile->defaultType()); + static::assertSame(100.0, $profile->defaultValue()); + static::assertSame(5.0, $profile->defaultFormValue()); + static::assertSame('Premium Seller', $profile->label()); + static::assertSame(300, $profile->sessionLifetime()); + static::assertSame(7, $profile->tenantId()); + } + + /** @test */ + public function show_seller_profile_with_recurring_enabled(): void + { + $client = (new AffiliateSellerShowMockClient())->profile([ + 'default_recurring' => true, + ]); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::PROFILE)->find(1); + + $profile = $seller->profile(); + static::assertTrue($profile->defaultRecurring()); + } + + /** @test */ + public function show_seller_profile_with_zero_values(): void + { + $client = (new AffiliateSellerShowMockClient())->profile([ + 'default_value' => '0.00', + 'default_form_value' => '0.00', + 'session_lifetime' => 0, + ]); + $service = new AffiliateSellerService($client); + + $seller = $service->include(AffiliateSellerIncludes::PROFILE)->find(1); + + $profile = $seller->profile(); + static::assertSame(0.0, $profile->defaultValue()); + static::assertSame(0.0, $profile->defaultFormValue()); + static::assertSame(0, $profile->sessionLifetime()); + } + /** @test */ public function test_seller_statistics_isset_for_existing_property(): void { diff --git a/src/Director/BodyTo/BodyToSellerProfile.php b/src/Director/BodyTo/BodyToSellerProfile.php index 1509c68..b4b21ea 100644 --- a/src/Director/BodyTo/BodyToSellerProfile.php +++ b/src/Director/BodyTo/BodyToSellerProfile.php @@ -13,6 +13,13 @@ class BodyToSellerProfile implements BuildObjectInterface public static function build(array $data): SellerProfile { return (new SellerProfileInternal()) - ->setId($data['id']); + ->setId($data['id']) + ->setDefaultRecurring((bool) $data['default_recurring']) + ->setDefaultType($data['default_type']) + ->setDefaultValue((float) $data['default_value']) + ->setDefaultFormValue((float) $data['default_form_value']) + ->setLabel($data['label']) + ->setSessionLifetime($data['session_lifetime']) + ->setTenantId($data['tenant_id']); } } diff --git a/src/Entity/SellerProfile.php b/src/Entity/SellerProfile.php index 1ddd47d..099e08e 100644 --- a/src/Entity/SellerProfile.php +++ b/src/Entity/SellerProfile.php @@ -11,9 +11,51 @@ class SellerProfile extends AbstractEntity use HasDynamicFields; protected int $id; + protected bool $defaultRecurring; + protected string $defaultType; + protected float $defaultValue; + protected float $defaultFormValue; + protected string $label; + protected int $sessionLifetime; + protected int $tenantId; public function id(): int { return $this->id; } -} + + public function defaultRecurring(): bool + { + return $this->defaultRecurring; + } + + public function defaultType(): string + { + return $this->defaultType; + } + + public function defaultValue(): float + { + return $this->defaultValue; + } + + public function defaultFormValue(): float + { + return $this->defaultFormValue; + } + + public function label(): string + { + return $this->label; + } + + public function sessionLifetime(): int + { + return $this->sessionLifetime; + } + + public function tenantId(): int + { + return $this->tenantId; + } +} \ No newline at end of file diff --git a/src/Entity/SellerProfileInternal.php b/src/Entity/SellerProfileInternal.php index c504866..d6cd15d 100644 --- a/src/Entity/SellerProfileInternal.php +++ b/src/Entity/SellerProfileInternal.php @@ -12,4 +12,53 @@ public function setId(int $id): self return $this; } -} + + public function setDefaultRecurring(bool $defaultRecurring): self + { + $this->defaultRecurring = $defaultRecurring; + + return $this; + } + + public function setDefaultType(string $defaultType): self + { + $this->defaultType = $defaultType; + + return $this; + } + + public function setDefaultValue(float $defaultValue): self + { + $this->defaultValue = $defaultValue; + + return $this; + } + + public function setDefaultFormValue(float $defaultFormValue): self + { + $this->defaultFormValue = $defaultFormValue; + + return $this; + } + + public function setLabel(string $label): self + { + $this->label = $label; + + return $this; + } + + public function setSessionLifetime(int $sessionLifetime): self + { + $this->sessionLifetime = $sessionLifetime; + + return $this; + } + + public function setTenantId(int $tenantId): self + { + $this->tenantId = $tenantId; + + return $this; + } +} \ No newline at end of file From c916cdb10f5885bf8514765394ddcfa5bec73fce Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Thu, 6 Nov 2025 18:01:30 +0100 Subject: [PATCH 11/19] Fix sort types to sort the affiliate sellers --- .../IndexAffiliateSellersTest.php | 36 +++++++++++++++++++ src/Enum/SellerSortType.php | 15 ++++---- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php b/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php index 1b51e97..c8c6f90 100644 --- a/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php +++ b/Tests/Feature/AffiliateSeller/IndexAffiliateSellersTest.php @@ -104,6 +104,42 @@ public static function sellerFilterDataProvider(): array 'queryKey' => 'sort', 'queryValue' => 'name', ], + [ + 'method' => 'sort', + 'value' => SellerSortType::COMMISSION, + 'queryKey' => 'sort', + 'queryValue' => 'commission', + ], + [ + 'method' => 'sort', + 'value' => SellerSortType::LOCKED_REVENUE, + 'queryKey' => 'sort', + 'queryValue' => 'locked_revenue', + ], + [ + 'method' => 'sort', + 'value' => SellerSortType::PAID_REVENUE, + 'queryKey' => 'sort', + 'queryValue' => 'paid_revenue', + ], + [ + 'method' => 'sort', + 'value' => SellerSortType::PENDING_REVENUE, + 'queryKey' => 'sort', + 'queryValue' => 'pending_revenue', + ], + [ + 'method' => 'sort', + 'value' => SellerSortType::SALES, + 'queryKey' => 'sort', + 'queryValue' => 'sales', + ], + [ + 'method' => 'sort', + 'value' => SellerSortType::TOTAL_REVENUE, + 'queryKey' => 'sort', + 'queryValue' => 'total_revenue', + ], [ 'method' => 'direction', 'value' => Direction::DESC, diff --git a/src/Enum/SellerSortType.php b/src/Enum/SellerSortType.php index 55c6fec..bb9f59f 100644 --- a/src/Enum/SellerSortType.php +++ b/src/Enum/SellerSortType.php @@ -6,12 +6,11 @@ enum SellerSortType: string { - case NAME = 'name'; - case ORDERS = 'orders'; - case SALES = 'sales'; - case PENDING = 'pending'; - case LOCKED = 'locked'; - case PAIDOUT = 'paidout'; - case VALUE = 'value'; - case CREATED_AT = 'created_at'; + case COMMISSION = 'commission'; + case LOCKED_REVENUE = 'locked_revenue'; + case NAME = 'name'; + case PAID_REVENUE = 'paid_revenue'; + case PENDING_REVENUE = 'pending_revenue'; + case SALES = 'sales'; + case TOTAL_REVENUE = 'total_revenue'; } From 88dc6f574c91058b44f0f3c148f21116df5a2e05 Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Thu, 6 Nov 2025 18:03:53 +0100 Subject: [PATCH 12/19] Remove payout_methods, it is not a (default) value --- .../AffiliateSeller/Mock/AffiliateSellerShowMockClient.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php index c4267d7..0d84920 100644 --- a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php +++ b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php @@ -20,7 +20,6 @@ class AffiliateSellerShowMockClient extends ClientMock 'decline_reason' => '', 'profile_id' => 1, 'status' => 'accepted', - 'payout_methods' => null, ]; protected string $path; From c3ca85cca30a669985449f130e0be577f7082873 Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Thu, 6 Nov 2025 17:05:43 +0000 Subject: [PATCH 13/19] Apply php-cs-fixer changes --- .../Mock/AffiliateSellerShowMockClient.php | 1 + src/Director/BodyTo/BodyToAffiliateSeller.php | 2 +- src/Director/BodyTo/BodyToPayoutMethod.php | 2 +- src/Entity/PayoutMethod.php | 2 +- src/Entity/PayoutMethodInternal.php | 2 +- src/Entity/SellerPayoutOptions.php | 2 ++ src/Entity/SellerProfile.php | 2 +- src/Entity/SellerProfileInternal.php | 2 +- src/Entity/SellerStatistics.php | 9 +++++++++ 9 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php index 0d84920..4a286dd 100644 --- a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php +++ b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php @@ -127,6 +127,7 @@ public function payoutOptions(array $data = []): self return $this; } + public function payoutMethods(?array $data = null): self { if ($data === null) { diff --git a/src/Director/BodyTo/BodyToAffiliateSeller.php b/src/Director/BodyTo/BodyToAffiliateSeller.php index 1027f03..beab772 100644 --- a/src/Director/BodyTo/BodyToAffiliateSeller.php +++ b/src/Director/BodyTo/BodyToAffiliateSeller.php @@ -51,4 +51,4 @@ public static function build(array $data): AffiliateSeller return $seller; } -} \ No newline at end of file +} diff --git a/src/Director/BodyTo/BodyToPayoutMethod.php b/src/Director/BodyTo/BodyToPayoutMethod.php index 1c02d0e..ad64cd7 100644 --- a/src/Director/BodyTo/BodyToPayoutMethod.php +++ b/src/Director/BodyTo/BodyToPayoutMethod.php @@ -24,4 +24,4 @@ public static function build(array $data): PayoutMethod ->setCreatedAt(new DateTimeImmutable($data['created_at'])) ->setUpdatedAt(new DateTimeImmutable($data['updated_at'])); } -} \ No newline at end of file +} diff --git a/src/Entity/PayoutMethod.php b/src/Entity/PayoutMethod.php index 76a7a27..e7b963c 100644 --- a/src/Entity/PayoutMethod.php +++ b/src/Entity/PayoutMethod.php @@ -38,4 +38,4 @@ public function updatedAt(): DateTimeImmutable { return $this->updatedAt; } -} \ No newline at end of file +} diff --git a/src/Entity/PayoutMethodInternal.php b/src/Entity/PayoutMethodInternal.php index cd6c8bc..7d0a277 100644 --- a/src/Entity/PayoutMethodInternal.php +++ b/src/Entity/PayoutMethodInternal.php @@ -60,4 +60,4 @@ public function setUpdatedAt(DateTimeImmutable $updatedAt): self return $this; } -} \ No newline at end of file +} diff --git a/src/Entity/SellerPayoutOptions.php b/src/Entity/SellerPayoutOptions.php index b4afe3c..9ffce21 100644 --- a/src/Entity/SellerPayoutOptions.php +++ b/src/Entity/SellerPayoutOptions.php @@ -21,6 +21,7 @@ public function method(): ?string public function setMethod(?string $method): self { $this->method = $method; + return $this; } @@ -32,6 +33,7 @@ public function settings(): ?array public function setSettings(?array $settings): self { $this->settings = $settings; + return $this; } } diff --git a/src/Entity/SellerProfile.php b/src/Entity/SellerProfile.php index 099e08e..616e0c9 100644 --- a/src/Entity/SellerProfile.php +++ b/src/Entity/SellerProfile.php @@ -58,4 +58,4 @@ public function tenantId(): int { return $this->tenantId; } -} \ No newline at end of file +} diff --git a/src/Entity/SellerProfileInternal.php b/src/Entity/SellerProfileInternal.php index d6cd15d..147428b 100644 --- a/src/Entity/SellerProfileInternal.php +++ b/src/Entity/SellerProfileInternal.php @@ -61,4 +61,4 @@ public function setTenantId(int $tenantId): self return $this; } -} \ No newline at end of file +} diff --git a/src/Entity/SellerStatistics.php b/src/Entity/SellerStatistics.php index eae12a6..96c8ff7 100644 --- a/src/Entity/SellerStatistics.php +++ b/src/Entity/SellerStatistics.php @@ -28,6 +28,7 @@ public function clicks(): ?int public function setClicks(?int $clicks): self { $this->clicks = $clicks; + return $this; } @@ -39,6 +40,7 @@ public function commission(): int public function setCommission(int $commission): self { $this->commission = $commission; + return $this; } @@ -50,6 +52,7 @@ public function locked(): int public function setLocked(int $locked): self { $this->locked = $locked; + return $this; } @@ -61,6 +64,7 @@ public function orders(): int public function setOrders(int $orders): self { $this->orders = $orders; + return $this; } @@ -72,6 +76,7 @@ public function paidout(): int public function setPaidout(int $paidout): self { $this->paidout = $paidout; + return $this; } @@ -83,6 +88,7 @@ public function pending(): int public function setPending(int $pending): self { $this->pending = $pending; + return $this; } @@ -94,6 +100,7 @@ public function recurring(): int public function setRecurring(int $recurring): self { $this->recurring = $recurring; + return $this; } @@ -105,6 +112,7 @@ public function sales(): int public function setSales(int $sales): self { $this->sales = $sales; + return $this; } @@ -116,6 +124,7 @@ public function value(): int public function setValue(int $value): self { $this->value = $value; + return $this; } } From bf01a23db228a16e04879324d3e857ececeffd29 Mon Sep 17 00:00:00 2001 From: joeydehaas Date: Mon, 10 Nov 2025 13:53:41 +0100 Subject: [PATCH 14/19] fix: remove payout methods from affiliate --- src/Director/BodyTo/BodyToAffiliateSeller.php | 4 ---- src/Enum/AffiliateSellerIncludes.php | 1 - 2 files changed, 5 deletions(-) diff --git a/src/Director/BodyTo/BodyToAffiliateSeller.php b/src/Director/BodyTo/BodyToAffiliateSeller.php index beab772..b7930eb 100644 --- a/src/Director/BodyTo/BodyToAffiliateSeller.php +++ b/src/Director/BodyTo/BodyToAffiliateSeller.php @@ -45,10 +45,6 @@ public static function build(array $data): AffiliateSeller $seller->setPayoutOptions(BodyToSellerPayoutOptions::build($data['payout_options'])); } - if (isset($data['payout_methods'])) { - $seller->setPayoutMethods(BodyToPayoutMethod::buildMulti($data['payout_methods'])); - } - return $seller; } } diff --git a/src/Enum/AffiliateSellerIncludes.php b/src/Enum/AffiliateSellerIncludes.php index 195d67b..990aad9 100644 --- a/src/Enum/AffiliateSellerIncludes.php +++ b/src/Enum/AffiliateSellerIncludes.php @@ -11,5 +11,4 @@ enum AffiliateSellerIncludes: string case PROFILE = 'profile'; case STATISTICS = 'statistics'; case PAYOUT_OPTIONS = 'payout_options'; - case PAYOUT_METHODS = 'payout_methods'; } From e6fd65fc552ce9c9e8697a9273e4657db2fdc2f9 Mon Sep 17 00:00:00 2001 From: joeydehaas Date: Mon, 10 Nov 2025 13:53:55 +0100 Subject: [PATCH 15/19] fix: update type hinting --- src/Entity/SellerStatistics.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Entity/SellerStatistics.php b/src/Entity/SellerStatistics.php index 96c8ff7..252e9bd 100644 --- a/src/Entity/SellerStatistics.php +++ b/src/Entity/SellerStatistics.php @@ -11,8 +11,8 @@ class SellerStatistics extends AbstractEntity use HasDynamicFields; protected ?int $clicks; - protected int $commission; - protected int $locked; + protected float $commission; + protected float $locked; protected int $orders; protected int $paidout; protected int $pending; @@ -32,24 +32,24 @@ public function setClicks(?int $clicks): self return $this; } - public function commission(): int + public function commission(): float { return $this->commission; } - public function setCommission(int $commission): self + public function setCommission(float $commission): self { $this->commission = $commission; return $this; } - public function locked(): int + public function locked(): float { return $this->locked; } - public function setLocked(int $locked): self + public function setLocked(float $locked): self { $this->locked = $locked; From 91b21865612cb4cb774f26ff279f24aee4caf0af Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Mon, 10 Nov 2025 14:24:30 +0100 Subject: [PATCH 16/19] Some statistics fields are floats --- .../BodyTo/BodyToSellerStatistics.php | 12 +-- src/Entity/PayoutMethodInternal.php | 15 ---- src/Entity/SellerStatistics.php | 79 ++----------------- src/Entity/SellerStatisticsInternal.php | 66 +++++++++++++++- src/Enum/AffiliateSellerIncludes.php | 1 + 5 files changed, 80 insertions(+), 93 deletions(-) diff --git a/src/Director/BodyTo/BodyToSellerStatistics.php b/src/Director/BodyTo/BodyToSellerStatistics.php index 63d7ffe..db0084c 100644 --- a/src/Director/BodyTo/BodyToSellerStatistics.php +++ b/src/Director/BodyTo/BodyToSellerStatistics.php @@ -14,13 +14,13 @@ public static function build(array $data): SellerStatistics { return (new SellerStatisticsInternal()) ->setClicks($data['clicks'] ?? null) - ->setCommission($data['commission'] ?? 0) - ->setLocked($data['locked'] ?? 0) + ->setCommission((float) ($data['commission'] ?? 0)) + ->setLocked((float) ($data['locked'] ?? 0)) ->setOrders($data['orders'] ?? 0) - ->setPaidout($data['paidout'] ?? 0) - ->setPending($data['pending'] ?? 0) - ->setRecurring($data['recurring'] ?? 0) + ->setPaidout((float)($data['paidout'] ?? 0)) + ->setPending((float)($data['pending'] ?? 0)) + ->setRecurring((float)$data['recurring'] ?? 0) ->setSales($data['sales'] ?? 0) - ->setValue($data['value'] ?? 0); + ->setValue((float)($data['value'] ?? 0)); } } diff --git a/src/Entity/PayoutMethodInternal.php b/src/Entity/PayoutMethodInternal.php index 7d0a277..7b9b5b9 100644 --- a/src/Entity/PayoutMethodInternal.php +++ b/src/Entity/PayoutMethodInternal.php @@ -11,9 +11,6 @@ */ class PayoutMethodInternal extends PayoutMethod { - /** - * @internal - */ public function setCreatedAt(DateTimeImmutable $createdAt): self { $this->createdAt = $createdAt; @@ -21,9 +18,6 @@ public function setCreatedAt(DateTimeImmutable $createdAt): self return $this; } - /** - * @internal - */ public function setId(int $id): self { $this->id = $id; @@ -31,9 +25,6 @@ public function setId(int $id): self return $this; } - /** - * @internal - */ public function setMethod(string $method): self { $this->method = $method; @@ -41,9 +32,6 @@ public function setMethod(string $method): self return $this; } - /** - * @internal - */ public function setSettings(?array $settings): self { $this->settings = $settings; @@ -51,9 +39,6 @@ public function setSettings(?array $settings): self return $this; } - /** - * @internal - */ public function setUpdatedAt(DateTimeImmutable $updatedAt): self { $this->updatedAt = $updatedAt; diff --git a/src/Entity/SellerStatistics.php b/src/Entity/SellerStatistics.php index 252e9bd..e48515f 100644 --- a/src/Entity/SellerStatistics.php +++ b/src/Entity/SellerStatistics.php @@ -14,117 +14,54 @@ class SellerStatistics extends AbstractEntity protected float $commission; protected float $locked; protected int $orders; - protected int $paidout; - protected int $pending; - protected int $recurring; + protected float $paidout; + protected float $pending; + protected float $recurring; protected int $sales; - protected int $value; + protected float $value; public function clicks(): ?int { return $this->clicks; } - public function setClicks(?int $clicks): self - { - $this->clicks = $clicks; - - return $this; - } - public function commission(): float { return $this->commission; } - public function setCommission(float $commission): self - { - $this->commission = $commission; - - return $this; - } - public function locked(): float { return $this->locked; } - public function setLocked(float $locked): self - { - $this->locked = $locked; - - return $this; - } - public function orders(): int { return $this->orders; } - public function setOrders(int $orders): self - { - $this->orders = $orders; - - return $this; - } - - public function paidout(): int + public function paidout(): float { return $this->paidout; } - public function setPaidout(int $paidout): self - { - $this->paidout = $paidout; - - return $this; - } - - public function pending(): int + public function pending(): float { return $this->pending; } - public function setPending(int $pending): self - { - $this->pending = $pending; - - return $this; - } - - public function recurring(): int + public function recurring(): float { return $this->recurring; } - public function setRecurring(int $recurring): self - { - $this->recurring = $recurring; - - return $this; - } - public function sales(): int { return $this->sales; } - public function setSales(int $sales): self - { - $this->sales = $sales; - - return $this; - } - - public function value(): int + public function value(): float { return $this->value; } - - public function setValue(int $value): self - { - $this->value = $value; - - return $this; - } } diff --git a/src/Entity/SellerStatisticsInternal.php b/src/Entity/SellerStatisticsInternal.php index 0813af6..1b7a5a3 100644 --- a/src/Entity/SellerStatisticsInternal.php +++ b/src/Entity/SellerStatisticsInternal.php @@ -4,7 +4,71 @@ namespace PlugAndPay\Sdk\Entity; +/** + * @internal + */ class SellerStatisticsInternal extends SellerStatistics { - // Internal entity for building from API response + public function setClicks(?int $clicks): self + { + $this->clicks = $clicks; + + return $this; + } + + public function setCommission(float $commission): self + { + $this->commission = $commission; + + return $this; + } + + public function setLocked(float $locked): self + { + $this->locked = $locked; + + return $this; + } + + public function setOrders(int $orders): self + { + $this->orders = $orders; + + return $this; + } + + public function setPaidout(float $paidout): self + { + $this->paidout = $paidout; + + return $this; + } + + public function setPending(float $pending): self + { + $this->pending = $pending; + + return $this; + } + + public function setRecurring(float $recurring): self + { + $this->recurring = $recurring; + + return $this; + } + + public function setSales(int $sales): self + { + $this->sales = $sales; + + return $this; + } + + public function setValue(float $value): self + { + $this->value = $value; + + return $this; + } } diff --git a/src/Enum/AffiliateSellerIncludes.php b/src/Enum/AffiliateSellerIncludes.php index 990aad9..195d67b 100644 --- a/src/Enum/AffiliateSellerIncludes.php +++ b/src/Enum/AffiliateSellerIncludes.php @@ -11,4 +11,5 @@ enum AffiliateSellerIncludes: string case PROFILE = 'profile'; case STATISTICS = 'statistics'; case PAYOUT_OPTIONS = 'payout_options'; + case PAYOUT_METHODS = 'payout_methods'; } From 822f24e6adc20f1b4d534d7302a254bc9e46bd22 Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Mon, 10 Nov 2025 13:26:08 +0000 Subject: [PATCH 17/19] Apply php-cs-fixer changes --- src/Director/BodyTo/BodyToSellerStatistics.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Director/BodyTo/BodyToSellerStatistics.php b/src/Director/BodyTo/BodyToSellerStatistics.php index db0084c..1b88f7e 100644 --- a/src/Director/BodyTo/BodyToSellerStatistics.php +++ b/src/Director/BodyTo/BodyToSellerStatistics.php @@ -17,10 +17,10 @@ public static function build(array $data): SellerStatistics ->setCommission((float) ($data['commission'] ?? 0)) ->setLocked((float) ($data['locked'] ?? 0)) ->setOrders($data['orders'] ?? 0) - ->setPaidout((float)($data['paidout'] ?? 0)) - ->setPending((float)($data['pending'] ?? 0)) - ->setRecurring((float)$data['recurring'] ?? 0) + ->setPaidout((float) ($data['paidout'] ?? 0)) + ->setPending((float) ($data['pending'] ?? 0)) + ->setRecurring((float) $data['recurring'] ?? 0) ->setSales($data['sales'] ?? 0) - ->setValue((float)($data['value'] ?? 0)); + ->setValue((float) ($data['value'] ?? 0)); } } From d759a653bb0ce94c7dce8e217e314c9b743532b6 Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Mon, 10 Nov 2025 14:38:20 +0100 Subject: [PATCH 18/19] Payment methods without id and dates --- .../Mock/AffiliateSellerShowMockClient.php | 3 --- .../ShowAffiliateSellerTest.php | 27 ++++++------------- src/Director/BodyTo/BodyToAffiliateSeller.php | 5 ++++ src/Director/BodyTo/BodyToPayoutMethod.php | 5 +--- src/Entity/AffiliateSellerInternal.php | 4 +++ src/Entity/PayoutMethod.php | 20 -------------- src/Entity/PayoutMethodInternal.php | 21 --------------- 7 files changed, 18 insertions(+), 67 deletions(-) diff --git a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php index 4a286dd..7b0330b 100644 --- a/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php +++ b/Tests/Feature/AffiliateSeller/Mock/AffiliateSellerShowMockClient.php @@ -133,14 +133,11 @@ public function payoutMethods(?array $data = null): self if ($data === null) { $data = [ [ - 'id' => 1, 'method' => 'bank_transfer', 'settings' => [ 'iban' => 'NL91ABNA0417164300', 'bic' => 'ABNANL2A', ], - 'created_at' => '2024-01-01T00:00:00.000000Z', - 'updated_at' => '2024-01-01T00:00:00.000000Z', ], ]; } diff --git a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php index d89d3b0..601b9a6 100644 --- a/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php +++ b/Tests/Feature/AffiliateSeller/ShowAffiliateSellerTest.php @@ -196,14 +196,14 @@ public function show_seller_statistics_with_values(): void $statistics = $seller->statistics(); static::assertSame(150, $statistics->clicks()); - static::assertSame(2500, $statistics->commission()); - static::assertSame(750, $statistics->locked()); + static::assertSame(2500.0, $statistics->commission()); + static::assertSame(750.0, $statistics->locked()); static::assertSame(42, $statistics->orders()); - static::assertSame(10000, $statistics->paidout()); - static::assertSame(1250, $statistics->pending()); - static::assertSame(3500, $statistics->recurring()); + static::assertSame(10000.0, $statistics->paidout()); + static::assertSame(1250.0, $statistics->pending()); + static::assertSame(3500.0, $statistics->recurring()); static::assertSame(55, $statistics->sales()); - static::assertSame(15000, $statistics->value()); + static::assertSame(15000.0, $statistics->value()); } /** @test */ @@ -428,7 +428,6 @@ public function show_seller_with_payout_methods(): void $payoutMethods = $seller->payoutMethods(); static::assertIsArray($payoutMethods); static::assertCount(1, $payoutMethods); - static::assertSame(1, $payoutMethods[0]->id()); static::assertSame('bank_transfer', $payoutMethods[0]->method()); static::assertIsArray($payoutMethods[0]->settings()); static::assertSame('NL91ABNA0417164300', $payoutMethods[0]->settings()['iban']); @@ -441,14 +440,11 @@ public function show_seller_payout_methods_with_bank_transfer(): void { $client = (new AffiliateSellerShowMockClient())->payoutMethods([ [ - 'id' => 2, 'method' => 'bank_transfer', 'settings' => [ 'iban' => 'DE89370400440532013000', 'bic' => 'COBADEFFXXX', ], - 'created_at' => '2024-01-01T00:00:00.000000Z', - 'updated_at' => '2024-01-01T00:00:00.000000Z', ], ]); $service = new AffiliateSellerService($client); @@ -457,7 +453,6 @@ public function show_seller_payout_methods_with_bank_transfer(): void $payoutMethods = $seller->payoutMethods(); static::assertCount(1, $payoutMethods); - static::assertSame(2, $payoutMethods[0]->id()); static::assertSame('bank_transfer', $payoutMethods[0]->method()); static::assertSame('DE89370400440532013000', $payoutMethods[0]->settings()['iban']); static::assertSame('COBADEFFXXX', $payoutMethods[0]->settings()['bic']); @@ -481,23 +476,17 @@ public function show_seller_payout_methods_multiple(): void { $client = (new AffiliateSellerShowMockClient())->payoutMethods([ [ - 'id' => 1, 'method' => 'bank_transfer', 'settings' => [ 'iban' => 'NL91ABNA0417164300', 'bic' => 'ABNANL2A', ], - 'created_at' => '2024-01-01T00:00:00.000000Z', - 'updated_at' => '2024-01-01T00:00:00.000000Z', ], [ - 'id' => 2, 'method' => 'paypal', 'settings' => [ 'email' => 'seller@example.com', ], - 'created_at' => '2024-01-02T00:00:00.000000Z', - 'updated_at' => '2024-01-02T00:00:00.000000Z', ], ]); $service = new AffiliateSellerService($client); @@ -507,7 +496,7 @@ public function show_seller_payout_methods_multiple(): void $payoutMethods = $seller->payoutMethods(); static::assertCount(2, $payoutMethods); - static::assertSame(1, $payoutMethods[0]->id()); - static::assertSame(2, $payoutMethods[1]->id()); + static::assertSame('bank_transfer', $payoutMethods[0]->method()); + static::assertSame('paypal', $payoutMethods[1]->method()); } } diff --git a/src/Director/BodyTo/BodyToAffiliateSeller.php b/src/Director/BodyTo/BodyToAffiliateSeller.php index b7930eb..f416d4e 100644 --- a/src/Director/BodyTo/BodyToAffiliateSeller.php +++ b/src/Director/BodyTo/BodyToAffiliateSeller.php @@ -45,6 +45,11 @@ public static function build(array $data): AffiliateSeller $seller->setPayoutOptions(BodyToSellerPayoutOptions::build($data['payout_options'])); } + if (isset($data['payout_methods'])) { + /** @noinspection PhpParamsInspection */ + $seller->setPayoutMethods(BodyToPayoutMethod::buildMulti($data['payout_methods'])); + } + return $seller; } } diff --git a/src/Director/BodyTo/BodyToPayoutMethod.php b/src/Director/BodyTo/BodyToPayoutMethod.php index ad64cd7..3773646 100644 --- a/src/Director/BodyTo/BodyToPayoutMethod.php +++ b/src/Director/BodyTo/BodyToPayoutMethod.php @@ -18,10 +18,7 @@ class BodyToPayoutMethod implements BuildObjectInterface, BuildMultipleObjectsIn public static function build(array $data): PayoutMethod { return (new PayoutMethodInternal()) - ->setId($data['id']) ->setMethod($data['method']) - ->setSettings($data['settings'] ?? null) - ->setCreatedAt(new DateTimeImmutable($data['created_at'])) - ->setUpdatedAt(new DateTimeImmutable($data['updated_at'])); + ->setSettings($data['settings'] ?? null); } } diff --git a/src/Entity/AffiliateSellerInternal.php b/src/Entity/AffiliateSellerInternal.php index 0aed705..fa38b97 100644 --- a/src/Entity/AffiliateSellerInternal.php +++ b/src/Entity/AffiliateSellerInternal.php @@ -50,6 +50,10 @@ public function setStatus(SellerStatus $status): self return $this; } + /** + * @param PayoutMethod[] $payoutMethods + * @return $this + */ public function setPayoutMethods(array $payoutMethods): self { $this->payoutMethods = $payoutMethods; diff --git a/src/Entity/PayoutMethod.php b/src/Entity/PayoutMethod.php index e7b963c..9ce1495 100644 --- a/src/Entity/PayoutMethod.php +++ b/src/Entity/PayoutMethod.php @@ -4,25 +4,10 @@ namespace PlugAndPay\Sdk\Entity; -use DateTimeImmutable; - class PayoutMethod extends AbstractEntity { - protected DateTimeImmutable $createdAt; - protected int $id; protected string $method; protected ?array $settings; - protected DateTimeImmutable $updatedAt; - - public function createdAt(): DateTimeImmutable - { - return $this->createdAt; - } - - public function id(): int - { - return $this->id; - } public function method(): string { @@ -33,9 +18,4 @@ public function settings(): ?array { return $this->settings; } - - public function updatedAt(): DateTimeImmutable - { - return $this->updatedAt; - } } diff --git a/src/Entity/PayoutMethodInternal.php b/src/Entity/PayoutMethodInternal.php index 7b9b5b9..8e22a43 100644 --- a/src/Entity/PayoutMethodInternal.php +++ b/src/Entity/PayoutMethodInternal.php @@ -11,20 +11,6 @@ */ class PayoutMethodInternal extends PayoutMethod { - public function setCreatedAt(DateTimeImmutable $createdAt): self - { - $this->createdAt = $createdAt; - - return $this; - } - - public function setId(int $id): self - { - $this->id = $id; - - return $this; - } - public function setMethod(string $method): self { $this->method = $method; @@ -38,11 +24,4 @@ public function setSettings(?array $settings): self return $this; } - - public function setUpdatedAt(DateTimeImmutable $updatedAt): self - { - $this->updatedAt = $updatedAt; - - return $this; - } } From 2114d4859f9caf726776a55648f3992cdf837924 Mon Sep 17 00:00:00 2001 From: reindert-vetter Date: Mon, 10 Nov 2025 13:40:31 +0000 Subject: [PATCH 19/19] Apply php-cs-fixer changes --- src/Director/BodyTo/BodyToAffiliateSeller.php | 2 +- src/Director/BodyTo/BodyToPayoutMethod.php | 1 - src/Entity/PayoutMethodInternal.php | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Director/BodyTo/BodyToAffiliateSeller.php b/src/Director/BodyTo/BodyToAffiliateSeller.php index f416d4e..aad1f1e 100644 --- a/src/Director/BodyTo/BodyToAffiliateSeller.php +++ b/src/Director/BodyTo/BodyToAffiliateSeller.php @@ -46,7 +46,7 @@ public static function build(array $data): AffiliateSeller } if (isset($data['payout_methods'])) { - /** @noinspection PhpParamsInspection */ + /* @noinspection PhpParamsInspection */ $seller->setPayoutMethods(BodyToPayoutMethod::buildMulti($data['payout_methods'])); } diff --git a/src/Director/BodyTo/BodyToPayoutMethod.php b/src/Director/BodyTo/BodyToPayoutMethod.php index 3773646..677db09 100644 --- a/src/Director/BodyTo/BodyToPayoutMethod.php +++ b/src/Director/BodyTo/BodyToPayoutMethod.php @@ -4,7 +4,6 @@ namespace PlugAndPay\Sdk\Director\BodyTo; -use DateTimeImmutable; use PlugAndPay\Sdk\Contract\BuildMultipleObjectsInterface; use PlugAndPay\Sdk\Contract\BuildObjectInterface; use PlugAndPay\Sdk\Entity\PayoutMethod; diff --git a/src/Entity/PayoutMethodInternal.php b/src/Entity/PayoutMethodInternal.php index 8e22a43..edc12c6 100644 --- a/src/Entity/PayoutMethodInternal.php +++ b/src/Entity/PayoutMethodInternal.php @@ -4,8 +4,6 @@ namespace PlugAndPay\Sdk\Entity; -use DateTimeImmutable; - /** * @internal */