feat(telepagos): enhance webhook handling
This commit is contained in:
@@ -39,6 +39,20 @@ class TelepagosWebhookService
|
|||||||
$amount = $this->normalizeAmount($details['data']['amount'] ?? $details['amount'] ?? 0);
|
$amount = $this->normalizeAmount($details['data']['amount'] ?? $details['amount'] ?? 0);
|
||||||
$operationId = $details['data']['operation_id'] ?? $details['operation_id'] ?? null;
|
$operationId = $details['data']['operation_id'] ?? $details['operation_id'] ?? null;
|
||||||
|
|
||||||
|
$paymentData = [
|
||||||
|
'compra_id' => null,
|
||||||
|
'matched_purchase_ids' => null,
|
||||||
|
'cuit_buyer' => $details['data']['buyer']['cuit'] ?? $details['buyer']['cuit'] ?? null,
|
||||||
|
'cvu_buyer' => $details['data']['buyer']['cvu'] ?? $details['buyer']['cvu'] ?? null,
|
||||||
|
'amount' => $amount,
|
||||||
|
'concept' => $details['data']['concept'] ?? $details['concept'] ?? null,
|
||||||
|
'operation' => $details['data']['operation'] ?? $details['operation'] ?? null,
|
||||||
|
'operation_id' => $details['data']['operation_id'] ?? $details['operation_id'] ?? null,
|
||||||
|
'transaction_id' => $details['data']['transaction_id'] ?? $details['transaction_id'] ?? null,
|
||||||
|
'qr_order_id' => $qrOrderId,
|
||||||
|
'link_id' => $details['data']['link_id'] ?? $details['link_id'] ?? null,
|
||||||
|
];
|
||||||
|
|
||||||
$transferenciaOperationIds = [1, 3, 11];
|
$transferenciaOperationIds = [1, 3, 11];
|
||||||
$qrOperationIds = [31, 37, 47];
|
$qrOperationIds = [31, 37, 47];
|
||||||
|
|
||||||
@@ -68,12 +82,16 @@ class TelepagosWebhookService
|
|||||||
->where('payment_method', 'transfer')
|
->where('payment_method', 'transfer')
|
||||||
->where('total', $amount)
|
->where('total', $amount)
|
||||||
->latest()
|
->latest()
|
||||||
->limit(2)
|
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
$paymentData['matched_purchase_ids'] = $purchases->pluck('id')->all();
|
||||||
$compra = $purchases->count() === 1 ? $purchases->first() : null;
|
$compra = $purchases->count() === 1 ? $purchases->first() : null;
|
||||||
|
|
||||||
if (! $compra) {
|
if (! $compra) {
|
||||||
|
if ($purchases->count() > 1) {
|
||||||
|
TelepagosPayment::create($paymentData);
|
||||||
|
}
|
||||||
|
|
||||||
Log::channel('telepagos')->warning('Telepagos webhook: Expected exactly one matching purchase.', [
|
Log::channel('telepagos')->warning('Telepagos webhook: Expected exactly one matching purchase.', [
|
||||||
'client_code' => $client->code,
|
'client_code' => $client->code,
|
||||||
'cashin_id' => $cashinId,
|
'cashin_id' => $cashinId,
|
||||||
@@ -163,18 +181,7 @@ class TelepagosWebhookService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$paymentData = [
|
$paymentData['compra_id'] = $compra->id;
|
||||||
'compra_id' => $compra->id,
|
|
||||||
'cuit_buyer' => $details['data']['buyer']['cuit'] ?? $details['buyer']['cuit'] ?? null,
|
|
||||||
'cvu_buyer' => $details['data']['buyer']['cvu'] ?? $details['buyer']['cvu'] ?? null,
|
|
||||||
'amount' => $amount,
|
|
||||||
'concept' => $details['data']['concept'] ?? $details['concept'] ?? null,
|
|
||||||
'operation' => $details['data']['operation'] ?? $details['operation'] ?? null,
|
|
||||||
'operation_id' => $details['data']['operation_id'] ?? $details['operation_id'] ?? null,
|
|
||||||
'transaction_id' => $details['data']['transaction_id'] ?? $details['transaction_id'] ?? null,
|
|
||||||
'qr_order_id' => $qrOrderId,
|
|
||||||
'link_id' => $details['data']['link_id'] ?? $details['link_id'] ?? null,
|
|
||||||
];
|
|
||||||
|
|
||||||
DB::transaction(function () use ($compra, $paymentData) {
|
DB::transaction(function () use ($compra, $paymentData) {
|
||||||
TelepagosPayment::create($paymentData);
|
TelepagosPayment::create($paymentData);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||||||
|
|
||||||
#[Fillable([
|
#[Fillable([
|
||||||
'compra_id',
|
'compra_id',
|
||||||
|
'matched_purchase_ids',
|
||||||
'cuit_buyer',
|
'cuit_buyer',
|
||||||
'cvu_buyer',
|
'cvu_buyer',
|
||||||
'amount',
|
'amount',
|
||||||
@@ -29,6 +30,7 @@ class TelepagosPayment extends Model
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'compra_id' => 'integer',
|
'compra_id' => 'integer',
|
||||||
|
'matched_purchase_ids' => 'array',
|
||||||
'amount' => 'decimal:2',
|
'amount' => 'decimal:2',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('telepagos_payments', function (Blueprint $table) {
|
||||||
|
$table->foreignId('compra_id')->nullable()->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('telepagos_payments', function (Blueprint $table) {
|
||||||
|
$table->foreignId('compra_id')->nullable(false)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('telepagos_payments', function (Blueprint $table) {
|
||||||
|
$table->json('matched_purchase_ids')->nullable()->after('compra_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('telepagos_payments', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('matched_purchase_ids');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -14,6 +14,7 @@ use App\Domains\Catalog\Models\Variant;
|
|||||||
use App\Domains\Integration\Models\ClientIntegration;
|
use App\Domains\Integration\Models\ClientIntegration;
|
||||||
use App\Domains\Integration\Models\Integration;
|
use App\Domains\Integration\Models\Integration;
|
||||||
use App\Domains\Purchase\Models\Purchase;
|
use App\Domains\Purchase\Models\Purchase;
|
||||||
|
use App\Domains\Purchase\Models\TelepagosPayment;
|
||||||
use App\Domains\Purchase\Services\CheckoutService;
|
use App\Domains\Purchase\Services\CheckoutService;
|
||||||
use App\Domains\Tenant\Models\Tenant;
|
use App\Domains\Tenant\Models\Tenant;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
@@ -261,6 +262,84 @@ class TelepagosWebhookTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_transfer_webhook_records_unmatched_payment_when_multiple_purchases_match(): void
|
||||||
|
{
|
||||||
|
$tenant = $this->createTenant('ambiguous', 'Ambiguous', 'ambiguous.com.ar');
|
||||||
|
$this->configureTelepagosIntegration($tenant);
|
||||||
|
|
||||||
|
$variant = $this->createVariantForTenant('ambiguous', 10, '50.00');
|
||||||
|
$firstPurchase = $this->createPendingTransferPurchase(
|
||||||
|
$tenant,
|
||||||
|
User::factory()->create()->id,
|
||||||
|
$variant->id,
|
||||||
|
1,
|
||||||
|
'12345678',
|
||||||
|
);
|
||||||
|
$secondPurchase = $this->createPendingTransferPurchase(
|
||||||
|
$tenant,
|
||||||
|
User::factory()->create()->id,
|
||||||
|
$variant->id,
|
||||||
|
1,
|
||||||
|
'12345678',
|
||||||
|
);
|
||||||
|
$thirdPurchase = $this->createPendingTransferPurchase(
|
||||||
|
$tenant,
|
||||||
|
User::factory()->create()->id,
|
||||||
|
$variant->id,
|
||||||
|
1,
|
||||||
|
'12345678',
|
||||||
|
);
|
||||||
|
|
||||||
|
Http::fake([
|
||||||
|
'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
|
||||||
|
'status' => 'ok',
|
||||||
|
'token' => 'test-token',
|
||||||
|
'expires_at' => now()->addHour()->toIso8601String(),
|
||||||
|
]),
|
||||||
|
'https://api.telepagos.com.ar/v2/payment/cashin/ambiguous' => Http::response([
|
||||||
|
'status' => 'ok',
|
||||||
|
'data' => [
|
||||||
|
'amount' => 50,
|
||||||
|
'operation_id' => 1,
|
||||||
|
'transaction_id' => 'tx-ambiguous',
|
||||||
|
'buyer' => [
|
||||||
|
'cuit' => '20123456789',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->postJson('/api/webhooks/telepagos/ambiguous', [
|
||||||
|
'id' => 'ambiguous',
|
||||||
|
])->assertOk()->assertJsonPath('status', 'success');
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('telepagos_payments', [
|
||||||
|
'compra_id' => null,
|
||||||
|
'amount' => 50,
|
||||||
|
'operation_id' => 1,
|
||||||
|
'transaction_id' => 'tx-ambiguous',
|
||||||
|
]);
|
||||||
|
$payment = TelepagosPayment::query()
|
||||||
|
->where('transaction_id', 'tx-ambiguous')
|
||||||
|
->firstOrFail();
|
||||||
|
$this->assertEqualsCanonicalizing(
|
||||||
|
[$firstPurchase->id, $secondPurchase->id, $thirdPurchase->id],
|
||||||
|
$payment->matched_purchase_ids,
|
||||||
|
);
|
||||||
|
$this->assertDatabaseHas('compras', [
|
||||||
|
'id' => $firstPurchase->id,
|
||||||
|
'status' => Purchase::STATUS_PENDING_PAYMENT,
|
||||||
|
]);
|
||||||
|
$this->assertDatabaseHas('compras', [
|
||||||
|
'id' => $secondPurchase->id,
|
||||||
|
'status' => Purchase::STATUS_PENDING_PAYMENT,
|
||||||
|
]);
|
||||||
|
$this->assertDatabaseHas('compras', [
|
||||||
|
'id' => $thirdPurchase->id,
|
||||||
|
'status' => Purchase::STATUS_PENDING_PAYMENT,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_webhook_confirms_a_purchase_with_tickets_enabled(): void
|
public function test_webhook_confirms_a_purchase_with_tickets_enabled(): void
|
||||||
{
|
{
|
||||||
$tenant = $this->createTenant('expired-ticket', 'Expired Ticket', 'expired-ticket.com.ar');
|
$tenant = $this->createTenant('expired-ticket', 'Expired Ticket', 'expired-ticket.com.ar');
|
||||||
|
|||||||
Reference in New Issue
Block a user