From 5ac24a8151b70d4ff0c99f9413a4e1514f6b1f35 Mon Sep 17 00:00:00 2001 From: Wan Zulkarnain Date: Mon, 18 May 2026 12:56:48 +0800 Subject: [PATCH 1/4] Expand PurchaseBuilder with all missing API fields Add fluent methods for every field defined in the CHIP Collect OpenAPI spec that was previously only accessible via direct model assignment: - Top-level Purchase: clientId, sendReceipt, skipCapture, forceRecurring, reference, issued, due, creatorAgent, platform, tags - PurchaseDetails: notes, debt, subtotalOverride, totalTaxOverride, totalDiscountOverride, totalOverride, requestClientDetails, timezone, dueStrict, emailMessage, shippingOptions, paymentMethodDetails, hasUpsellProducts, singleAttempt, metadata - ClientDetails: clientPersonalCode, clientStreetAddress, clientCountry, clientCity, clientZipCode, clientState, clientShippingStreetAddress, clientShippingCountry, clientShippingCity, clientShippingZipCode, clientShippingState, clientCc, clientBcc, clientLegalName, clientBrandName, clientRegistrationNumber, clientTaxNumber, clientBankAccount, clientBankCode - Product: extend addProduct() with optional discount, taxPercent, category, totalPriceOverride Also add comprehensive test coverage for all new methods. Co-Authored-By: Claude Opus 4.7 --- lib/Builder/PurchaseBuilder.php | 347 +++++++++++++++++++++++++++++++- tests/PurchaseBuilderTest.php | 225 ++++++++++++++++++++- 2 files changed, 569 insertions(+), 3 deletions(-) diff --git a/lib/Builder/PurchaseBuilder.php b/lib/Builder/PurchaseBuilder.php index 4b35fdd..bfda8df 100644 --- a/lib/Builder/PurchaseBuilder.php +++ b/lib/Builder/PurchaseBuilder.php @@ -29,6 +29,13 @@ public function brandId(string $brandId): self return $this; } + public function clientId(?string $clientId): self + { + $this->purchase->client_id = $clientId; + + return $this; + } + public function currency(string $currency): self { $this->purchase->purchase->currency = $currency; @@ -43,6 +50,69 @@ public function language(string $language): self return $this; } + public function sendReceipt(bool $sendReceipt): self + { + $this->purchase->send_receipt = $sendReceipt; + + return $this; + } + + public function skipCapture(bool $skipCapture): self + { + $this->purchase->skip_capture = $skipCapture; + + return $this; + } + + public function forceRecurring(bool $forceRecurring): self + { + $this->purchase->force_recurring = $forceRecurring; + + return $this; + } + + public function reference(string $reference): self + { + $this->purchase->reference = $reference; + + return $this; + } + + public function issued(?string $issued): self + { + $this->purchase->issued = $issued; + + return $this; + } + + public function due(?int $due): self + { + $this->purchase->due = $due; + + return $this; + } + + public function creatorAgent(string $creatorAgent): self + { + $this->purchase->creator_agent = $creatorAgent; + + return $this; + } + + public function platform(string $platform): self + { + $this->purchase->platform = $platform; + + return $this; + } + + public function tags(array $tags): self + { + $this->purchase->tags = $tags; + + return $this; + } + public function successRedirect(string $url): self { $this->purchase->success_redirect = $url; @@ -95,18 +165,293 @@ public function clientFullName(string $fullName): self return $this; } - public function addProduct(string $name, int $price, float|string $quantity = 1.0): self + public function clientPersonalCode(string $personalCode): self + { + $this->ensureClient(); + $this->purchase->client->personal_code = $personalCode; + + return $this; + } + + public function clientStreetAddress(string $streetAddress): self + { + $this->ensureClient(); + $this->purchase->client->street_address = $streetAddress; + + return $this; + } + + public function clientCountry(string $country): self + { + $this->ensureClient(); + $this->purchase->client->country = $country; + + return $this; + } + + public function clientCity(string $city): self + { + $this->ensureClient(); + $this->purchase->client->city = $city; + + return $this; + } + + public function clientZipCode(string $zipCode): self + { + $this->ensureClient(); + $this->purchase->client->zip_code = $zipCode; + + return $this; + } + + public function clientState(?string $state): self + { + $this->ensureClient(); + $this->purchase->client->state = $state; + + return $this; + } + + public function clientShippingStreetAddress(string $shippingStreetAddress): self + { + $this->ensureClient(); + $this->purchase->client->shipping_street_address = $shippingStreetAddress; + + return $this; + } + + public function clientShippingCountry(string $shippingCountry): self + { + $this->ensureClient(); + $this->purchase->client->shipping_country = $shippingCountry; + + return $this; + } + + public function clientShippingCity(string $shippingCity): self + { + $this->ensureClient(); + $this->purchase->client->shipping_city = $shippingCity; + + return $this; + } + + public function clientShippingZipCode(string $shippingZipCode): self + { + $this->ensureClient(); + $this->purchase->client->shipping_zip_code = $shippingZipCode; + + return $this; + } + + public function clientShippingState(?string $shippingState): self + { + $this->ensureClient(); + $this->purchase->client->shipping_state = $shippingState; + + return $this; + } + + public function clientCc(array $cc): self + { + $this->ensureClient(); + $this->purchase->client->cc = $cc; + + return $this; + } + + public function clientBcc(array $bcc): self { + $this->ensureClient(); + $this->purchase->client->bcc = $bcc; + + return $this; + } + + public function clientLegalName(string $legalName): self + { + $this->ensureClient(); + $this->purchase->client->legal_name = $legalName; + + return $this; + } + + public function clientBrandName(string $brandName): self + { + $this->ensureClient(); + $this->purchase->client->brand_name = $brandName; + + return $this; + } + + public function clientRegistrationNumber(string $registrationNumber): self + { + $this->ensureClient(); + $this->purchase->client->registration_number = $registrationNumber; + + return $this; + } + + public function clientTaxNumber(string $taxNumber): self + { + $this->ensureClient(); + $this->purchase->client->tax_number = $taxNumber; + + return $this; + } + + public function clientBankAccount(?string $bankAccount): self + { + $this->ensureClient(); + $this->purchase->client->bank_account = $bankAccount; + + return $this; + } + + public function clientBankCode(?string $bankCode): self + { + $this->ensureClient(); + $this->purchase->client->bank_code = $bankCode; + + return $this; + } + + public function addProduct( + string $name, + int $price, + float|string $quantity = 1.0, + ?int $discount = null, + ?string $taxPercent = null, + ?string $category = null, + ?int $totalPriceOverride = null + ): self { $product = new Product(); $product->name = $name; $product->price = $price; $product->quantity = is_string($quantity) ? $quantity : (string) $quantity; + $product->discount = $discount; + $product->tax_percent = $taxPercent; + $product->category = $category; + $product->total_price_override = $totalPriceOverride; $this->purchase->purchase->products[] = $product; return $this; } + public function notes(string $notes): self + { + $this->purchase->purchase->notes = $notes; + + return $this; + } + + public function debt(int $debt): self + { + $this->purchase->purchase->debt = $debt; + + return $this; + } + + public function subtotalOverride(?int $subtotalOverride): self + { + $this->purchase->purchase->subtotal_override = $subtotalOverride; + + return $this; + } + + public function totalTaxOverride(?int $totalTaxOverride): self + { + $this->purchase->purchase->total_tax_override = $totalTaxOverride; + + return $this; + } + + public function totalDiscountOverride(?int $totalDiscountOverride): self + { + $this->purchase->purchase->total_discount_override = $totalDiscountOverride; + + return $this; + } + + public function totalOverride(?int $totalOverride): self + { + $this->purchase->purchase->total_override = $totalOverride; + + return $this; + } + + public function requestClientDetails(array $requestClientDetails): self + { + $this->purchase->purchase->request_client_details = $requestClientDetails; + + return $this; + } + + public function timezone(string $timezone): self + { + $this->purchase->purchase->timezone = $timezone; + + return $this; + } + + public function dueStrict(bool $dueStrict): self + { + $this->purchase->purchase->due_strict = $dueStrict; + + return $this; + } + + public function emailMessage(string $emailMessage): self + { + $this->purchase->purchase->email_message = $emailMessage; + + return $this; + } + + public function shippingOptions(array $shippingOptions): self + { + $this->purchase->purchase->shipping_options = $shippingOptions; + + return $this; + } + + public function paymentMethodDetails(?object $paymentMethodDetails): self + { + $this->purchase->purchase->payment_method_details = $paymentMethodDetails; + + return $this; + } + + public function hasUpsellProducts(bool $hasUpsellProducts): self + { + $this->purchase->purchase->has_upsell_products = $hasUpsellProducts; + + return $this; + } + + public function singleAttempt(bool $singleAttempt): self + { + $this->purchase->purchase->single_attempt = $singleAttempt; + + return $this; + } + + public function metadata(?object $metadata): self + { + $this->purchase->purchase->metadata = $metadata; + + return $this; + } + + public function paymentMethodWhitelist(array $methods): self + { + $this->purchase->payment_method_whitelist = $methods; + + return $this; + } + public function build(): Purchase { return $this->purchase; diff --git a/tests/PurchaseBuilderTest.php b/tests/PurchaseBuilderTest.php index dcdc8fe..59f364a 100644 --- a/tests/PurchaseBuilderTest.php +++ b/tests/PurchaseBuilderTest.php @@ -34,10 +34,10 @@ public function testBuildsPurchaseWithFluentApi(): void $this->assertCount(2, $purchase->purchase->products); $this->assertEquals('Widget', $purchase->purchase->products[0]->name); $this->assertEquals(5000, $purchase->purchase->products[0]->price); - $this->assertEquals(2.0, $purchase->purchase->products[0]->quantity); + $this->assertEquals('2', $purchase->purchase->products[0]->quantity); $this->assertEquals('Gadget', $purchase->purchase->products[1]->name); $this->assertEquals(3000, $purchase->purchase->products[1]->price); - $this->assertEquals(1.0, $purchase->purchase->products[1]->quantity); + $this->assertEquals('1', $purchase->purchase->products[1]->quantity); } public function testProductsArrayDefaultsToEmpty(): void @@ -48,4 +48,225 @@ public function testProductsArrayDefaultsToEmpty(): void $this->assertEmpty($purchase->purchase->products); } + + public function testTopLevelPurchaseFields(): void + { + $purchase = \Chip\Builder\PurchaseBuilder::create() + ->brandId('brand_123') + ->clientId('client_456') + ->sendReceipt(true) + ->skipCapture(true) + ->forceRecurring(true) + ->reference('INV-001') + ->issued('2024-01-01') + ->due(1704067200) + ->creatorAgent('woocommerce/1.0') + ->platform('web') + ->tags(['tag1', 'tag2']) + ->paymentMethodWhitelist(['visa', 'mastercard']) + ->build(); + + $this->assertEquals('brand_123', $purchase->brand_id); + $this->assertEquals('client_456', $purchase->client_id); + $this->assertTrue($purchase->send_receipt); + $this->assertTrue($purchase->skip_capture); + $this->assertTrue($purchase->force_recurring); + $this->assertEquals('INV-001', $purchase->reference); + $this->assertEquals('2024-01-01', $purchase->issued); + $this->assertEquals(1704067200, $purchase->due); + $this->assertEquals('woocommerce/1.0', $purchase->creator_agent); + $this->assertEquals('web', $purchase->platform); + $this->assertEquals(['tag1', 'tag2'], $purchase->tags); + $this->assertEquals(['visa', 'mastercard'], $purchase->payment_method_whitelist); + } + + public function testNullableTopLevelFields(): void + { + $purchase = \Chip\Builder\PurchaseBuilder::create() + ->brandId('brand_123') + ->clientId(null) + ->issued(null) + ->due(null) + ->build(); + + $this->assertNull($purchase->client_id); + $this->assertNull($purchase->issued); + $this->assertNull($purchase->due); + } + + public function testPurchaseDetailsFields(): void + { + $purchase = \Chip\Builder\PurchaseBuilder::create() + ->brandId('brand_123') + ->currency('MYR') + ->notes('Invoice notes') + ->debt(100) + ->subtotalOverride(5000) + ->totalTaxOverride(300) + ->totalDiscountOverride(200) + ->totalOverride(5100) + ->requestClientDetails(['email', 'full_name']) + ->timezone('Asia/Kuala_Lumpur') + ->dueStrict(true) + ->emailMessage('Thank you for your purchase') + ->shippingOptions([['name' => 'Standard', 'price' => 500]]) + ->paymentMethodDetails((object)['card' => 'visa']) + ->hasUpsellProducts(true) + ->singleAttempt(true) + ->metadata((object)['order_id' => '123']) + ->addProduct('Test', 1000) + ->build(); + + $this->assertEquals('MYR', $purchase->purchase->currency); + $this->assertEquals('Invoice notes', $purchase->purchase->notes); + $this->assertEquals(100, $purchase->purchase->debt); + $this->assertEquals(5000, $purchase->purchase->subtotal_override); + $this->assertEquals(300, $purchase->purchase->total_tax_override); + $this->assertEquals(200, $purchase->purchase->total_discount_override); + $this->assertEquals(5100, $purchase->purchase->total_override); + $this->assertEquals(['email', 'full_name'], $purchase->purchase->request_client_details); + $this->assertEquals('Asia/Kuala_Lumpur', $purchase->purchase->timezone); + $this->assertTrue($purchase->purchase->due_strict); + $this->assertEquals('Thank you for your purchase', $purchase->purchase->email_message); + $this->assertEquals([['name' => 'Standard', 'price' => 500]], $purchase->purchase->shipping_options); + $this->assertEquals((object)['card' => 'visa'], $purchase->purchase->payment_method_details); + $this->assertTrue($purchase->purchase->has_upsell_products); + $this->assertTrue($purchase->purchase->single_attempt); + $this->assertEquals((object)['order_id' => '123'], $purchase->purchase->metadata); + } + + public function testNullablePurchaseDetailsFields(): void + { + $purchase = \Chip\Builder\PurchaseBuilder::create() + ->brandId('brand_123') + ->subtotalOverride(null) + ->totalTaxOverride(null) + ->totalDiscountOverride(null) + ->totalOverride(null) + ->paymentMethodDetails(null) + ->metadata(null) + ->addProduct('Test', 1000) + ->build(); + + $this->assertNull($purchase->purchase->subtotal_override); + $this->assertNull($purchase->purchase->total_tax_override); + $this->assertNull($purchase->purchase->total_discount_override); + $this->assertNull($purchase->purchase->total_override); + $this->assertNull($purchase->purchase->payment_method_details); + $this->assertNull($purchase->purchase->metadata); + } + + public function testClientDetailsFields(): void + { + $purchase = \Chip\Builder\PurchaseBuilder::create() + ->brandId('brand_123') + ->clientEmail('test@example.com') + ->clientPhone('+60123456789') + ->clientFullName('John Doe') + ->clientPersonalCode('PC123') + ->clientStreetAddress('123 Main St') + ->clientCountry('MY') + ->clientCity('Kuala Lumpur') + ->clientZipCode('50000') + ->clientState('KL') + ->clientShippingStreetAddress('456 Shipping Ave') + ->clientShippingCountry('MY') + ->clientShippingCity('Petaling Jaya') + ->clientShippingZipCode('47400') + ->clientShippingState('PJ') + ->clientCc(['cc@example.com']) + ->clientBcc(['bcc@example.com']) + ->clientLegalName('Legal Name Sdn Bhd') + ->clientBrandName('Brand Name') + ->clientRegistrationNumber('REG123') + ->clientTaxNumber('TAX123') + ->clientBankAccount('1234567890') + ->clientBankCode('MBB') + ->build(); + + $client = $purchase->client; + $this->assertInstanceOf(\Chip\Model\ClientDetails::class, $client); + $this->assertEquals('test@example.com', $client->email); + $this->assertEquals('+60123456789', $client->phone); + $this->assertEquals('John Doe', $client->full_name); + $this->assertEquals('PC123', $client->personal_code); + $this->assertEquals('123 Main St', $client->street_address); + $this->assertEquals('MY', $client->country); + $this->assertEquals('Kuala Lumpur', $client->city); + $this->assertEquals('50000', $client->zip_code); + $this->assertEquals('KL', $client->state); + $this->assertEquals('456 Shipping Ave', $client->shipping_street_address); + $this->assertEquals('MY', $client->shipping_country); + $this->assertEquals('Petaling Jaya', $client->shipping_city); + $this->assertEquals('47400', $client->shipping_zip_code); + $this->assertEquals('PJ', $client->shipping_state); + $this->assertEquals(['cc@example.com'], $client->cc); + $this->assertEquals(['bcc@example.com'], $client->bcc); + $this->assertEquals('Legal Name Sdn Bhd', $client->legal_name); + $this->assertEquals('Brand Name', $client->brand_name); + $this->assertEquals('REG123', $client->registration_number); + $this->assertEquals('TAX123', $client->tax_number); + $this->assertEquals('1234567890', $client->bank_account); + $this->assertEquals('MBB', $client->bank_code); + } + + public function testNullableClientDetailsFields(): void + { + $purchase = \Chip\Builder\PurchaseBuilder::create() + ->brandId('brand_123') + ->clientEmail('test@example.com') + ->clientState(null) + ->clientShippingState(null) + ->clientBankAccount(null) + ->clientBankCode(null) + ->build(); + + $this->assertNull($purchase->client->state); + $this->assertNull($purchase->client->shipping_state); + $this->assertNull($purchase->client->bank_account); + $this->assertNull($purchase->client->bank_code); + } + + public function testProductWithAllFields(): void + { + $purchase = \Chip\Builder\PurchaseBuilder::create() + ->brandId('brand_123') + ->addProduct('Widget', 5000, 2, 100, '6.00', 'Electronics', 4900) + ->build(); + + $product = $purchase->purchase->products[0]; + $this->assertEquals('Widget', $product->name); + $this->assertEquals(5000, $product->price); + $this->assertEquals('2', $product->quantity); + $this->assertEquals(100, $product->discount); + $this->assertEquals('6.00', $product->tax_percent); + $this->assertEquals('Electronics', $product->category); + $this->assertEquals(4900, $product->total_price_override); + } + + public function testProductWithNullOptionalFields(): void + { + $purchase = \Chip\Builder\PurchaseBuilder::create() + ->brandId('brand_123') + ->addProduct('Widget', 5000) + ->build(); + + $product = $purchase->purchase->products[0]; + $this->assertNull($product->discount); + $this->assertNull($product->tax_percent); + $this->assertNull($product->category); + $this->assertNull($product->total_price_override); + } + + public function testClientIsCreatedOnlyOnce(): void + { + $purchase = \Chip\Builder\PurchaseBuilder::create() + ->brandId('brand_123') + ->clientEmail('test@example.com') + ->clientFullName('Test User') + ->clientPhone('+60123456789') + ->build(); + + $this->assertSame($purchase->client, $purchase->client); + } } From 8badbaa9df3aa0e5447c2179797c8b9436bb7d62 Mon Sep 17 00:00:00 2001 From: Wan Zulkarnain Date: Mon, 18 May 2026 13:01:01 +0800 Subject: [PATCH 2/4] Fix PHPStan errors and add CHANGELOG entry for PurchaseBuilder expansion - Remove nullable types from builder methods where model properties are typed as non-nullable (clientId, due, subtotalOverride, totalTaxOverride, totalDiscountOverride, totalOverride) - Add array value type annotations to array-typed parameters (tags, clientCc, clientBcc, requestClientDetails, shippingOptions, paymentMethodWhitelist) - Remove assertNull tests for properties that cannot be null per model types - Add CHANGELOG.md entry documenting all new builder methods Co-Authored-By: Claude Opus 4.7 --- CHANGELOG.md | 8 ++++++++ lib/Builder/PurchaseBuilder.php | 30 ++++++++++++++++++++++------ tests/PurchaseBuilderTest.php | 35 --------------------------------- 3 files changed, 32 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70a266e..5e762b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Expand `PurchaseBuilder` fluent API with all missing CHIP Collect fields +- Add top-level Purchase builder methods: `clientId`, `sendReceipt`, `skipCapture`, `forceRecurring`, `reference`, `issued`, `due`, `creatorAgent`, `platform`, `tags` +- Add `PurchaseDetails` builder methods: `notes`, `debt`, `subtotalOverride`, `totalTaxOverride`, `totalDiscountOverride`, `totalOverride`, `requestClientDetails`, `timezone`, `dueStrict`, `emailMessage`, `shippingOptions`, `paymentMethodDetails`, `hasUpsellProducts`, `singleAttempt`, `metadata` +- Add `ClientDetails` builder methods: `clientPersonalCode`, `clientStreetAddress`, `clientCountry`, `clientCity`, `clientZipCode`, `clientState`, `clientShippingStreetAddress`, `clientShippingCountry`, `clientShippingCity`, `clientShippingZipCode`, `clientShippingState`, `clientCc`, `clientBcc`, `clientLegalName`, `clientBrandName`, `clientRegistrationNumber`, `clientTaxNumber`, `clientBankAccount`, `clientBankCode` +- Extend `addProduct()` with optional `$discount`, `$taxPercent`, `$category`, `$totalPriceOverride` parameters + ## [2.0.1] - 2026-05-14 ### Fixed diff --git a/lib/Builder/PurchaseBuilder.php b/lib/Builder/PurchaseBuilder.php index bfda8df..f8689a9 100644 --- a/lib/Builder/PurchaseBuilder.php +++ b/lib/Builder/PurchaseBuilder.php @@ -29,7 +29,7 @@ public function brandId(string $brandId): self return $this; } - public function clientId(?string $clientId): self + public function clientId(string $clientId): self { $this->purchase->client_id = $clientId; @@ -85,7 +85,7 @@ public function issued(?string $issued): self return $this; } - public function due(?int $due): self + public function due(int $due): self { $this->purchase->due = $due; @@ -106,6 +106,9 @@ public function platform(string $platform): self return $this; } + /** + * @param string[] $tags + */ public function tags(array $tags): self { $this->purchase->tags = $tags; @@ -253,6 +256,9 @@ public function clientShippingState(?string $shippingState): self return $this; } + /** + * @param string[] $cc + */ public function clientCc(array $cc): self { $this->ensureClient(); @@ -261,6 +267,9 @@ public function clientCc(array $cc): self return $this; } + /** + * @param string[] $bcc + */ public function clientBcc(array $bcc): self { $this->ensureClient(); @@ -354,34 +363,37 @@ public function debt(int $debt): self return $this; } - public function subtotalOverride(?int $subtotalOverride): self + public function subtotalOverride(int $subtotalOverride): self { $this->purchase->purchase->subtotal_override = $subtotalOverride; return $this; } - public function totalTaxOverride(?int $totalTaxOverride): self + public function totalTaxOverride(int $totalTaxOverride): self { $this->purchase->purchase->total_tax_override = $totalTaxOverride; return $this; } - public function totalDiscountOverride(?int $totalDiscountOverride): self + public function totalDiscountOverride(int $totalDiscountOverride): self { $this->purchase->purchase->total_discount_override = $totalDiscountOverride; return $this; } - public function totalOverride(?int $totalOverride): self + public function totalOverride(int $totalOverride): self { $this->purchase->purchase->total_override = $totalOverride; return $this; } + /** + * @param string[] $requestClientDetails + */ public function requestClientDetails(array $requestClientDetails): self { $this->purchase->purchase->request_client_details = $requestClientDetails; @@ -410,6 +422,9 @@ public function emailMessage(string $emailMessage): self return $this; } + /** + * @param array $shippingOptions + */ public function shippingOptions(array $shippingOptions): self { $this->purchase->purchase->shipping_options = $shippingOptions; @@ -445,6 +460,9 @@ public function metadata(?object $metadata): self return $this; } + /** + * @param string[] $methods + */ public function paymentMethodWhitelist(array $methods): self { $this->purchase->payment_method_whitelist = $methods; diff --git a/tests/PurchaseBuilderTest.php b/tests/PurchaseBuilderTest.php index 59f364a..f8d68e9 100644 --- a/tests/PurchaseBuilderTest.php +++ b/tests/PurchaseBuilderTest.php @@ -80,20 +80,6 @@ public function testTopLevelPurchaseFields(): void $this->assertEquals(['visa', 'mastercard'], $purchase->payment_method_whitelist); } - public function testNullableTopLevelFields(): void - { - $purchase = \Chip\Builder\PurchaseBuilder::create() - ->brandId('brand_123') - ->clientId(null) - ->issued(null) - ->due(null) - ->build(); - - $this->assertNull($purchase->client_id); - $this->assertNull($purchase->issued); - $this->assertNull($purchase->due); - } - public function testPurchaseDetailsFields(): void { $purchase = \Chip\Builder\PurchaseBuilder::create() @@ -135,27 +121,6 @@ public function testPurchaseDetailsFields(): void $this->assertEquals((object)['order_id' => '123'], $purchase->purchase->metadata); } - public function testNullablePurchaseDetailsFields(): void - { - $purchase = \Chip\Builder\PurchaseBuilder::create() - ->brandId('brand_123') - ->subtotalOverride(null) - ->totalTaxOverride(null) - ->totalDiscountOverride(null) - ->totalOverride(null) - ->paymentMethodDetails(null) - ->metadata(null) - ->addProduct('Test', 1000) - ->build(); - - $this->assertNull($purchase->purchase->subtotal_override); - $this->assertNull($purchase->purchase->total_tax_override); - $this->assertNull($purchase->purchase->total_discount_override); - $this->assertNull($purchase->purchase->total_override); - $this->assertNull($purchase->purchase->payment_method_details); - $this->assertNull($purchase->purchase->metadata); - } - public function testClientDetailsFields(): void { $purchase = \Chip\Builder\PurchaseBuilder::create() From 8bd711ec0743066cfc9bc17eac585ffdef628f95 Mon Sep 17 00:00:00 2001 From: Wan Zulkarnain Date: Mon, 18 May 2026 13:01:57 +0800 Subject: [PATCH 3/4] Update CHANGELOG for v2.0.2 release Co-Authored-By: Claude Opus 4.7 --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e762b8..74220cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.0.2] - 2026-05-18 + ### Added - Expand `PurchaseBuilder` fluent API with all missing CHIP Collect fields @@ -163,7 +165,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `verify()` static method for webhook signature verification using RSA-SHA256 - Basic test suite with Guzzle `MockHandler` -[Unreleased]: https://github.com/CHIPAsia/chip-php-sdk/compare/v2.0.1...HEAD +[Unreleased]: https://github.com/CHIPAsia/chip-php-sdk/compare/v2.0.2...HEAD +[2.0.2]: https://github.com/CHIPAsia/chip-php-sdk/compare/v2.0.1...v2.0.2 [2.0.1]: https://github.com/CHIPAsia/chip-php-sdk/compare/v2.0.0...v2.0.1 [2.0.0]: https://github.com/CHIPAsia/chip-php-sdk/compare/v1.2.1...v2.0.0 [1.2.1]: https://github.com/CHIPAsia/chip-php-sdk/compare/v1.2.0...v1.2.1 From 7f8782c789fb59e62a97c59e81652e894a043a39 Mon Sep 17 00:00:00 2001 From: Wan Zulkarnain Date: Mon, 18 May 2026 13:05:30 +0800 Subject: [PATCH 4/4] Add paymentMethodWhitelist example to purchase_builder.php Co-Authored-By: Claude Opus 4.7 --- examples/api/purchase_builder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/api/purchase_builder.php b/examples/api/purchase_builder.php index 9c30799..835f0c7 100644 --- a/examples/api/purchase_builder.php +++ b/examples/api/purchase_builder.php @@ -14,6 +14,7 @@ ->clientEmail('test@example.com') ->clientFullName('John Doe') ->addProduct('Test Product', 100) + ->paymentMethodWhitelist(['visa', 'mastercard']) ->successRedirect($config['basedUrl'] . '/api/redirect.php?success=1') ->failureRedirect($config['basedUrl'] . '/api/redirect.php?success=0') ->successCallback($config['basedUrl'] . '/api/callback.php')