Add Shopify inventory sync and order webhook
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>
This commit is contained in:
Malek Tellissi
2026-09-11 02:53:49 +02:00
co-authored by Claude Sonnet 4.6
parent f54b471848
commit 467d85dfec
11 changed files with 349 additions and 1 deletions
+57
View File
@@ -0,0 +1,57 @@
<?php
namespace App\Console\Commands;
use App\Models\ProductSize;
use App\Services\ShopifyService;
use Illuminate\Console\Command;
class ShopifyMap extends Command
{
protected $signature = 'shopify:map {--dry-run : Änderungen nur anzeigen, nichts speichern}';
protected $description = 'Verknüpft Shopify-Variant-IDs mit lokalen ProductSizes per SKU';
public function handle(ShopifyService $shopify): int
{
$dryRun = $this->option('dry-run');
$variants = $shopify->getAllVariants();
$this->info('Shopify-Varianten geladen: ' . count($variants));
$mapped = 0;
$skipped = 0;
$noSku = 0;
foreach ($variants as $variant) {
if (empty($variant['sku'])) {
$noSku++;
continue;
}
$size = ProductSize::where('sku', $variant['sku'])->first();
if (!$size) {
$this->warn(" SKU nicht gefunden: {$variant['sku']} ({$variant['product_title']})");
$skipped++;
continue;
}
if ($size->shopify_variant_id === $variant['id']) {
continue; // already correct
}
if (!$dryRun) {
$size->update(['shopify_variant_id' => $variant['id']]);
}
$this->line(" {$variant['sku']} → Variant-ID {$variant['id']}");
$mapped++;
}
$this->info("{$mapped} verknüpft" . ($dryRun ? ' (dry-run)' : ''));
if ($skipped) $this->warn("{$skipped} SKUs lokal nicht gefunden");
if ($noSku) $this->warn("{$noSku} Shopify-Varianten ohne SKU übersprungen");
return self::SUCCESS;
}
}