如何在 ThriveCart Test Mode 测试订阅创建与取消
结论
ThriveCart Test Mode 会生成用于测试的订单和订阅记录。创建成功后,通常可以直接测试取消订阅,无需切换到真实付款环境。
You must provide matching customer IDs. 通常不代表 Test Mode 没有创建订阅。它表示 ThriveCart 根据 order_id 和 subscription_id 找到的记录不属于同一个测试客户或同一次购买。还有一种常见情况:创建和取消请求使用了不同模式。例如,订单来自 Test Mode,但取消请求的 X-TC-Mode 实际上传成了 live。
取消订阅时需要同时满足以下条件:
order_id来自创建该订阅的同一笔订单。subscription_id来自该订单对应的订阅。- 两个值都取自 ThriveCart 返回的数据,不能使用 Laravel 本地订单号或套餐 ID。
- 取消请求使用的 Test Mode 与下单时一致。
- 测试购买的商品包含 recurring pricing,而不是一次性付款。
为什么会出现 customer IDs 不匹配
取消接口虽然不要求直接提交 customer_id,但 ThriveCart 会在后台根据 order_id 和 subscription_id 分别查找订单、订阅及其客户,再检查它们是否属于同一个客户。
以下情况都可能触发这个错误:
order_id是 ThriveCart 订单号,但subscription_id来自另一笔测试购买。- 数据库保存的是应用自己的订单主键,而不是 ThriveCart 返回的
order_id。 - 代码将产品 ID、套餐 ID、invoice ID 或 transaction ID 当成了
subscription_id。 - 同一测试邮箱购买过多次,代码把旧订阅和新订单组合到了一起。
- 创建订阅时使用 Test Mode,取消时却发送了 live 模式头。
- webhook 被重复处理或处理顺序发生变化,导致原本正确的 ID 被后续事件覆盖。
如果订阅完全不存在,接口更可能返回找不到订阅之类的错误。当前错误明确提到 matching customer IDs,因此应该先检查两个 ID 的来源,以及它们是否正确配对。
正确的测试流程
1. 确认测试商品是订阅商品
ThriveCart 中的产品或价格选项必须配置为周期性收费。一次性付款即使能够生成成功订单,也不会创建可供取消的 recurring subscription。
还要确认结账页面处于 Test Mode,并使用 ThriveCart 提供或当前支付处理器支持的测试付款信息。
2. 从同一次 webhook 中保存标识符
收到购买成功或订阅创建事件后,保存 ThriveCart 返回的原始字段:
$purchase->thrivecart_order_id = $payload['order_id'] ?? null;
$purchase->thrivecart_subscription_id = $payload['subscription_id'] ?? null;
$purchase->thrivecart_mode = 'test';
$purchase->save();
这些字段的实际位置可能因 webhook 类型和当前 ThriveCart payload 结构而异。如果字段位于嵌套对象中,应按照实际收到的 JSON 读取,不要假设它们一定在顶层。
调试时可以记录脱敏后的 payload 结构:
Log::info('ThriveCart webhook identifiers', [
'event' => $payload['event'] ?? null,
'order_id' => $payload['order_id'] ?? null,
'subscription_id' => $payload['subscription_id'] ?? null,
'mode' => $payload['mode'] ?? null,
]);
不要记录 API key、完整支付信息或其他敏感数据。
3. 检查数据库中的 ID 是否成对保存
调用取消接口前,至少要验证:
if (
empty($purchase->thrivecart_order_id) ||
empty($purchase->thrivecart_subscription_id)
) {
throw new RuntimeException(
'Missing ThriveCart order_id or subscription_id.'
);
}
如果同一用户有多次购买,不要只通过邮箱查询”最新订单”,再拼接另一条订阅记录。更稳妥的做法是让每条本地购买记录同时绑定 ThriveCart 的 order_id 和 subscription_id。
4. 使用一致的 Test Mode 调用取消接口
可以将模式明确固定为 test,以免环境变量配置错误:
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$client = new Client([
'base_uri' => 'https://thrivecart.com',
'timeout' => 15,
]);
try {
$response = $client->post('/api/external/cancelSubscription', [
'headers' => [
'Authorization' => 'Bearer ' . $apiKey,
'X-TC-Mode' => 'test',
'Accept' => 'application/json',
],
'form_params' => [
'order_id' => $purchase->thrivecart_order_id,
'subscription_id' => $purchase->thrivecart_subscription_id,
],
]);
$result = json_decode(
(string) $response->getBody(),
true,
512,
JSON_THROW_ON_ERROR
);
Log::info('ThriveCart subscription cancellation response', [
'order_id' => $purchase->thrivecart_order_id,
'subscription_id' => $purchase->thrivecart_subscription_id,
'response' => $result,
]);
} catch (RequestException $e) {
$body = $e->hasResponse()
? (string) $e->getResponse()->getBody()
: null;
Log::error('ThriveCart subscription cancellation failed', [
'order_id' => $purchase->thrivecart_order_id,
'subscription_id' => $purchase->thrivecart_subscription_id,
'status' => $e->hasResponse()
? $e->getResponse()->getStatusCode()
: null,
'response' => $body,
]);
throw $e;
}
X-TC-Mode 的具体取值和大小写应以当前 ThriveCart API 文档及账户配置为准。如果创建请求使用的不是字面值 test,取消请求也必须使用相同的有效值。
原代码中的:
?passthrough[type]=cancel
通常不是用来指定取消操作的,因为请求路径本身已经是 cancelSubscription。除非业务确实需要 passthrough 数据,否则可以先移除它,减少排查变量。它通常也不是 customer ID 不匹配的直接原因。
建议的排查顺序
先在 ThriveCart Dashboard 中打开这笔 Test Mode 购买,确认它确实显示为订阅交易。然后逐项核对 Dashboard、创建 webhook 和数据库中的以下内容:
mode
order_id
subscription_id
customer/email
product
purchase time
重点确认 order_id 和 subscription_id 来自同一次 webhook 或同一笔购买。取消前还可以增加一条日志:
Log::info('Canceling ThriveCart subscription', [
'mode' => 'test',
'order_id' => $purchase->thrivecart_order_id,
'subscription_id' => $purchase->thrivecart_subscription_id,
]);
取消完成后,不要只根据 HTTP 状态码更新本地订阅状态。应解析接口响应,并结合 ThriveCart 后续 webhook 或 Dashboard 状态确认取消结果。同时注意区分”立即取消”和”在当前计费周期结束时取消”,具体行为取决于 ThriveCart 当前接口及产品配置。
如果模式一致,而且两个 ID 确实来自同一笔测试购买,但接口仍然返回相同错误,应保存完整响应、请求时间和对应的测试交易信息,再交给 ThriveCart 支持团队核对。不要为了绕过错误而自行猜测或提交 customer_id,除非当前官方 API 文档明确要求这个参数。
备注:内容仅供参考。
