Files
portal/app/tests/Feature/Billing/AdvertisingCardTopupTest.php
T

130 lines
5.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use App\Models\AdWallet;
use App\Models\BalanceTransaction;
use App\Models\LegalEntity;
use App\Models\PaymentGateway;
use App\Models\SaasTransaction;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Billing\Gateway\CreatePaymentResult;
use App\Services\Billing\Gateway\PaymentGatewayDriver;
use App\Services\Billing\Gateway\WebhookVerifyResult;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;
uses(DatabaseTransactions::class);
uses(SharesSupplierPdo::class);
/**
* Рекламный кошелёк, Часть A — оплата картой (ЮKassa) с credit_target='advertising'
* зачисляет ad_wallets, НЕ tenants.balance_rub (leads). Зеркалит
* AdWalletInvoiceTopupTest (счёт) и существующий PaymentWebhookTest (webhook/settle).
*/
function seedPendingCardTx(Tenant $tenant, PaymentGateway $gw, string $payId, string $creditTarget = 'leads'): SaasTransaction
{
return SaasTransaction::create([
'tenant_id' => $tenant->id, 'type' => 'topup', 'amount_rub' => '500.00',
'gateway_id' => $gw->id, 'gateway_code' => 'yookassa', 'gateway_payment_id' => $payId,
'credit_target' => $creditTarget,
'status' => 'pending', 'created_at' => now(),
]);
}
beforeEach(function () {
$this->tenant = Tenant::factory()->create(['balance_rub' => '0.00']);
$legalEntity = LegalEntity::create([
'code' => 'test_le_adv_'.uniqid(), 'name' => 'ООО Тест', 'legal_form' => 'OOO', 'inn' => '7700000000',
]);
$this->gw = PaymentGateway::create([
'code' => 'yookassa_adv_'.uniqid(), 'name' => 'ЮKassa', 'driver' => 'yookassa',
'legal_entity_id' => $legalEntity->id, 'config' => '', 'is_active' => true,
'accepts_methods' => ['card'], 'min_amount_rub' => '100.00',
]);
});
it('settle с credit_target=advertising зачисляет рекламный кошелёк, не баланс за лиды', function () {
$tx = seedPendingCardTx($this->tenant, $this->gw, 'pay_adv_ok', 'advertising');
$this->mock(PaymentGatewayDriver::class, function ($m) {
$m->shouldReceive('verifyPayment')->once()
->andReturn(new WebhookVerifyResult('pay_adv_ok', 'succeeded', '500.00', 'RUB', 'bank_card'));
});
$resp = $this->postJson('/api/webhook/payment', [
'event' => 'payment.succeeded',
'object' => ['id' => 'pay_adv_ok'],
]);
$resp->assertOk();
$wallet = AdWallet::where('tenant_id', $this->tenant->id)->first();
expect((string) $wallet->balance_rub)->toBe('500.00')
->and($this->tenant->fresh()->balance_rub)->toBe('0.00') // основной баланс НЕ тронут
->and($tx->fresh()->status)->toBe('success');
});
it('settle с credit_target=leads (регресс) зачисляет баланс за лиды как раньше', function () {
$tx = seedPendingCardTx($this->tenant, $this->gw, 'pay_leads_ok', 'leads');
$this->mock(PaymentGatewayDriver::class, function ($m) {
$m->shouldReceive('verifyPayment')->once()
->andReturn(new WebhookVerifyResult('pay_leads_ok', 'succeeded', '500.00', 'RUB', 'bank_card'));
});
$resp = $this->postJson('/api/webhook/payment', [
'event' => 'payment.succeeded',
'object' => ['id' => 'pay_leads_ok'],
]);
$resp->assertOk();
$ledgerId = BalanceTransaction::where('tenant_id', $this->tenant->id)
->where('type', 'topup')->latest('id')->value('id');
expect($this->tenant->fresh()->balance_rub)->toBe('500.00')
->and(AdWallet::where('tenant_id', $this->tenant->id)->exists())->toBeFalse()
->and($tx->fresh()->status)->toBe('success')
->and($tx->fresh()->balance_rub_after)->toBe('500.00')
->and($tx->fresh()->balance_transaction_id)->toBe($ledgerId);
});
it('идемпотентность: повторный settle advertising не пополняет рекламный кошелёк дважды', function () {
seedPendingCardTx($this->tenant, $this->gw, 'pay_adv_dup', 'advertising');
$this->mock(PaymentGatewayDriver::class, function ($m) {
$m->shouldReceive('verifyPayment')->twice()
->andReturn(new WebhookVerifyResult('pay_adv_dup', 'succeeded', '500.00', 'RUB', 'bank_card'));
});
$payload = ['event' => 'payment.succeeded', 'object' => ['id' => 'pay_adv_dup']];
$this->postJson('/api/webhook/payment', $payload)->assertOk();
$this->postJson('/api/webhook/payment', $payload)->assertOk();
$wallet = AdWallet::where('tenant_id', $this->tenant->id)->first();
expect((string) $wallet->balance_rub)->toBe('500.00'); // не 1000
});
it('POST /api/billing/topup с credit_target=advertising при флаге ВКЛ создаёт pending-транзакцию рекламного кошелька', function () {
DB::table('system_settings')->updateOrInsert(
['key' => 'billing_yookassa_enabled'],
['value' => 'true', 'type' => 'bool', 'updated_at' => now()]
);
$user = User::factory()->create(['tenant_id' => $this->tenant->id]);
$this->mock(PaymentGatewayDriver::class, function ($m) {
$m->shouldReceive('createPayment')->once()
->andReturn(new CreatePaymentResult('pay_adv_start', 'https://yoomoney.ru/checkout/pay_adv_start'));
});
$resp = $this->actingAs($user)->postJson('/api/billing/topup', [
'amount_rub' => 500,
'credit_target' => 'advertising',
]);
$resp->assertCreated()->assertJson(['confirmation_url' => 'https://yoomoney.ru/checkout/pay_adv_start']);
$tx = SaasTransaction::where('gateway_payment_id', 'pay_adv_start')->firstOrFail();
expect($tx->credit_target)->toBe('advertising')
->and($tx->status)->toBe('pending');
});