'base64:'.base64_encode(random_bytes(32))]); Cache::flush(); } public function test_transfer_webhook_matches_pending_purchase_by_dni_and_total_amount(): void { $tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar'); $this->configureTelepagosIntegration($tenant); $matchingUser = User::factory()->create([ 'email' => 'buyer@example.com', ]); $newerUser = User::factory()->create([ 'email' => 'buyer-2@example.com', ]); $variant = $this->createVariantForTenant('sonder', 10, '50.00'); $matchingPurchase = $this->createPendingTransferPurchase( $tenant, $matchingUser->id, $variant->id, 1, '12345678' ); $newerPurchase = $this->createPendingTransferPurchase( $tenant, $newerUser->id, $variant->id, 2, '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/6351' => Http::response([ 'status' => 'ok', 'data' => [ 'amount' => 50, 'operation_id' => 1, 'transaction_id' => 'tx-123', 'buyer' => [ 'cuit' => '20123456789', ], ], ]), ]); $this->postJson('/api/webhooks/telepagos/sonder', [ 'id' => '6351', ])->assertOk()->assertJsonPath('status', 'success'); $this->assertDatabaseHas('compras', [ 'id' => $matchingPurchase->id, 'status' => Purchase::STATUS_PAID, 'payment_method' => 'transfer', 'total' => 50, ]); $this->assertDatabaseHas('compras', [ 'id' => $newerPurchase->id, 'status' => Purchase::STATUS_PENDING_PAYMENT, 'payment_method' => 'transfer', 'total' => 100, ]); $this->assertDatabaseHas('telepagos_payments', [ 'compra_id' => $matchingPurchase->id, 'amount' => 50, 'operation_id' => 1, 'transaction_id' => 'tx-123', ]); $this->assertDatabaseMissing('telepagos_payments', [ 'compra_id' => $newerPurchase->id, ]); $this->assertDatabaseHas('compra_items', [ 'compra_id' => $matchingPurchase->id, 'producto_variante_id' => $variant->id, 'cantidad' => 1, 'total' => 50, ]); $this->assertDatabaseHas('productos_variantes', [ 'id' => $variant->id, 'stock_real' => 9, 'stock_reservado' => 2, 'cantidad_vendida' => 1, ]); $this->assertDatabaseMissing('compra_items', [ 'compra_id' => $newerPurchase->id, ]); $this->assertSoftDeleted('carritos', [ 'id' => $matchingPurchase->cart_id, ]); } public function test_transfer_webhook_buys_unlimited_inventory_without_reducing_real_stock(): void { $tenant = $this->createTenant('unlimited', 'Unlimited', 'unlimited.com.ar'); $this->configureTelepagosIntegration($tenant); $user = User::factory()->create(); $variant = $this->createVariantForTenant( 'unlimited', 0, '50.00', 'service', InventoryPolicy::Unlimited, ); $purchase = $this->createPendingTransferPurchase( $tenant, $user->id, $variant->id, 3, '87654321', ); 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/7000' => Http::response([ 'status' => 'ok', 'data' => [ 'amount' => 150, 'operation_id' => 1, 'transaction_id' => 'tx-unlimited', 'buyer' => ['cuit' => '20876543219'], ], ]), ]); $this->postJson('/api/webhooks/telepagos/unlimited', ['id' => '7000']) ->assertOk() ->assertJsonPath('status', 'success'); $this->assertDatabaseHas('compras', [ 'id' => $purchase->id, 'status' => Purchase::STATUS_PAID, ]); $this->assertDatabaseHas('productos_variantes', [ 'id' => $variant->id, 'inventory_policy' => InventoryPolicy::Unlimited->value, 'stock_real' => 0, 'stock_reservado' => 0, 'cantidad_vendida' => 3, ]); } private function createPendingTransferPurchase( Tenant $tenant, int $userId, int $variantId, int $quantity, string $dni, ): Purchase { $cart = Cart::query()->create([ 'tenant_codigo' => $tenant->codigo, 'user_id' => $userId, 'status' => 'active', ]); $cart->addItem($variantId, $quantity); /** @var CheckoutService $checkoutService */ $checkoutService = app(CheckoutService::class); $purchase = $checkoutService->startCheckout($tenant, $userId, [ 'cart_id' => $cart->id, 'dni' => $dni, 'telefono' => '+54 9 341 555-4321', 'nombre_apellido' => 'Juan Perez', 'email' => 'juan.perez@example.com', ]); $purchase->update([ 'payment_method' => 'transfer', ]); $checkoutService->completePurchase($purchase); return $purchase->fresh(); } private function configureTelepagosIntegration(Tenant $tenant): void { Integration::create([ 'integration_code' => 'telepagos_homo', 'name' => 'Telepagos', 'url' => 'https://api.telepagos.com.ar', 'integration_data_schema' => [ 'username' => 'required|string', 'password' => 'required|string', ], ]); TenantIntegration::create([ 'tenant_code' => $tenant->codigo, 'integration_code' => 'telepagos_homo', 'integration_data' => [ 'username' => 'user123', 'password' => 'pass123', ], ]); } private function createVariantForTenant( string $tenantCode, int $stock, string $price, string $slugPrefix = 'shirt', InventoryPolicy $inventoryPolicy = InventoryPolicy::Tracked, ): ProductVariant { $category = Category::query()->create([ 'tenant_code' => $tenantCode, 'nombre' => "{$slugPrefix} category {$tenantCode}", ]); $product = Product::query()->create([ 'tenant_codigo' => $tenantCode, 'categoria_id' => $category->id, 'slug' => "{$slugPrefix}-{$tenantCode}-".Product::query()->count(), 'nombre' => ucfirst($slugPrefix)." {$tenantCode}", 'descripcion' => 'Test product', 'precio' => $price, ]); return ProductVariant::query()->create([ 'producto_id' => $product->id, 'inventory_policy' => $inventoryPolicy->value, 'slug' => "{$slugPrefix}-variant-".ProductVariant::query()->count(), 'nombre' => ucfirst($slugPrefix).' Variant', 'stock' => $stock, 'descripcion' => 'Test variant', 'precio' => $price, ])->load('product'); } private function createTenant(string $codigo, string $nombre, string $dominio): Tenant { $hdrKey = (string) Str::uuid(); $ftrKey = (string) Str::uuid(); $headerAttachment = Attachment::create([ 'key' => $hdrKey, 'path' => 'tenants/'.$hdrKey.'.png', 'filename' => 'logo_header.png', 'type' => AttachmentType::Image, 'mime_type' => 'image/png', ]); $footerAttachment = Attachment::create([ 'key' => $ftrKey, 'path' => 'tenants/'.$ftrKey.'.png', 'filename' => 'logo_footer.png', 'type' => AttachmentType::Image, 'mime_type' => 'image/png', ]); return Tenant::create([ 'codigo' => $codigo, 'nombre' => $nombre, 'dominio' => $dominio, 'primary_color' => '#111111', 'secondary_color' => '#222222', 'danger_color' => '#333333', 'success_color' => '#28a745', 'header_bg_color' => '#444444', 'footer_bg_color' => '#444444', 'header_logo_id' => $headerAttachment->id, 'footer_logo_id' => $footerAttachment->id, ]); } }