Compare commits

...

5 Commits

15 changed files with 145 additions and 22 deletions

View File

@@ -12,9 +12,9 @@ class CatalogController extends Controller
public function index(string $tenant): JsonResponse public function index(string $tenant): JsonResponse
{ {
$featuredGroups = FeaturedGroup::where('tenant_codigo', $tenant) $featuredGroups = FeaturedGroup::where('tenant_codigo', $tenant)
->with(['featuredVariants' => function ($query) { ->with(['groupItems' => function ($query) {
$query->orderBy('order'); $query->orderBy('order');
}, 'featuredVariants.variant.product', 'featuredVariants.variant.attachments', 'featuredVariants.variant.product.attachments']) }, 'groupItems.variant.product', 'groupItems.variant.attachments', 'groupItems.variant.product.attachments'])
->orderBy('group_order') ->orderBy('group_order')
->get(); ->get();

View File

@@ -32,7 +32,7 @@ class FeaturedGroupController extends Controller
public function show(string $tenant, FeaturedGroup $featuredGroup): FeaturedGroupResource public function show(string $tenant, FeaturedGroup $featuredGroup): FeaturedGroupResource
{ {
abort_if($featuredGroup->tenant_codigo !== $tenant, 404); abort_if($featuredGroup->tenant_codigo !== $tenant, 404);
return new FeaturedGroupResource($featuredGroup->load('featuredVariants')); return new FeaturedGroupResource($featuredGroup->load('groupItems'));
} }
public function update(UpdateFeaturedGroupRequest $request, string $tenant, FeaturedGroup $featuredGroup): FeaturedGroupResource public function update(UpdateFeaturedGroupRequest $request, string $tenant, FeaturedGroup $featuredGroup): FeaturedGroupResource

View File

@@ -3,7 +3,7 @@
namespace App\Domains\Catalog\Controllers; namespace App\Domains\Catalog\Controllers;
use App\Domains\Catalog\Models\FeaturedGroup; use App\Domains\Catalog\Models\FeaturedGroup;
use App\Domains\Catalog\Models\FeaturedVariant; use App\Domains\Catalog\Models\GroupItem;
use App\Domains\Catalog\Requests\StoreFeaturedVariantRequest; use App\Domains\Catalog\Requests\StoreFeaturedVariantRequest;
use App\Domains\Catalog\Requests\UpdateFeaturedVariantRequest; use App\Domains\Catalog\Requests\UpdateFeaturedVariantRequest;
use App\Domains\Catalog\Resources\FeaturedVariantResource; use App\Domains\Catalog\Resources\FeaturedVariantResource;
@@ -16,24 +16,24 @@ class FeaturedVariantController extends Controller
public function index(string $tenant, FeaturedGroup $featuredGroup): AnonymousResourceCollection public function index(string $tenant, FeaturedGroup $featuredGroup): AnonymousResourceCollection
{ {
abort_if($featuredGroup->tenant_codigo !== $tenant, 404); abort_if($featuredGroup->tenant_codigo !== $tenant, 404);
return FeaturedVariantResource::collection($featuredGroup->featuredVariants); return FeaturedVariantResource::collection($featuredGroup->groupItems);
} }
public function store(StoreFeaturedVariantRequest $request, string $tenant, FeaturedGroup $featuredGroup): FeaturedVariantResource public function store(StoreFeaturedVariantRequest $request, string $tenant, FeaturedGroup $featuredGroup): FeaturedVariantResource
{ {
abort_if($featuredGroup->tenant_codigo !== $tenant, 404); abort_if($featuredGroup->tenant_codigo !== $tenant, 404);
$variant = $featuredGroup->featuredVariants()->create($request->validated()); $variant = $featuredGroup->groupItems()->create($request->validated());
return new FeaturedVariantResource($variant); return new FeaturedVariantResource($variant);
} }
public function update(UpdateFeaturedVariantRequest $request, string $tenant, FeaturedGroup $featuredGroup, FeaturedVariant $featuredVariant): FeaturedVariantResource public function update(UpdateFeaturedVariantRequest $request, string $tenant, FeaturedGroup $featuredGroup, GroupItem $featuredVariant): FeaturedVariantResource
{ {
abort_if($featuredGroup->tenant_codigo !== $tenant || $featuredVariant->featured_group_id !== $featuredGroup->id, 404); abort_if($featuredGroup->tenant_codigo !== $tenant || $featuredVariant->featured_group_id !== $featuredGroup->id, 404);
$featuredVariant->update($request->validated()); $featuredVariant->update($request->validated());
return new FeaturedVariantResource($featuredVariant); return new FeaturedVariantResource($featuredVariant);
} }
public function destroy(string $tenant, FeaturedGroup $featuredGroup, FeaturedVariant $featuredVariant): JsonResponse public function destroy(string $tenant, FeaturedGroup $featuredGroup, GroupItem $featuredVariant): JsonResponse
{ {
abort_if($featuredGroup->tenant_codigo !== $tenant || $featuredVariant->featured_group_id !== $featuredGroup->id, 404); abort_if($featuredGroup->tenant_codigo !== $tenant || $featuredVariant->featured_group_id !== $featuredGroup->id, 404);
$featuredVariant->delete(); $featuredVariant->delete();

View File

@@ -14,8 +14,8 @@ class FeaturedGroup extends Model
'group_order', 'group_order',
]; ];
public function featuredVariants(): HasMany public function groupItems(): HasMany
{ {
return $this->hasMany(FeaturedVariant::class); return $this->hasMany(GroupItem::class);
} }
} }

View File

@@ -2,13 +2,12 @@
namespace App\Domains\Catalog\Models; namespace App\Domains\Catalog\Models;
use App\Domains\Catalog\Models\ProductVariant;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
class FeaturedVariant extends Model class GroupItem extends Model
{ {
protected $table = 'featured_variants'; protected $table = 'group_items';
protected $fillable = [ protected $fillable = [
'featured_group_id', 'featured_group_id',

View File

@@ -4,7 +4,7 @@ namespace App\Domains\Catalog\Resources;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
use App\Domains\Catalog\Models\FeaturedVariant; use App\Domains\Catalog\Models\GroupItem;
class CatalogFeaturedGroupResource extends JsonResource class CatalogFeaturedGroupResource extends JsonResource
{ {
@@ -15,9 +15,9 @@ class CatalogFeaturedGroupResource extends JsonResource
'title' => $this->group_name, 'title' => $this->group_name,
'layout' => $this->product_layout, 'layout' => $this->product_layout,
'group_order' => $this->group_order, 'group_order' => $this->group_order,
'items' => $this->whenLoaded('featuredVariants', function () use ($request) { 'items' => $this->whenLoaded('groupItems', function () use ($request) {
return $this->featuredVariants->map(function (FeaturedVariant $featuredVariant) use ($request) { return $this->groupItems->map(function (GroupItem $groupItem) use ($request) {
$variant = $featuredVariant->variant; $variant = $groupItem->variant;
$variantResource = (new ProductVariantResource($variant))->toArray($request); $variantResource = (new ProductVariantResource($variant))->toArray($request);
if ($this->product_layout === 'row' || $this->product_layout === 'column_with_cart' || $this->product_layout === 'vertical_with_cart') { if ($this->product_layout === 'row' || $this->product_layout === 'column_with_cart' || $this->product_layout === 'vertical_with_cart') {

View File

@@ -15,7 +15,7 @@ class FeaturedGroupResource extends JsonResource
'group_name' => $this->group_name, 'group_name' => $this->group_name,
'product_layout' => $this->product_layout, 'product_layout' => $this->product_layout,
'group_order' => $this->group_order, 'group_order' => $this->group_order,
'featured_variants' => FeaturedVariantResource::collection($this->whenLoaded('featuredVariants')), 'featured_variants' => FeaturedVariantResource::collection($this->whenLoaded('groupItems')),
]; ];
} }
} }

View File

@@ -54,7 +54,7 @@ class TelepagosWebhookService
$dni = substr($cuit, 2, -1); $dni = substr($cuit, 2, -1);
$compra = Purchase::where('tenant_codigo', $tenantCodigo) $compra = Purchase::where('tenant_codigo', $tenantCodigo)
->whereRaw("REPLACE(dni, '.', '') = ?", [$dni]) ->where('transfer_payer_dni', $dni)
->whereIn('status', [Purchase::STATUS_CREATED, Purchase::STATUS_PENDING_PAYMENT]) ->whereIn('status', [Purchase::STATUS_CREATED, Purchase::STATUS_PENDING_PAYMENT])
->where('payment_method', 'transfer') ->where('payment_method', 'transfer')
->where('total', $amount) ->where('total', $amount)

View File

@@ -68,10 +68,17 @@ class PurchaseController extends Controller
$method = $request->validated('method'); $method = $request->validated('method');
$totalAmount = $compra->calculateCurrentTotalAmount(); $totalAmount = $compra->calculateCurrentTotalAmount();
$compra->update([ $purchaseUpdate = [
'payment_method' => $method, 'payment_method' => $method,
'status' => Purchase::STATUS_PENDING_PAYMENT,
'total' => $totalAmount, 'total' => $totalAmount,
]); ];
if ($method === 'transfer') {
$purchaseUpdate['transfer_payer_dni'] = preg_replace('/\D+/', '', (string) $request->validated('transfer_payer_dni'));
}
$compra->update($purchaseUpdate);
if ($method === 'transfer') { if ($method === 'transfer') {
$telepagosService = new TelepagosIntegrationService; $telepagosService = new TelepagosIntegrationService;

View File

@@ -20,6 +20,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
'payment_method', 'payment_method',
'total', 'total',
'dni', 'dni',
'transfer_payer_dni',
'telefono', 'telefono',
'nombre_apellido', 'nombre_apellido',
'email', 'email',

View File

@@ -19,6 +19,11 @@ class PaymentIntentRequest extends FormRequest
{ {
return [ return [
'method' => ['required', 'string', Rule::in(['qr', 'transfer'])], 'method' => ['required', 'string', Rule::in(['qr', 'transfer'])],
'transfer_payer_dni' => [
Rule::requiredIf(fn (): bool => $this->input('method') === 'transfer'),
'string',
'regex:/^\d{7,8}$/',
],
]; ];
} }
} }

View File

@@ -44,6 +44,7 @@ class PurchaseResource extends JsonResource
'status' => $this->status, 'status' => $this->status,
'payment_method' => $this->payment_method, 'payment_method' => $this->payment_method,
'dni' => $this->dni, 'dni' => $this->dni,
'transfer_payer_dni' => $this->transfer_payer_dni,
'telefono' => $this->telefono, 'telefono' => $this->telefono,
'nombre_apellido' => $this->nombre_apellido, 'nombre_apellido' => $this->nombre_apellido,
'email' => $this->email, 'email' => $this->email,

View File

@@ -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('compras', function (Blueprint $table) {
$table->string('transfer_payer_dni', 8)->nullable()->after('dni');
});
}
public function down(): void
{
Schema::table('compras', function (Blueprint $table) {
$table->dropColumn('transfer_payer_dni');
});
}
};

View File

@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::rename('featured_variants', 'group_items');
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::rename('group_items', 'featured_variants');
}
};

View File

@@ -33,6 +33,70 @@ class TelepagosWebhookTest extends TestCase
Cache::flush(); Cache::flush();
} }
public function test_transfer_payment_intent_requires_a_valid_transfer_payer_dni(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$user = User::factory()->create();
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
$purchase = $this->createPendingTransferPurchase($tenant, $user->id, $variant->id, 1, '12345678');
$this->actingAs($user, 'sanctum')
->postJson("/api/tenants/sonder/compras/{$purchase->id}/payment-intent", [
'method' => 'transfer',
])
->assertUnprocessable()
->assertJsonValidationErrors(['transfer_payer_dni']);
$this->actingAs($user, 'sanctum')
->postJson("/api/tenants/sonder/compras/{$purchase->id}/payment-intent", [
'method' => 'transfer',
'transfer_payer_dni' => '12.345.678',
])
->assertUnprocessable()
->assertJsonValidationErrors(['transfer_payer_dni']);
}
public function test_transfer_payment_intent_persists_transfer_payer_dni_without_replacing_customer_dni(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$this->configureTelepagosIntegration($tenant);
$user = User::factory()->create();
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
$purchase = $this->createPendingTransferPurchase($tenant, $user->id, $variant->id, 1, '12345678');
$purchase->update(['status' => Purchase::STATUS_CREATED]);
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/account/info' => Http::response([
'status' => 'ok',
'holder' => 'Telepagos Test',
'cvu' => '0000003100000000000001',
'alias' => 'telepagos.test',
'entity' => 'Telepagos S.A.',
]),
]);
$this->actingAs($user, 'sanctum')
->postJson("/api/tenants/sonder/compras/{$purchase->id}/payment-intent", [
'method' => 'transfer',
'transfer_payer_dni' => '23456789',
])
->assertOk()
->assertJsonPath('transfer_data.alias', 'telepagos.test');
$this->assertDatabaseHas('compras', [
'id' => $purchase->id,
'dni' => '87654321',
'transfer_payer_dni' => '23456789',
'payment_method' => 'transfer',
'status' => Purchase::STATUS_PENDING_PAYMENT,
]);
}
public function test_transfer_webhook_matches_pending_purchase_by_dni_and_total_amount(): void public function test_transfer_webhook_matches_pending_purchase_by_dni_and_total_amount(): void
{ {
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar'); $tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
@@ -209,7 +273,7 @@ class TelepagosWebhookTest extends TestCase
$purchase = $checkoutService->startCheckout($tenant, $userId, [ $purchase = $checkoutService->startCheckout($tenant, $userId, [
'cart_id' => $cart->id, 'cart_id' => $cart->id,
'dni' => $dni, 'dni' => '87654321',
'telefono' => '+54 9 341 555-4321', 'telefono' => '+54 9 341 555-4321',
'nombre_apellido' => 'Juan Perez', 'nombre_apellido' => 'Juan Perez',
'email' => 'juan.perez@example.com', 'email' => 'juan.perez@example.com',
@@ -217,6 +281,7 @@ class TelepagosWebhookTest extends TestCase
$purchase->update([ $purchase->update([
'payment_method' => 'transfer', 'payment_method' => 'transfer',
'transfer_payer_dni' => $dni,
]); ]);
$checkoutService->completePurchase($purchase); $checkoutService->completePurchase($purchase);