ビルド · Shopify

Shopify向けtry-onアプリの構築

要約

Shopify try-onアプリは、Admin APIから商品画像を読み取り、PhottaにPOSTし、バックグラウンドワーカーでジョブをポーリングして、完成したURLをproductCreateMediaを通じてShopifyに渡します。

  • Credits: 2Kで5 credits · 4Kで6 credits
  • 所要時間の目安: 1.5–4 min
  • 処理方式: 非同期、ポーリング

このページに記載されている数値はすべて以下に基づきます: Photta APIドキュメント · 確認済み

Admin APIから商品写真を読み込み、Phottaでtry-onをレンダリングして、結果を商品メディアとしてプッシュします。必要なのは4回のリクエストのみで、自前での画像処理は不要です。

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')

仕組み

商品ページからtry-on画像まで

アプリのスコープ設定、写真の読み取り、送信、リクエストパス外でのポーリング、メディアの添付。

  1. ステップ 1

    アプリのスコープ設定

    try-onアプリには商品の読み取りとメディアの書き込み権限が必要です。アプリが実際に使用する商品スコープのみを要求してください—マーチャントは同意画面を確認します。

  2. ステップ 2

    Photta APIキーの取得

    環境ごとに1つのキーを使用し、アプリのサーバー側設定で保持します。ストアフロントやマーチャントのブラウザに渡ることはありません。

  3. ステップ 3

    商品画像の読み取り

    商品の最初の画像をクエリします。そのURLは公開されており、Photta endpointが求めている形式そのものです—再アップロードも、署名付きURLの管理も不要です。

  4. ステップ 4

    try-onのリクエスト送信

    画像、mannequin、poseを指定してPOSTします。レスポンスは即座に返され、generation idが含まれます。この時点ではまだレンダリングされていません。

  5. ステップ 5

    バックグラウンドジョブでのポーリング

    Shopifyはwebhookハンドラーが数秒以内に応答することを想定していますが、生成にはそれ以上の時間がかかります。即座に応答を返し、キューに追加して、ワーカーから3秒ごとにポーリングしてください。

  6. ステップ 6

    メディアの添付

    完成したURLをShopifyに渡せば、Shopify自体がファイルをダウンロードします。ギャラリーが独立して存続できるよう、画像はアプリ側でも永続化してください。

エンドツーエンドの4つの呼び出し

PhottaのリクエストとポーリングはAPIドキュメントに基づいています。Shopifyの呼び出しは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" }],
  },
});

開発者からのよくある質問

Shopify開発者からよくある質問

Shopifyアプリ内でPhottaキーはどこに保持すべきですか?

テーマ内、アプリブロック、またはアプリプロキシのレスポンスではなく、アプリサーバーの環境内に保持してください。ストアフロントが読み取れるものは、購入者も読み取ることができます。

これにはどのShopifyスコープが必要ですか?

商品画像の読み取りと商品メディアの書き込みです。Photta側にはShopifyへのアクセス権限は一切不要です—両側と通信するのは作成するアプリのみです。

webhookの処理速度を維持するにはどうすればよいですか?

即座に応答を返し、処理をキューに追加して、ワーカーからポーリングします。生成にはwebhookハンドラーが許容する以上の時間がかかるため、完了を待つ処理をハンドラー内に含めるべきではありません。

マーチャントに対して画像ごとに課金できますか?

それは価格設定の判断次第です。Phottaは呼び出しごとにアカウントへcreditsを請求するため、レンダリング前に画像あたりのコストが確定します。そこにいくら上乗せするかは自由です。

商品に写真がない場合はどうなりますか?

プレースホルダーを送信するのではなく、スキップしてください。endpointには実際の衣服の写真が必要であり、プレースホルダーを使用すると誰も公開しない画像にcreditsを消費することになります。

インフラの構築ではなく、アプリのリリースに注力する

このページのPhottaの呼び出しは、APIドキュメントからそのまま引用しています。キーを取得すれば、数分で最初のレンダリングを実行できます。