PAYATHON 2026

SubscriptionPurchase.Get 响应缺少 cancelSurveyResult

数据小陈

结论

cancelReason: 0 表示用户主动取消了订阅,但响应中不一定有 cancelSurveyResult。

只有当 Google Play 的取消流程收集并保存了问卷结果时,才会返回 cancelSurveyResult。如果用户没有填写、跳过了问卷,或者当前取消入口没有生成问卷数据,这个字段可能完全不存在。

userInputCancelReason 的返回条件更严格。通常,用户需要在取消问卷中选择“其他”,也就是 cancelSurveyReason: 0,并填写自定义文字,响应中才可能出现该字段。顶层的 cancelReason: 0 和问卷中的 cancelSurveyReason: 0 含义不同,不要混淆。

字段之间的关系

这些字段可以分为三个层级:

cancelReason == 0
└── 用户主动取消
    └── cancelSurveyResult(仅在实际取得问卷结果时存在)
        ├── cancelSurveyReason
        └── userInputCancelReason(选择“其他”并输入文字时才可能存在)

只有顶层字段时,响应可能是:

{
  "cancelReason": 0,
  "userCancellationTimeMillis": "1615196648401"
}

这已经可以说明订阅由用户主动取消,并记录了取消时间,但不表示用户提交过取消问卷。

如果用户选择了预定义原因,响应可能类似:

{
  "cancelReason": 0,
  "userCancellationTimeMillis": "1615196648401",
  "cancelSurveyResult": {
    "cancelSurveyReason": 3
  }
}

这里没有 userInputCancelReason 很正常,因为用户选择的是预定义选项。

用户选择“其他”并填写说明后,才可能返回完整结构:

{
  "cancelReason": 0,
  "userCancellationTimeMillis": "1615196648401",
  "cancelSurveyResult": {
    "cancelSurveyReason": 0,
    "userInputCancelReason": "暂时不再需要该服务"
  }
}

因此,文档中的 “Only present if cancelReason is 0” 表达的是必要条件,而非充分条件。在 SubscriptionCancelSurveyResult 的上下文中,还需要确认这里的 cancelReason 是否指问卷原因值,而不是顶层的 SubscriptionPurchase.cancelReason。

建议的处理方式

收到 SUBSCRIPTION_CANCELED 后,应将 RTDN 视为订阅状态已发生变化的通知,再使用购买令牌查询最新状态。不要假定每种通知类型都会对应一组固定且完整的 JSON 字段。

处理取消数据时:

  1. 根据顶层的 cancelReason 判断取消类别。
  2. 使用 userCancellationTimeMillis 记录用户取消时间。
  3. 将 cancelSurveyResult 和 userInputCancelReason 都按可选字段解析。
  4. 字段缺失时,将其保存为 null、unknown 或“不详”,不要因此判定接口调用失败。
  5. 如果在 RTDN 到达后立即查询,可以延迟并重试有限次数,以排除短暂的数据同步延迟。重试后字段仍然缺失,也属于合法结果。
  6. 不要根据取消问卷控制用户权益。用户取消自动续订后,订阅通常仍可使用到 expiryTimeMillis。

代码示例

下面的 JavaScript 示例将问卷字段作为可选数据处理:

function parseCancellation(subscription) {
  if (subscription.cancelReason !== 0) {
    return {
      cancelledByUser: false,
      cancellationTimeMillis: null,
      surveyReason: null,
      userInputReason: null
    };
  }

  const survey = subscription.cancelSurveyResult;

  return {
    cancelledByUser: true,
    cancellationTimeMillis:
      subscription.userCancellationTimeMillis ?? null,
    surveyReason:
      survey?.cancelSurveyReason ?? null,
    userInputReason:
      survey?.userInputCancelReason ?? null
  };
}

业务逻辑还可以区分“没有问卷”和“选择了其他但未返回文字”:

const result = parseCancellation(subscription);

if (result.cancelledByUser) {
  if (result.surveyReason === null) {
    console.log("用户已取消,但 Google Play 未返回取消问卷");
  } else if (result.surveyReason === 0) {
    console.log(
      "用户选择了其他原因:",
      result.userInputReason ?? "未提供文字说明"
    );
  } else {
    console.log("用户选择的取消原因代码:", result.surveyReason);
  }
}

注意事项

  • SUBSCRIPTION_CANCELED 表示自动续订已取消,不代表用户会立即失去订阅权益。
  • autoRenewing: false 表示订阅当前不会自动续订。实际有效期仍应以 expiryTimeMillis 等订阅状态字段为准。
  • RTDN 的作用主要是通知服务端重新查询订阅状态,不保证携带完整的业务信息。
  • 取消问卷不是可靠的必填数据源,不应作为结算、权限控制或关键审计逻辑的依据。
  • 如果需要稳定收集取消原因,可以在应用自己的取消或反馈流程中单独收集,但仍不能假定用户一定会经过应用内流程。

备注:内容仅供参考。