Deploy / deploy (push) Successful in 14s
- Migration: shopify_variant_id on products_sizes, shopify_order_id on products_sales - ShopifyService: paginated REST client (2024-10 API) - shopify:map — one-time command to link variants by SKU - shopify:sync — reconciliation command (Shopify is master) - VerifyShopifyWebhook middleware: HMAC-SHA256 verification - ShopifyWebhookController: idempotent orders/paid handler, FIFO inventory deduction - Webhook route: POST /api/webhooks/shopify/orders-paid Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
2.0 KiB
PHP
64 lines
2.0 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\ProductInventory;
|
|
use App\Models\ProductSale;
|
|
use App\Models\ProductSize;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Response;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class ShopifyWebhookController extends Controller
|
|
{
|
|
public function ordersPaid(Request $request): Response
|
|
{
|
|
$order = $request->json()->all();
|
|
$orderId = (string) ($order['id'] ?? '');
|
|
|
|
// Idempotency: ignore already-processed orders
|
|
if (ProductSale::where('shopify_order_id', $orderId)->exists()) {
|
|
return response('', 200);
|
|
}
|
|
|
|
DB::transaction(function () use ($order, $orderId) {
|
|
foreach ($order['line_items'] ?? [] as $item) {
|
|
$variantId = (string) ($item['variant_id'] ?? '');
|
|
$quantity = (int) ($item['quantity'] ?? 0);
|
|
|
|
if (!$variantId || $quantity <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$size = ProductSize::where('shopify_variant_id', $variantId)->first();
|
|
|
|
if (!$size) {
|
|
Log::warning('Shopify webhook: unbekannte Variant-ID', [
|
|
'variant_id' => $variantId,
|
|
'order_id' => $orderId,
|
|
]);
|
|
continue;
|
|
}
|
|
|
|
// FIFO: älteste verfügbare Einheiten ausbuchen
|
|
ProductInventory::where('size_id', $size->id)
|
|
->whereNull('out_at')
|
|
->oldest()
|
|
->limit($quantity)
|
|
->update(['out_at' => now()]);
|
|
|
|
ProductSale::create([
|
|
'product_id' => $size->product_id,
|
|
'size_id' => $size->id,
|
|
'amount' => $quantity,
|
|
'sold_at' => now(),
|
|
'shopify_order_id' => $orderId,
|
|
]);
|
|
}
|
|
});
|
|
|
|
return response('', 200);
|
|
}
|
|
}
|