Created
December 2, 2025 14:55
-
-
Save lucascardial/d725a77325d49ba56425d08f6c71ff16 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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