Build · Shopify

Xây dựng ứng dụng try-on cho Shopify

Tóm tắt trong một câu

Một ứng dụng Shopify try-on đọc ảnh sản phẩm từ Admin API, POST tới Photta, polling job trong background worker, sau đó chuyển URL hoàn chỉnh cho Shopify thông qua productCreateMedia.

  • Credits: 5 credits ở 2K · 6 credits ở 4K
  • Thời gian điển hình: 1.5–4 min
  • Xử lý: async, polling

Mọi số liệu trên trang này đều lấy từ tài liệu Photta API · đã kiểm tra

Đọc ảnh sản phẩm từ Admin API, render try-on bằng Photta và đẩy kết quả ngược lại làm media sản phẩm. Bốn lệnh gọi, không cần tự xử lý hình ảnh.

RESPONSE=$(curl -s -X POST https://api.photta.app/api/v1/tryon/apparel \
  -H "Authorization: Bearer $PHOTTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "product_type": "top",
  "product_images": [
    "https://cdn.yourstore.com/products/shirt-front.jpg"
  ],
  "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
  "product_category": "t_shirt",
  "mannequin_id": "mnk_f3a9b1",
  "pose_id": "pose_c2d4e5",
  "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
  "lock_background": true,
  "resolution": "2K",
  "aspect_ratio": "3:4",
  "body_size": "M",
  "expression": "neutral",
  "user_instruction": "Keep the collar pattern crisp and centered."
}')
ID=$(echo "$RESPONSE" | jq -r '.data.id')

Cơ chế hoạt động

Từ trang sản phẩm đến hình ảnh try-on

Xác định scope cho app, đọc ảnh, gửi request, poll tách biệt khỏi luồng request chính, gắn media.

  1. Bước 1

    Xác định scope cho app

    Ứng dụng try-on cần quyền đọc sản phẩm và ghi media. Chỉ yêu cầu các product scope mà app thực sự dùng và không thêm gì khác — merchants sẽ đọc màn hình cấp quyền.

  2. Bước 2

    Lấy API key của Photta

    Một key cho mỗi môi trường, lưu trong cấu hình server-side của app. Key không bao giờ được gửi tới storefront hay trình duyệt của merchant.

  3. Bước 3

    Đọc ảnh sản phẩm

    Query sản phẩm để lấy ảnh đầu tiên. URL đó ở chế độ công khai, chính xác là thứ Photta endpoint cần — không cần upload lại, không cần quản lý signed URL.

  4. Bước 4

    Gửi yêu cầu try-on

    POST ảnh kèm mannequin và pose. Response trả về ngay lập tức chứa generation id; chưa có nội dung nào được render tại thời điểm này.

  5. Bước 5

    Polling trong background job

    Shopify yêu cầu webhook handler phản hồi trong vài giây, trong khi quá trình generation mất nhiều thời gian hơn thế. Hãy phản hồi xác nhận, đưa vào queue và poll mỗi 3 giây từ worker.

  6. Bước 6

    Gắn media

    Chuyển URL hoàn chỉnh cho Shopify và hệ thống sẽ tự tải file về. Bạn cũng nên lưu ảnh phía server của mình để gallery vẫn tồn tại độc lập.

Bốn lệnh gọi, từ đầu đến cuối

Request và poll tới Photta được trích xuất từ tài liệu API; các lệnh gọi Shopify sử dụng trực tiếp Admin API.

// Shopify Admin GraphQL — the product's first image is your source photo
const query = `query($id: ID!) {
  product(id: $id) { title images(first: 1) { nodes { url } } }
}`;

const res = await admin.graphql(query, { variables: { id: productGid } });
const { data } = await res.json();
const productImage = data.product.images.nodes[0].url;
const res = await fetch("https://api.photta.app/api/v1/tryon/apparel", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "product_type": "top",
    "product_images": [
      "https://cdn.yourstore.com/products/shirt-front.jpg"
    ],
    "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
    "product_category": "t_shirt",
    "mannequin_id": "mnk_f3a9b1",
    "pose_id": "pose_c2d4e5",
    "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
    "lock_background": true,
    "resolution": "2K",
    "aspect_ratio": "3:4",
    "body_size": "M",
    "expression": "neutral",
    "user_instruction": "Keep the collar pattern crisp and centered."
  }),
});
const { data } = await res.json(); // data.id, data.status
async function waitFor(id) {
  for (let attempt = 0; attempt < 120; attempt++) {
    const res = await fetch(`https://api.photta.app/api/v1/tryon/apparel/${id}`, { headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}` } });
    const { data } = await res.json();
    if (data.status === "completed") return data.output_url;
    if (data.status === "failed") throw new Error(data.error_message ?? "generation failed");
    await new Promise((r) => setTimeout(r, 3000));
  }
  throw new Error("timed out after 6 minutes");
}
// Shopify downloads the URL itself, so hand it the Photta output_url
const mutation = `mutation($id: ID!, $media: [CreateMediaInput!]!) {
  productCreateMedia(productId: $id, media: $media) {
    mediaUserErrors { field message }
  }
}`;

await admin.graphql(mutation, {
  variables: {
    id: productGid,
    media: [{ originalSource: outputUrl, mediaContentType: "IMAGE", alt: "Virtual try-on" }],
  },
});

Câu hỏi thường gặp từ developer

Câu hỏi lập trình viên Shopify thường gặp

Photta key được lưu ở đâu trong ứng dụng Shopify?

Trong biến môi trường trên server của app, tuyệt đối không đặt trong theme, app block hay response của app proxy. Bất cứ thứ gì storefront đọc được thì người mua hàng cũng đọc được.

Ứng dụng này cần những scope nào của Shopify?

Quyền đọc ảnh sản phẩm và ghi product media. Photta hoàn toàn không cần quyền truy cập vào Shopify — ứng dụng của bạn là thành phần duy nhất giao tiếp với cả hai bên.

Làm thế nào để giữ webhook phản hồi nhanh?

Xác nhận phản hồi ngay lập tức, đưa tác vụ vào queue và poll từ worker. Quá trình generation mất nhiều thời gian hơn ngưỡng cho phép của webhook handler, vì vậy không được đặt tác vụ chờ đợi vào handler.

Tôi có thể tính phí merchant theo từng ảnh không?

Đó là quyết định định giá của bạn. Photta trừ credits vào tài khoản của bạn theo từng lượt gọi, vì vậy chi phí trên mỗi ảnh đã được biết trước khi render; bạn tính thêm bao nhiêu là tùy bạn.

Nếu sản phẩm không có ảnh thì sao?

Hãy bỏ qua thay vì gửi ảnh giữ chỗ (placeholder). Endpoint cần ảnh chụp trang phục thực tế, và ảnh placeholder sẽ làm lãng phí credits vào một bức ảnh không ai sử dụng.

Phát hành ứng dụng, không tốn công dựng hạ tầng

Các lệnh gọi Photta trên trang này được lấy trực tiếp từ tài liệu API. Lấy key và bạn sẽ có bản render đầu tiên chỉ sau vài phút.