diff --git a/src/Coupon/CouponResource.php b/src/Coupon/CouponResource.php index 5a721b3..a38327c 100644 --- a/src/Coupon/CouponResource.php +++ b/src/Coupon/CouponResource.php @@ -28,7 +28,7 @@ public function create(CreateCouponRequest $request): CouponResponse { try { $response = $this->client->post(sprintf('%s/create', self::BASE_PATH), [ - 'json' => $request->jsonSerialize(), + 'json' => $request->toArray(), ]); $responsePayload = json_decode( diff --git a/src/Coupon/Http/Request/CreateCouponRequest.php b/src/Coupon/Http/Request/CreateCouponRequest.php index 1944872..f765943 100644 --- a/src/Coupon/Http/Request/CreateCouponRequest.php +++ b/src/Coupon/Http/Request/CreateCouponRequest.php @@ -6,9 +6,8 @@ use Basement\AbacatePay\Coupon\Enums\CouponDiscountKindEnum; use Basement\AbacatePay\Coupon\Http\Builder\CreateCouponRequestBuilder; -use JsonSerializable; -final readonly class CreateCouponRequest implements JsonSerializable +final readonly class CreateCouponRequest { public function __construct( public string $code, @@ -36,7 +35,7 @@ public static function builder(): CreateCouponRequestBuilder return new CreateCouponRequestBuilder; } - public function jsonSerialize(): array + public function toArray(): array { return [ 'code' => $this->code, diff --git a/src/Pix/Entities/PixQrCodeEntity.php b/src/Pix/Entities/PixQrCodeEntity.php index e94cae6..4b84185 100644 --- a/src/Pix/Entities/PixQrCodeEntity.php +++ b/src/Pix/Entities/PixQrCodeEntity.php @@ -4,15 +4,11 @@ namespace Basement\AbacatePay\Pix\Entities; -use Basement\AbacatePay\Billing\Enum\BillingMethodEnum; use Basement\AbacatePay\Billing\Enum\BillingStatusEnum; use JsonSerializable; final readonly class PixQrCodeEntity implements JsonSerializable { - /** - * @param BillingMethodEnum[] $methods - */ public function __construct( public string $id, public int $amount, diff --git a/src/Pix/Http/Builder/CreatePixQrCodeRequestBuilder.php b/src/Pix/Http/Builder/CreatePixQrCodeRequestBuilder.php index 8200060..d247fc6 100644 --- a/src/Pix/Http/Builder/CreatePixQrCodeRequestBuilder.php +++ b/src/Pix/Http/Builder/CreatePixQrCodeRequestBuilder.php @@ -28,28 +28,28 @@ public function amount(int $amount): self return $this; } - public function expiresIn(int $seconds): self + public function expiresIn(?int $seconds): self { $this->expiresIn = $seconds; return $this; } - public function description(string $description): self + public function description(?string $description): self { $this->description = $description; return $this; } - public function customer(PixCustomerRequest $customer): self + public function customer(?PixCustomerRequest $customer): self { $this->customer = $customer; return $this; } - public function metadata(PixMetadataRequest $metadata): self + public function metadata(?PixMetadataRequest $metadata): self { $this->metadata = $metadata; @@ -61,36 +61,14 @@ public function metadata(PixMetadataRequest $metadata): self */ public function build(): CreatePixQrCodeRequest { - $errors = []; - if ($this->amount === null) { - $errors[] = 'amount'; - } - - if ($this->expiresIn === null) { - $errors[] = 'expiresIn'; - } - - if ($this->description === null) { - $errors[] = 'description'; - } - - if (! $this->customer instanceof PixCustomerRequest) { - $errors[] = 'customer'; - } - - if (! $this->metadata instanceof PixMetadataRequest) { - $errors[] = 'metadata'; - } - - if ($errors !== []) { - throw AbacatePayException::missingRequiredFields($errors); + throw AbacatePayException::missingRequiredFields(['amount']); } return new CreatePixQrCodeRequest( amount: $this->amount, - expiresIn: $this->expiresIn, - description: $this->description, + expiresIn: $this->expiresIn ?? null, + description: $this->description ?? null, customer: $this->customer, metadata: $this->metadata, ); diff --git a/src/Pix/Http/Request/CreatePixQrCodeRequest.php b/src/Pix/Http/Request/CreatePixQrCodeRequest.php index 96b3754..15b5f13 100644 --- a/src/Pix/Http/Request/CreatePixQrCodeRequest.php +++ b/src/Pix/Http/Request/CreatePixQrCodeRequest.php @@ -5,16 +5,15 @@ namespace Basement\AbacatePay\Pix\Http\Request; use Basement\AbacatePay\Pix\Http\Builder\CreatePixQrCodeRequestBuilder; -use JsonSerializable; -final readonly class CreatePixQrCodeRequest implements JsonSerializable +final readonly class CreatePixQrCodeRequest { public function __construct( public int $amount, - public int $expiresIn, - public string $description, - public PixCustomerRequest $customer, - public PixMetadataRequest $metadata, + public ?int $expiresIn, + public ?string $description, + public ?PixCustomerRequest $customer, + public ?PixMetadataRequest $metadata, ) {} public static function builder(): CreatePixQrCodeRequestBuilder @@ -26,21 +25,33 @@ public static function make(array $data): self { return new self( amount: $data['amount'], - expiresIn: $data['expiresIn'], - description: $data['description'], - customer: $data['customer'], - metadata: $data['metadata'], + expiresIn: $data['expiresIn'] ?? null, + description: $data['description'] ?? null, + customer: $data['customer'] ?? null, + metadata: $data['metadata'] ?? null, ); } - public function jsonSerialize(): array + public function toArray(): array { - return [ - 'amount' => $this->amount, - 'expiresIn' => $this->expiresIn, - 'description' => $this->description, - 'customer' => $this->customer->toArray(), - 'metadata' => $this->metadata->toArray(), - ]; + $data = ['amount' => $this->amount]; + + if ($this->expiresIn !== null) { + $data['expiresIn'] = $this->expiresIn; + } + + if ($this->description !== null) { + $data['description'] = $this->description; + } + + if ($this->customer instanceof PixCustomerRequest) { + $data['customer'] = $this->customer->toArray(); + } + + if ($this->metadata instanceof PixMetadataRequest) { + $data['metadata'] = $this->metadata->toArray(); + } + + return $data; } } diff --git a/src/Pix/PixResource.php b/src/Pix/PixResource.php index 25f2783..201cb3b 100644 --- a/src/Pix/PixResource.php +++ b/src/Pix/PixResource.php @@ -29,7 +29,7 @@ public function createQrCode(CreatePixQrCodeRequest $request): CreatePixQrCodeRe { try { $response = $this->client->post(sprintf('%s/create', self::BASE_PATH), [ - 'json' => $request->jsonSerialize(), + 'json' => $request->toArray(), ]); $responsePayload = json_decode( diff --git a/tests/Feature/Billing/BillingFeatureTest.php b/tests/Feature/Billing/BillingFeatureTest.php index 7cb08d2..bad8924 100644 --- a/tests/Feature/Billing/BillingFeatureTest.php +++ b/tests/Feature/Billing/BillingFeatureTest.php @@ -111,10 +111,10 @@ products: [], return_url: 'https://example.com/return', completion_url: 'https://example.com/complete', - customerId: null, - customer: null, allow_coupons: false, coupons: [], + customerId: null, + customer: null, externalId: null ); @@ -140,10 +140,10 @@ products: [], return_url: 'https://example.com/return', completion_url: 'https://example.com/complete', - customerId: null, - customer: null, allow_coupons: false, coupons: [], + customerId: null, + customer: null, externalId: null ); diff --git a/tests/Feature/Pix/PixResourceTest.php b/tests/Feature/Pix/PixResourceTest.php index 07481c3..f3b5db0 100644 --- a/tests/Feature/Pix/PixResourceTest.php +++ b/tests/Feature/Pix/PixResourceTest.php @@ -16,20 +16,24 @@ use GuzzleHttp\Psr7\Response; beforeEach(function () { - $this->requestDto = CreatePixQrCodeRequest::make([ - 'amount' => 100, - 'expiresIn' => 10, - 'description' => 'abacate description', - 'customer' => PixCustomerRequest::make([ - 'name' => 'customer name', - 'cellphone' => '11982516627', - 'email' => 'joe@doe.com', - 'taxId' => '123.456.789-01', - ]), - 'metadata' => PixMetadataRequest::make([ - 'externalId' => 'customer-id-123', - ]), - ]); + $this->requestDto = CreatePixQrCodeRequest::builder() + ->amount(100) + ->expiresIn(10) + ->description('abacate description') + ->customer( + PixCustomerRequest::builder() + ->name('customer name') + ->cellphone('11982516627') + ->email('joe@doe.com') + ->taxId('123.456.789-01') + ->build() + ) + ->metadata( + PixMetadataRequest::builder() + ->externalId('customer-id-123') + ->build() + ) + ->build(); }); it('should be able to create a Qr for pix', function (): void { @@ -56,9 +60,7 @@ $resource = new PixResource(client: $client); - $response = $resource->createQrCode( - $this->requestDto, - ); + $response = $resource->createQrCode($this->requestDto); expect($response)->toBeInstanceOf(CreatePixQrCodeResponse::class) ->and($response->data->id)->toBe('pix_char_123456') @@ -74,7 +76,6 @@ }); it('should throw exception when anything goes wrong', function () { - $handler = new MockHandler([ new ClientException( 'Unauthorized', diff --git a/tests/Unit/Billing/CreateBillingRequestBuilderTest.php b/tests/Unit/Billing/CreateBillingRequestBuilderTest.php new file mode 100644 index 0000000..8133b33 --- /dev/null +++ b/tests/Unit/Billing/CreateBillingRequestBuilderTest.php @@ -0,0 +1,102 @@ +product = ProductRequest::builder() + ->externalId('prod-123') + ->name('Curso PHP') + ->description('Curso completo de PHP moderno') + ->quantity(1) + ->price(15000) + ->build(); + + $this->customer = CustomerRequest::builder() + ->id('cust_abc123') + ->name('João Silva') + ->cellphone('+5511999999999') + ->email('joao@email.com') + ->taxId('12345678900') + ->build(); +}); + +it('should build a valid CreateBillingRequest', function () { + $request = CreateBillingRequest::oneTime() + ->pix() + ->returnUrl('https://retorno.exemplo.com') + ->completionUrl('https://finalizacao.exemplo.com') + ->addProduct($this->product) + ->forCustomer($this->customer) + ->build(); + + expect($request) + ->toBeInstanceOf(CreateBillingRequest::class) + ->and($request->frequency)->toBe(BillingFrequencyEnum::OneTime) + ->and($request->methods)->toBe([BillingMethodEnum::Pix]) + ->and($request->products)->toHaveCount(1) + ->and($request->customer->id)->toBe('cust_abc123') + ->and($request->allow_coupons)->toBeFalse() + ->and($request->coupons)->toBe([]); +}); + +it('should throw AbacatePayException when required fields are missing', function () { + expect(fn () => CreateBillingRequest::builder()->build()) + ->toThrow(AbacatePayException::class, 'Missing required fields'); +}); + +it('should throw AbacatePayException when both customer and customerId are set', function () { + $builder = CreateBillingRequest::oneTime() + ->pix() + ->returnUrl('https://retorno.exemplo.com') + ->completionUrl('https://finalizacao.exemplo.com') + ->addProduct($this->product) + ->forCustomer($this->customer) + ->forCustomerId('cust_conflict'); + + expect(fn () => $builder->build()) + ->toThrow(InvalidArgumentException::class); +}); + +it('should allow optional fields to be omitted', function () { + $request = CreateBillingRequest::oneTime() + ->pix() + ->returnUrl('https://retorno.exemplo.com') + ->completionUrl('https://finalizacao.exemplo.com') + ->addProduct($this->product) + ->build(); + + expect($request) + ->toBeInstanceOf(CreateBillingRequest::class) + ->and($request->externalId)->toBeNull() + ->and($request->customerId)->toBeNull() + ->and($request->customer)->toBeNull() + ->and($request->coupons)->toBe([]) + ->and($request->allow_coupons)->toBeFalse(); +}); + +it('should accept multiple methods and products', function () { + $product2 = ProductRequest::builder() + ->externalId('prod-456') + ->name('Curso Laravel') + ->description('Aprenda Laravel com exemplos práticos') + ->quantity(1) + ->price(18000) + ->build(); + + $request = CreateBillingRequest::multipleTimes() + ->methods(BillingMethodEnum::Card, BillingMethodEnum::Pix) + ->returnUrl('https://retorno.exemplo.com') + ->completionUrl('https://finalizacao.exemplo.com') + ->products($this->product, $product2) + ->build(); + + expect($request->methods)->toBe([BillingMethodEnum::Card, BillingMethodEnum::Pix]) + ->and($request->products)->toHaveCount(2); +}); diff --git a/tests/Unit/Pix/CreatePixQrCodeRequestBuilderTest.php b/tests/Unit/Pix/CreatePixQrCodeRequestBuilderTest.php new file mode 100644 index 0000000..4f7c780 --- /dev/null +++ b/tests/Unit/Pix/CreatePixQrCodeRequestBuilderTest.php @@ -0,0 +1,81 @@ +name('Maria') + ->cellphone('+5511999999999') + ->email('maria@email.com') + ->taxId('12345678900') + ->build(); + + $metadata = PixMetadataRequest::builder() + ->externalId('order-123') + ->build(); + + $request = CreatePixQrCodeRequest::builder() + ->amount(10000) + ->expiresIn(3600) + ->description('Pagamento de assinatura') + ->customer($customer) + ->metadata($metadata) + ->build(); + + expect($request) + ->toBeInstanceOf(CreatePixQrCodeRequest::class) + ->and($request->amount)->toBe(10000) + ->and($request->description)->toBe('Pagamento de assinatura') + ->and($request->customer->name)->toBe('Maria') + ->and($request->metadata->externalId)->toBe('order-123') + ->and($customer->toArray())->toBe([ + 'name' => 'Maria', + 'cellphone' => '+5511999999999', + 'email' => 'maria@email.com', + 'taxId' => '12345678900', + ]) + ->and($metadata->toArray())->toBe([ + 'externalId' => 'order-123', + ]) + ->and($request->toArray())->toBe([ + 'amount' => 10000, + 'expiresIn' => 3600, + 'description' => 'Pagamento de assinatura', + 'customer' => [ + 'name' => 'Maria', + 'cellphone' => '+5511999999999', + 'email' => 'maria@email.com', + 'taxId' => '12345678900', + ], + 'metadata' => [ + 'externalId' => 'order-123', + ], + ]); +}); + +it('should allow creating a QR code with only required fields', function (): void { + $request = CreatePixQrCodeRequest::builder() + ->amount(250) + ->build(); + + expect($request) + ->toBeInstanceOf(CreatePixQrCodeRequest::class) + ->and($request->amount)->toBe(250) + ->and($request->expiresIn)->toBeNull() + ->and($request->description)->toBeNull() + ->and($request->customer)->toBeNull() + ->and($request->metadata)->toBeNull(); + + $serialized = $request->toArray(); + + expect($serialized)->toBe(['amount' => 250]); +}); + +it('throws AbacatePayException when missing required fields', function () { + CreatePixQrCodeRequest::builder()->build(); +})->throws(AbacatePayException::class, 'Missing required fields');