目次
はじめに
Shopee は東南アジア・台湾の EC プラットフォームとして、シンガポール・マレーシア・インドネシア・タイ・台湾・フィリピン・ベトナムなど複数国に展開している。1 つの商品を複数国のショップに同時出品する際、各国の言語・通貨・税制・カテゴリの違いを自動処理する設計が必要になる。
Shopee OpenPlatform の Product API を使った実装を解説する。
認証: HMAC-SHA256 署名
Shopee API の認証は OAuth のアクセストークンに加えて、リクエストごとに HMAC-SHA256 署名 を生成する必要がある。
import crypto from "crypto"
function generateSignature(
partnerKey: string,
partnerId: number,
path: string,
timestamp: number,
accessToken: string,
shopId: number
): string {
const baseString = `${partnerId}${path}${timestamp}${accessToken}${shopId}`
return crypto
.createHmac("sha256", partnerKey)
.update(baseString)
.digest("hex")
}
// API リクエストの構築
async function callShopeeApi(
path: string,
params: Record<string, unknown>,
shopId: number,
accessToken: string
) {
const timestamp = Math.floor(Date.now() / 1000)
const sign = generateSignature(PARTNER_KEY, PARTNER_ID, path, timestamp, accessToken, shopId)
const url = new URL(`https://partner.shopeemobile.com${path}`)
url.searchParams.set("partner_id", String(PARTNER_ID))
url.searchParams.set("shop_id", String(shopId))
url.searchParams.set("timestamp", String(timestamp))
url.searchParams.set("access_token", accessToken)
url.searchParams.set("sign", sign)
const response = await fetch(url.toString(), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(params),
})
return response.json()
}商品の出品: addItem
Shopee で商品を出品するには addItem を呼ぶ。必要なフィールドが多く、カテゴリ別に必須の attribute が変わる点が難しい。
async function addProduct(shopId: number, accessToken: string, product: ShopeeProduct) {
return callShopeeApi(
"/api/v2/product/add_item",
{
original_price: product.price,
description: product.description,
weight: product.weight, // 単位: kg
item_name: product.name,
item_sku: product.sku,
logistics: product.logistics, // 配送方法の配列
category_id: product.categoryId,
attribute_list: product.attributes,
image: {
image_url_list: product.imageUrls,
},
// 多言語対応: 言語コードとテキストのリスト
description_info: {
extended_description: {
field_list: [
{ field_type: "text", text: product.description },
],
},
},
},
shopId,
accessToken
)
}多国展開: ショップごとに異なるカテゴリ ID
Shopee はマーケットプレイス(国)ごとにカテゴリ体系が異なる。「衣類 > メンズ > Tシャツ」の category_id が台湾(TW)とタイ(TH)で違う。
// 国別カテゴリマッピングのキャッシュ
const categoryCache = new Map<string, Map<string, number>>()
async function getCategoryId(
country: string,
shopId: number,
accessToken: string,
categoryPath: string
): Promise<number> {
const cacheKey = country
if (!categoryCache.has(cacheKey)) {
const tree = await fetchCategoryTree(shopId, accessToken)
categoryCache.set(cacheKey, buildCategoryMap(tree))
}
const map = categoryCache.get(cacheKey)!
const id = map.get(categoryPath)
if (!id) throw new Error(`Category not found: ${categoryPath} in ${country}`)
return id
}多言語対応: 翻訳テキストの管理
複数国に同時出品する場合、商品名・説明文の翻訳が必要になる。翻訳テキストをコンテンツ管理システムで管理し、出品時に各国の言語版を取り出す設計にするとよい。
interface ProductTranslation {
locale: string // "ja", "zh-TW", "th", "en"
name: string
description: string
}
interface ProductData {
sku: string
price: Record<string, number> // { "TW": 299, "TH": 350 } など通貨ごとの価格
translations: ProductTranslation[]
imageUrls: string[]
weightKg: number
}
async function publishToAllMarkets(product: ProductData) {
const markets = [
{ country: "TW", shopId: TW_SHOP_ID, accessToken: TW_TOKEN, locale: "zh-TW" },
{ country: "TH", shopId: TH_SHOP_ID, accessToken: TH_TOKEN, locale: "th" },
// ...
]
const results = await Promise.allSettled(
markets.map(async (market) => {
const translation = product.translations.find((t) => t.locale === market.locale)
?? product.translations.find((t) => t.locale === "en") // フォールバック: 英語
if (!translation) throw new Error(`No translation for ${market.locale}`)
const categoryId = await getCategoryId(
market.country,
market.shopId,
market.accessToken,
"clothing/mens/tshirt" // 内部パス
)
return addProduct(market.shopId, market.accessToken, {
name: translation.name,
description: translation.description,
price: product.price[market.country],
sku: product.sku,
categoryId,
imageUrls: product.imageUrls,
weight: product.weightKg,
attributes: [],
logistics: await getDefaultLogistics(market.shopId, market.accessToken),
})
})
)
return results.map((r, i) => ({
market: markets[i].country,
success: r.status === "fulfilled",
error: r.status === "rejected" ? String(r.reason) : undefined,
}))
}在庫の一括更新: updateStockQty
複数国の在庫を一元管理している場合、注文発生時に全市場の在庫を更新する。
async function syncInventory(
shopId: number,
accessToken: string,
itemId: number,
modelId: number,
newStock: number
) {
return callShopeeApi(
"/api/v2/product/update_stock",
{
item_id: itemId,
stock_list: [
{
model_id: modelId,
seller_stock: [{ stock: newStock }],
},
],
},
shopId,
accessToken
)
}まとめ
Shopee OpenPlatform は HMAC 署名が必須で最初の壁が高いが、一度ラッパーを作ると後は通常の REST API と同じように使える。多国展開では国別カテゴリキャッシュと翻訳管理の設計が肝になる。




