Files
2nd/10_Wiki/Topics/AI_and_ML/전자상거래 플랫폼.md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

7.9 KiB

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-전자상거래-플랫폼 전자상거래 플랫폼 10_Wiki/Topics verified self
E-commerce Platform
Shopify
headless commerce
커머스 플랫폼
none A 0.9 applied
ecommerce
platform
headless
shopify
checkout
2026-05-10 pending
language framework
typescript shopify-hydrogen-medusa

전자상거래 플랫폼

매 한 줄

"매 monolith 의 Shopify vs headless 의 Medusa/Saleor 의 build vs buy spectrum 결정". 2026 의 ecommerce 는 SaaS (Shopify, BigCommerce), Composable/MACH (commercetools, Medusa, Saleor), AI-native (agentic shopping, conversational commerce) 3축으로 분기. 매 plat 선택은 catalog scale, custom logic 양, dev resource, 매 international tax 의 4 변수 함수.

매 핵심

매 platform 분류

  • SaaS Monolith: Shopify (Plus), BigCommerce, Wix Commerce.
  • Headless / Composable: Shopify Hydrogen, commercetools, Saleor, Medusa.
  • Open-source: Medusa.js, Saleor, Sylius (PHP), Spree.
  • Marketplace: Mirakl, Marketplacer.
  • B2B specialized: SAP Commerce, Salesforce B2B, Spryker.

매 핵심 component

  • Catalog: product, variant, option, inventory.
  • Cart/Checkout: session, abandoned cart, taxonomy.
  • Payment: PSP integration (Stripe, Adyen, Toss).
  • Order/Fulfillment: state machine, OMS.
  • Tax/Shipping: TaxJar, Avalara, ShipBob.
  • Customer/Account: passwordless, social, subscription.

매 응용

  1. Shopify Hydrogen 의 storefront 와 headless backend.
  2. Medusa.js + Next.js 의 fully open-source stack.
  3. Composable commerce 의 best-of-breed 조합.

💻 패턴

Shopify Storefront API (GraphQL)

// app/lib/shopify.ts
import { createStorefrontClient } from "@shopify/hydrogen-react";

export const client = createStorefrontClient({
  storeDomain: "your-shop.myshopify.com",
  storefrontApiVersion: "2025-01",
  publicStorefrontToken: process.env.PUBLIC_STOREFRONT_TOKEN!,
});

const PRODUCTS_QUERY = `#graphql
  query Products($first: Int!) {
    products(first: $first) {
      nodes {
        id title handle
        priceRange { minVariantPrice { amount currencyCode } }
        featuredImage { url altText }
      }
    }
  }`;

export async function listProducts(first = 24) {
  const { data } = await client.query(PRODUCTS_QUERY, { variables: { first } });
  return data.products.nodes;
}

Medusa.js (open-source backend)

// medusa-config.ts
import { defineConfig } from "@medusajs/framework/utils";

export default defineConfig({
  projectConfig: {
    databaseUrl: process.env.DATABASE_URL,
    redisUrl: process.env.REDIS_URL,
    http: {
      storeCors: process.env.STORE_CORS!,
      adminCors: process.env.ADMIN_CORS!,
      authCors: process.env.AUTH_CORS!,
      jwtSecret: process.env.JWT_SECRET!,
      cookieSecret: process.env.COOKIE_SECRET!,
    },
  },
  modules: [
    { resolve: "@medusajs/medusa/payment-stripe",
      options: { apiKey: process.env.STRIPE_API_KEY }},
    { resolve: "@medusajs/medusa/cache-redis",
      options: { redisUrl: process.env.REDIS_URL }},
  ],
});

Cart state (Zustand + persist)

import { create } from "zustand";
import { persist } from "zustand/middleware";

type CartItem = { variantId: string; qty: number; priceCents: number };
interface CartState {
  items: CartItem[];
  add: (i: CartItem) => void;
  remove: (variantId: string) => void;
  total: () => number;
}

export const useCart = create<CartState>()(persist(
  (set, get) => ({
    items: [],
    add: (i) => set(s => {
      const ex = s.items.find(x => x.variantId === i.variantId);
      return ex
        ? { items: s.items.map(x => x === ex ? { ...x, qty: x.qty + i.qty } : x) }
        : { items: [...s.items, i] };
    }),
    remove: (id) => set(s => ({ items: s.items.filter(x => x.variantId !== id) })),
    total: () => get().items.reduce((a,b) => a + b.qty * b.priceCents, 0),
  }),
  { name: "cart" }
));

Stripe Checkout (server)

import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET!);

export async function createCheckoutSession(items: CartItem[], userId: string) {
  return await stripe.checkout.sessions.create({
    mode: "payment",
    line_items: items.map(i => ({
      price_data: {
        currency: "usd",
        product_data: { name: i.title },
        unit_amount: i.priceCents,
      },
      quantity: i.qty,
    })),
    success_url: `${process.env.SITE}/order/success?sid={CHECKOUT_SESSION_ID}`,
    cancel_url:  `${process.env.SITE}/cart`,
    automatic_tax: { enabled: true },
    metadata: { userId },
  });
}

Webhook idempotency

// app/api/webhooks/stripe/route.ts
import Stripe from "stripe";
import { redis } from "@/lib/redis";

export async function POST(req: Request) {
  const sig = req.headers.get("stripe-signature")!;
  const body = await req.text();
  const event = Stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);

  // Idempotency: refuse double-processing
  const seen = await redis.set(`stripe:evt:${event.id}`, "1", "EX", 60*60*24, "NX");
  if (!seen) return new Response("dup", { status: 200 });

  if (event.type === "checkout.session.completed") {
    await fulfillOrder(event.data.object);
  }
  return new Response("ok");
}

Order state machine (XState)

import { createMachine } from "xstate";

export const orderMachine = createMachine({
  id: "order",
  initial: "pending_payment",
  states: {
    pending_payment: {
      on: { PAID: "paid", CANCELLED: "cancelled", EXPIRED: "expired" }
    },
    paid: { on: { PACKED: "packed", REFUND: "refunded" } },
    packed: { on: { SHIPPED: "shipped" } },
    shipped: { on: { DELIVERED: "delivered", LOST: "investigating" } },
    delivered: { type: "final" },
    cancelled: { type: "final" },
    expired: { type: "final" },
    refunded: { type: "final" },
    investigating: { on: { RESOLVED: "delivered", REFUND: "refunded" } },
  },
});

Inventory reservation

// Atomic reserve via Redis Lua
const RESERVE_LUA = `
  local stock = tonumber(redis.call('GET', KEYS[1]))
  local need = tonumber(ARGV[1])
  if stock and stock >= need then
    redis.call('DECRBY', KEYS[1], need)
    return 1
  end
  return 0`;

await redis.eval(RESERVE_LUA, 1, `stock:${variantId}`, qty);

Tax calc (Stripe Tax / Avalara)

const calc = await stripe.tax.calculations.create({
  currency: "usd",
  line_items: items.map(i => ({
    amount: i.priceCents * i.qty,
    reference: i.variantId,
    tax_behavior: "exclusive",
  })),
  customer_details: {
    address: { line1, city, state, postal_code, country: "US" },
    address_source: "shipping",
  },
});

매 결정 기준

상황 Approach
<100 SKU, fast launch Shopify (Basic/Plus)
custom checkout flow Hydrogen (Shopify) or Medusa
100% control + open-source Medusa.js
enterprise, MACH commercetools
B2B multi-tenant Saleor / Spryker
AI agent shopping Storefront API + agent layer

기본값: Shopify Plus + Hydrogen for SMB-mid; Medusa.js for full-control startups.

🔗 Graph

🤖 LLM 활용

언제: product description gen, category mapping, support FAQ from KB, conversational search. 언제 X: payment authorization 의 final approval — deterministic system 만.

안티패턴

  • DIY checkout from scratch: PCI scope blowup.
  • Webhook without idempotency: double-fulfill, double-refund.
  • Cart in localStorage only: lost on multi-device.
  • No inventory reservation: oversell.

🧪 검증 / 중복

  • Verified (Shopify 2025 dev docs, Medusa v2, Stripe Tax docs, MACH alliance).
  • 신뢰도 A.

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — ecommerce platform decision matrix + patterns.