mcpField
P
product-catalog-management

product-catalog-management

github.com/dodopayments其他13198 次安装3 周前更新
代码仓库

关于 product-catalog-management

product-catalog-management 是一项指导开发者使用 Dodo Payments SDK 管理产品目录的技能。它涵盖多种定价模式、产品生命周期、附加项、集合及数字文件交付,并说明价格变更和归档等限制。

主要功能

  1. 01创建单次、订阅或按用量计费产品
  2. 02列出、检索、更新和归档产品
  3. 03管理产品图片与短链接
  4. 04创建附加项和产品集合
  5. 05通过权益交付数字文件

适用场景

  1. 01搭建包含不同定价模式的产品目录
  2. 02更新产品价格或将产品归档
  3. 03创建产品集合与可选附加项
  4. 04向客户提供可下载的数字文件

MCP 相关

输入未说明该技能与 MCP 的关系。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dodopayments/skills --skill product-catalog-management

安装后在 Claude Code 中输入/product-catalog-management确认已加载。

详细介绍

产品目录管理

此技能涵盖完整的产品生命周期:创建采用不同定价模式的产品、管理附加项和集合、上传产品图片,以及向客户交付数字文件。

何时使用此技能

  • 你需要创建或更新采用一次性、定期或按使用量计费的产品。
  • 你正在构建产品集合或店面。
  • 你需要上传产品图片或向客户交付数字文件。
  • 你正在管理附加项或产品变体。
  • 你需要了解产品更新失败的原因,或如何更改产品定价。

核心概念

产品模型: 每个产品在创建时都必须且只能选择一种定价模型。三种模型分别为:

  • one_time_price:每次购买收取一次费用。
  • recurring_price:按可配置的付款频率和订阅周期收费。
  • usage_based_price:按消耗的单位收费(有关计量器设置,请参阅 usage-based-billing 技能)。

定价嵌套在产品对象中。不存在独立的顶层 Price 资源。

定价结构: 每个价格对象包含:

  • type:上述三种模型之一。
  • currency:ISO 4217 代码(例如 USD、AED、INR)。
  • price:以货币最小单位表示的金额(USD 以美分为单位)。
  • discount:可选折扣金额,单位相同。

税务类别: 创建产品时必须指定。Dodo 使用此项来计算并收取销售税。支持的值为 digital_products、saas、e_book 和 edtech。

生命周期: 产品支持 list、retrieve、update 和 archive/unarchive。没有删除端点。已归档的产品仍保留在历史记录中,但不会出现在新的结账流程中。

图片: 预签名上传 URL 会在 60 秒后过期。请求 URL 后应立即上传。

数字交付: 权益可授予客户访问文件的权限。下载 URL 大约在 15 分钟后过期。

创建产品

所有产品都必须指定名称、定价模型和税务类别。

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'test_mode',
});

// One-time purchase
const product = await client.products.create({
  name: 'Pro Bundle',
  tax_category: 'digital_products',
  price: {
    type: 'one_time_price',
    currency: 'USD',
    price: 9900, // $99.00
    discount: 0,
    purchasing_power_parity: false,
  },
});

console.log(product.product_id); // pdt_...

对于定期产品,请指定计费周期:

const subscription = await client.products.create({
  name: 'Premium Plan',
  tax_category: 'saas',
  price: {
    type: 'recurring_price',
    currency: 'USD',
    price: 2999, // $29.99/month
    discount: 0,
    payment_frequency_count: 1,
    payment_frequency_interval: 'Month',
    subscription_period_count: 1,
    subscription_period_interval: 'Month',
    purchasing_power_parity: false,
  },
});

此技能是产品创建请求格式的权威来源。对于按使用量计费的产品,请先按照 usage-based-billing 技能中的说明创建计量器,然后通过单数形式的 price.meters 数组将其关联:

const metered = await client.products.create({
  name: 'API Calls',
  tax_category: 'saas',
  price: {
    type: 'usage_based_price',
    currency: 'USD',
    discount: 0,
    fixed_price: 0, // No fixed monthly charge; amounts use the smallest currency unit
    payment_frequency_count: 1,
    payment_frequency_interval: 'Month',
    subscription_period_count: 1,
    subscription_period_interval: 'Month',
    purchasing_power_parity: false,
    meters: [
      {
        meter_id: 'mtr_api_calls',
        price_per_unit: '0.01',
        free_threshold: 1000,
      },
    ],
  },
});

price_per_unit 是一个十进制字符串,单位为所配置货币的最小单位。可选的 free_threshold 会在开始按单位收费前,排除相应数量的聚合单位。

列出和检索产品

// List all products
const products = await client.products.list();

// Retrieve a single product
const product = await client.products.retrieve('pdt_pro_bundle');

更新产品

PATCH /products/{id}(client.products.update)接受名称、描述和元数据,也接受 price、tax_category 和 pricing_mode。所有字段均为可选;未提供的字段保持不变。

有两项限制。定价模型不可更改:你可以修改金额,但不能将一次性产品改为订阅产品或按使用量计费的产品(反之亦然)——请改为创建新产品。并且,修改价格只会影响新客户的付款金额:现有订阅会保留创建时的价格,因此需要通过 Change Plan(client.subscriptions.changePlan(...))明确迁移这些订阅。

await client.products.update('pdt_pro_bundle', {
  name: 'Pro Bundle (Updated)',
  description: 'Includes all pro features',
  metadata: {
    category: 'software',
    tier: 'premium',
  },
});

归档和取消归档

归档产品可将其从新的结账流程中隐藏,而不会删除:

await client.products.archive('pdt_pro_bundle');

// Restore it later
await client.products.unarchive('pdt_pro_bundle');

产品图片

通过预签名 URL 上传产品图片。该 URL 的有效期为 60 秒。

import fs from 'node:fs';

// Request a presigned upload URL
const uploadUrl = await client.products.images.update('pdt_pro_bundle', {
  force_update: true,
});

// Upload immediately (within 60 seconds)
const response = await fetch(uploadUrl.url, {
  method: 'PUT',
  body: fs.readFileSync('./product.png'),
  headers: { 'Content-Type': 'image/png' },
});

if (!response.ok) {
  throw new Error('Image upload failed');
}

附加项

附加项是客户可与产品一同购买的可选额外商品。请单独创建,然后在结账时引用。

// Create an add-on
const addon = await client.addons.create({
  name: 'Priority Support',
  tax_category: 'saas',
  currency: 'USD',
  price: 4900, // $49.00
});

// Upload an image for the add-on
const addonImageUrl = await client.addons.updateImages(addon.id);

// List add-ons
const addons = await client.addons.list();

产品集合

将产品分组到集合中,供主题式结账使用。集合包含嵌套的组和商品。

// Create a collection
const collection = await client.productCollections.create({
  name: 'Starter Bundle',
  description: 'Everything you need to get started',
  groups: [
    {
      group_name: 'Core Tools',
      products: [
        { product_id: 'pdt_tool_a' },
        { product_id: 'pdt_tool_b' },
      ],
    },
  ],
});

// Add another group later
await client.productCollections.groups.create(collection.id, {
  group_name: 'Optional Tools',
  products: [
    { product_id: 'pdt_tool_c' },
    { product_id: 'pdt_tool_d' },
  ],
});

// Server route (e.g. POST /api/checkout): create the checkout session and return its URL
export async function POST() {
  const session = await client.checkoutSessions.create({
    product_collection_id: collection.id,
    product_cart: [], // Required: pass an empty array for collection checkout
    return_url: 'https://yoursite.com/return',
  });

  return Response.json({ checkout_url: session.checkout_url });
}

切勿在浏览器代码中调用 SDK 或暴露 API 密钥。浏览器只会从你的服务端路由接收 URL 并进行重定向:

// Browser
const res = await fetch('/api/checkout', { method: 'POST' });
const { checkout_url } = (await res.json()) as { checkout_url: string };
window.location.href = checkout_url;

数字产品交付

权益可授予客户下载文件的权限。请创建权益、上传文件并生成下载链接。

// openAsBlob requires Node.js 20 or later
import { openAsBlob } from 'node:fs';

// Create an entitlement
const entitlement = await client.entitlements.create({
  name: 'Pro Bundle Files',
  integration_type: 'digital_files',
  integration_config: {
    digital_file_ids: [],
  },
});

// Upload and attach a file as multipart/form-data (append `filename` before `file`)
const form = new FormData();
form.append('filename', 'pro-bundle.zip');
form.append('file', await openAsBlob('./pro-bundle.zip'), 'pro-bundle.zip');
const { file_id } = await client.entitlements.files.upload(entitlement.id, { body: form });

// Attach the entitlement to a product.
// `entitlements` REPLACES every entitlement already attached to the product:
// include all of them, send [] to remove all, or omit the field to leave them unchanged.
await client.products.update('pdt_pro_bundle', {
  entitlements: [{ entitlement_id: entitlement.id }],
});

// A grant is produced by fulfillment when the customer purchases the product.
// Grants can be listed, revoked, or fulfilled with a manually managed license key;
// they cannot be created directly through the SDK.

// List grants for a customer
const grants = await client.customers.listEntitlementGrants('cus_customer_123');

// Generate a download URL (valid for roughly 15 minutes)
const grant = grants.items[0];
const downloadUrl = grant?.digital_product_delivery?.files[0]?.download_url;

短链接

创建可分享的产品短链接:

const shortLink = await client.products.shortLinks.create('pdt_pro_bundle', {
  slug: 'pro-bundle-deal',
});

console.log(shortLink.short_url); // https://dodo.link/pro-bundle-deal

本地化定价

如需按国家或货币定价,请使用 localized-pricing skill。不要在客户端中硬编码价格。

常见错误

期待存在删除端点: 产品没有删除方法。请改为归档。已归档的产品会保留在历史记录中,以便对账。

尝试更改定价模式: 你无法在一次性、订阅和按用量计费之间切换产品的定价模式(例如,从 one_time_price 切换为 recurring_price)。请使用所需模式创建新产品。

以为编辑价格会重新为现有订阅者定价: client.products.update(...) 只会更改新客户的价格。现有订阅会按创建时的价格续订;请通过 client.subscriptions.changePlan(...) 为其更换方案。

在客户端中硬编码价格: 始终从 API 获取产品目录。价格会变化,客户端代码也会因此过时。调用 client.products.retrieve(id) 获取当前价格。

忘记税务类别: 每个产品都必须设置 tax_category。Dodo 会用它来计算并缴纳销售税。省略该字段会导致创建请求失败。

在 60 秒有效期后上传图片: 请求预签名 URL 后立即上传。如果 URL 过期,请重新请求。

在合集结账时传入非空的 product_cart: 合集结账要求 product_cart: []。购物车内容由合集本身定义。

混淆购买力平价与本地化定价: PPP 是原生功能。设置 price.purchasing_power_parity: true,即可按国家对价格应用适用于整个业务的百分比(默认为 false)。此功能要求启用 Adaptive Currency;如果存在匹配的本地化价格,则始终优先使用该价格。请参阅 localized-pricing skill。

资源

评论

支持 Markdown