Skip to content
2 changes: 1 addition & 1 deletion src/Coupon/CouponResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
5 changes: 2 additions & 3 deletions src/Coupon/Http/Request/CreateCouponRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -36,7 +35,7 @@ public static function builder(): CreateCouponRequestBuilder
return new CreateCouponRequestBuilder;
}

public function jsonSerialize(): array
public function toArray(): array
{
return [
'code' => $this->code,
Expand Down
4 changes: 0 additions & 4 deletions src/Pix/Entities/PixQrCodeEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
36 changes: 7 additions & 29 deletions src/Pix/Http/Builder/CreatePixQrCodeRequestBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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,
);
Expand Down
47 changes: 29 additions & 18 deletions src/Pix/Http/Request/CreatePixQrCodeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
}
2 changes: 1 addition & 1 deletion src/Pix/PixResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 4 additions & 4 deletions tests/Feature/Billing/BillingFeatureTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
);

Expand All @@ -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
);

Expand Down
37 changes: 19 additions & 18 deletions tests/Feature/Pix/PixResourceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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')
Expand All @@ -74,7 +76,6 @@
});

it('should throw exception when anything goes wrong', function () {

$handler = new MockHandler([
new ClientException(
'Unauthorized',
Expand Down
102 changes: 102 additions & 0 deletions tests/Unit/Billing/CreateBillingRequestBuilderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

use Basement\AbacatePay\Billing\Enum\BillingFrequencyEnum;
use Basement\AbacatePay\Billing\Enum\BillingMethodEnum;
use Basement\AbacatePay\Billing\Http\Request\CreateBillingRequest;
use Basement\AbacatePay\Billing\Http\Request\ProductRequest;
use Basement\AbacatePay\Customer\Http\Request\CustomerRequest;
use Basement\AbacatePay\Exception\AbacatePayException;

beforeEach(function () {
$this->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);
});
Loading