Skip to content

Instantly share code, notes, and snippets.

@lucascardial
Created December 2, 2025 14:55
Show Gist options
  • Select an option

  • Save lucascardial/d725a77325d49ba56425d08f6c71ff16 to your computer and use it in GitHub Desktop.

Select an option

Save lucascardial/d725a77325d49ba56425d08f6c71ff16 to your computer and use it in GitHub Desktop.
<?php
declare(strict_types=1);
use App\Http\Middleware\GoogleRecaptchaMiddleware;
use App\Models\Order;
use App\Models\Organization;
use App\Models\OrganizationCheckoutFee;
use App\Models\OrganizationInstallmentFee;
use App\Models\Product;
use App\Models\Transaction;
use App\Support\Enums\PaymentMethod;
use Illuminate\Database\Eloquent\Factories\Sequence;
use function Pest\Laravel\postJson;
use function Pest\Laravel\withoutMiddleware;
use function PHPUnit\Framework\assertEqualsWithDelta;
use App\Support\Enums\PaymentMethod;
dataset('pix, boleto, credit_card 1 to 12 installments', [
[ PaymentMethod::PIX, 1],
[ PaymentMethod::BOLETO, 1],
...array_map(
fn (int $installment) => [PaymentMethod::CREDIT_CARD, $installment],
range(1, 12)
)
]);
describe('[AnatomyOfPaymentTest]', function () {
beforeEach(function () {
Event::fake();
Queue::fake();
// desabilitamos o middleware de recaptcha
withoutMiddleware(GoogleRecaptchaMiddleware::class);
/**
* Agora temos total Controle sobre o arranjo da organização.
*
* O Factory de Organization foi atualizado para fabricar uma organização
* completa, pronta para transacionar.
*
* Basta invocar os métodos completed(), withBankAccount(),
* withRecipientAccounts().
*
* O configure também foi atualizado para permitir sobrescrever
* as taxas de checkout (OrganizationCheckoutFee), dando mais
* controle no cenário de teste.
*/
$this->organization = Organization::factory()
->completed()
->withBankAccount()
->withRecipientAccounts()
->has(OrganizationInstallmentFee::factory(), 'installmentFee') // criará taxas de parcelamento customizada para a org
->has(OrganizationCheckoutFee::factory(), 'checkoutFees')
->create();
/**
* Crie quantos produtos desejar. Não há mais limitação de apenas dois produtos.
*/
$this->products = Product::factory()
->state(new Sequence(
['price' => 300_00],
['price' => 600_00],
['price' => 100_00],
))
->for($this->organization)
->withPixPaymentRule()
->withBoletoPaymentRule()
->withCreditCardPaymentRule(installments: 12)
->count(3)
->create();
});
/**
* O dataset [pix, boleto, credit_card 1 to 12 installments] fornece
* todos os métodos de pagamento, além do parcelamento no
* cartão de crédito de 1 a 12 parcelas. Dessa forma o teste será
* executado considerando todos os cenários de cobrança nos métodos
* atualmente disponíveis.
*/
it('should process purchase', function (PaymentMethod $paymentMethod, int $installment) {
/**
* Começamos recuperando a organização e suas taxas de checkout,
* para apoiar os calculos
*/
/** @var Organization $organization */
$organization = $this->organization;
$fixedFee = $organization->checkoutFees->value("{$paymentMethod->value}_amount_fee");
$percentualFee = $organization->checkoutFees->value("{$paymentMethod->value}_percentage_fee") / 100;
/**
* Devemos iniciar nosso teste definindo as variáveis que sinalizarão
* o que é esperado (expects) nas asserções:
*/
/**
* No arranjo do beforeEach, criamos 3 produtos e sabemos que o
* total é 1000_00 -> 300_00 + 600_00 + 100_00
*/
$expectedOriginalAmount = 1000_00;
/**
* O dataset gerará compras parceladas em até 12x, por isso devemos
* considerar o incremento das taxas de parcelamento:
*/
$installmentFee = $installment == 1 ? 0 : $organization->installmentFee->installments->$installment;
$expectedInstallmentFeeAmount = (int) bcmul((string) $expectedOriginalAmount, (string) $installmentFee, 0);
$expectedTotalAmount = $expectedOriginalAmount + $expectedInstallmentFeeAmount;
/**
* O valor da organização é o total original do pedido - taxas:
*/
$expectedOrganizationSplitAmount = intval($expectedOriginalAmount - $fixedFee - ($expectedOriginalAmount * $percentualFee));
/**
* O valor esperado para o checkout é sempre "o que sobra" após o sistema
* endereçar os valores de cada recebedor. Isso funciona bem pois o primeiro
* operador do split (SellerOrganizationSplitRule) já desconta as taxas de processamento,
* deixando que "reste" para o Checkout este valor e valores de juros de parcelamento se existirem.
*/
$expectedCheckoutSplitAmount = $expectedTotalAmount - $expectedOrganizationSplitAmount;
/**
* Com os expects definidos, podemos construir nosso payload para a rota v1/checkout/orders:
*/
$purchasePayload = preparePurchasePayload(
products: $this->products,
paymentMethod: $paymentMethod,
installments: $installment,
coupon: null
);
/**
* Definimos o HTTP Fake para simular as respostas do PSP.
*/
Http::fake([
'https://api.psp.io/v1/charges' => Http::response(createChargeOnPspResponseFactory(
chargeAmount: $expectedTotalAmount,
paymentMethod: $paymentMethod,
installments: $installment,
)),
'https://api.psp.io/v1/customers?force=true' => Http::response(createCustomerOnPspResponseFactory()),
]);
$response = postJson(route('checkout.orders.store'), $purchasePayload)
->json();
/** @var Order $order */
$order = Order::findOrFail($response['order']['id']);
$transaction = Transaction::first();
/**
* Devido a tolerancia de diferença de 1 centavo para mais ou para menos
* aceita nos nossos cálculos, não utilizaremos assertDatabaseHas(),
* que espera valores exatos.
*
* O assertEqualsWithDelta compara se dois valores são iguais
* considerando a variação delta permitida.
*/
assertEqualsWithDelta($expectedTotalAmount, $order->total_amount, 1);
assertEqualsWithDelta($expectedOriginalAmount, $order->original_amount, 1);
assertEqualsWithDelta($expectedOrganizationSplitAmount, $transaction->organizationSplit->split_amount, 1);
assertEqualsWithDelta($expectedCheckoutSplitAmount, $transaction->checkoutSplit->split_amount, 1);
})->with('pix, boleto, credit_card 1 to 12 installments');
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment