构建 · Shopify
构建 Shopify 试穿应用
一句话总结
Shopify 试穿应用从 Admin API 读取商品图片,将其 POST 至 Photta,在后台 worker 中轮询作业,然后通过 productCreateMedia 将生成完成的 URL 传给 Shopify。
- Credits: 2K 分辨率 5 credits · 4K 分辨率 6 credits
- 常规耗时: 1.5–4 min
- 处理方式: 异步,轮询
本页所有数据均来自 Photta API 文档 · 已验证
从 Admin API 读取商品图片,通过 Photta 渲染试穿效果,再将结果推回作为商品媒体资源。只需 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')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.statusimport os, requests
res = requests.post(
"https://api.photta.app/api/v1/tryon/apparel",
headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"},
json={
"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."
},
)
data = res.json()["data"] # data["id"], data["status"]工作原理
从商品页面到试穿图片
配置应用权限、读取图片、提交任务、在请求主路径外轮询、附加媒体资源。
步骤 1
配置应用权限
试穿应用需要读取商品并写入媒体资源。仅申请应用实际使用的商品权限范围,切勿多要——商家会查看授权界面。
步骤 2
获取 Photta API key
每个环境配置一个 key,保存在应用的服务器端配置中。切勿将其暴露给在线商店(storefront)或商家的浏览器。
步骤 3
读取商品图片
查询商品以获取其首张图片。该 URL 是公开的,正好符合 Photta endpoint 的需求——无需重新上传,也无需管理签名 URL。
步骤 4
提交试穿任务
POST 图片以及模特和姿势参数。响应是即时的,并返回 generation id;此时尚未开始渲染。
步骤 5
在后台作业中轮询
Shopify 要求 webhook 处理程序在数秒内响应,而生成耗时会超过这个时间。立即返回确认,加入队列,并在 worker 中每 3 秒轮询一次。
步骤 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.statusasync 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 开发者常见问题
Photta key 应该存放在 Shopify 应用的什么位置?
存放在应用服务器的环境变量中,切勿放在主题、app block 或 app proxy 响应中。凡是在线商店可读取的内容,顾客也同样能读取。
这需要哪些 Shopify 权限范围(scopes)?
读取商品图片和写入商品媒体资源的权限。Photta 完全不需要任何 Shopify 访问权限——你的应用是唯一同时与两端通信的主体。
如何确保 webhook 保持快速响应?
立即返回确认,将任务加入队列,并在 worker 中轮询。生成耗时超过了 webhook 处理程序应有的时长,因此任何等待生成的操作都不应放在处理程序中。
我可以按图片向商家收费吗?
这完全取决于你的定价策略。Photta 按每次调用从你的账户扣除 credits,因此每张图片的成本在渲染前即可明确;在此基础上加收多少由你决定。
如果商品没有图片怎么办?
直接跳过,切勿发送占位图。该 endpoint 需要真实的服装照片,使用占位图只会白白消耗 credits 生成无人会发布的图片。