qiwei-subscription-run.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. const { readQiweiAuthToken, readQiweiApiBase } = require('../core/credentials');
  2. const { callFmodeWecomGateway, redactSecret } = require('../providers/fmode-wecom-gateway');
  3. const { okResult, errorResult } = require('../core/result-envelope');
  4. function authRequiredResult() {
  5. return {
  6. status: 'needs_auth',
  7. assistantMessage: '还没有找到 Fmode 鉴权 token,无法调用由 Fmode 网关转发的企业微信接口。请配置 QIWEI_AUTH_TOKEN、FMODE_API_KEY 或平台 sessionToken 后重试。',
  8. summary: { errorKind: 'missing_auth_token', recoverable: true },
  9. data: {},
  10. files: [],
  11. nextActions: ['配置 Fmode 鉴权 token 后重试'],
  12. warnings: [],
  13. errors: []
  14. };
  15. }
  16. function subscriptionError(error, stage) {
  17. const safeMessage = redactSecret(error && (error.bizMessage || error.message));
  18. const kind = String((error && error.kind) || 'upstream');
  19. if (kind === 'auth') return authRequiredResult();
  20. if (kind === 'billing') {
  21. return {
  22. status: 'needs_recharge',
  23. assistantMessage: `飞马余额不足或订阅扣费失败(${safeMessage})。请充值后重新调用 qiwei_subscribe。`,
  24. summary: { stage, errorKind: kind, recoverable: true },
  25. data: {},
  26. files: [],
  27. nextActions: ['充值飞马余额', '重新调用 qiwei_subscribe'],
  28. warnings: [],
  29. errors: []
  30. };
  31. }
  32. return errorResult(`企微订阅操作失败:${safeMessage}`, {
  33. summary: { stage, errorKind: kind },
  34. nextActions: ['稍后重试', '若持续失败,请检查 Fmode 网关的企业微信接口状态']
  35. });
  36. }
  37. async function qiweiSubscriptionStatus(input = {}) {
  38. const token = readQiweiAuthToken(input);
  39. if (!token) return authRequiredResult();
  40. try {
  41. const apiBase = readQiweiApiBase(input);
  42. let trial = null;
  43. try {
  44. const trialResult = await callFmodeWecomGateway({
  45. gatewayPath: '/trial/status',
  46. httpMethod: 'GET',
  47. token,
  48. apiBase,
  49. cacheBust: true
  50. });
  51. trial = trialResult.data || null;
  52. } catch {
  53. // Older gateways may not expose trial/status; subscription/status remains authoritative.
  54. }
  55. const result = await callFmodeWecomGateway({
  56. gatewayPath: '/subscribe/status',
  57. httpMethod: 'GET',
  58. token,
  59. apiBase,
  60. cacheBust: true
  61. });
  62. const data = result.data || {};
  63. // `active` also means a paid subscription is currently valid. Only the
  64. // entitlement-specific state identifies a live trial.
  65. const trialActive = trial?.state === 'active';
  66. const subscribed = Boolean(data.subscribed || trialActive);
  67. const seats = data.seats ?? trial?.seats ?? null;
  68. const expireAt = data.expireAt || trial?.expireAt || null;
  69. return okResult({
  70. assistantMessage: trialActive
  71. ? `企微 7 天试用有效:${data.usedSeats ?? 0}/${seats ?? 0} 个席位已使用,到期时间 ${expireAt || '未知'}。`
  72. : subscribed
  73. ? `企微服务有效:${data.usedSeats ?? 0}/${seats ?? 0} 个席位已使用,到期时间 ${expireAt || '未知'}。`
  74. : '企微服务尚未开通或已到期,请调用 qiwei_subscribe 开通或续费。',
  75. summary: {
  76. subscribed,
  77. trialActive,
  78. source: trialActive ? 'trial' : 'subscription',
  79. seats,
  80. usedSeats: data.usedSeats ?? null,
  81. expireAt,
  82. autoRenew: data.autoRenew ?? null,
  83. balance: data.balance
  84. },
  85. data: { ...data, trial: trial || undefined, trialActive },
  86. nextActions: subscribed ? [] : ['调用 qiwei_subscribe 开通或续费']
  87. });
  88. } catch (error) {
  89. return subscriptionError(error, 'subscriptionStatus');
  90. }
  91. }
  92. async function qiweiSubscribe(input = {}) {
  93. const token = readQiweiAuthToken(input);
  94. if (!token) return authRequiredResult();
  95. const seats = input.seats === undefined ? undefined : Number(input.seats);
  96. const months = input.months === undefined ? 1 : Number(input.months);
  97. if (seats !== undefined && (!Number.isInteger(seats) || seats < 1)) {
  98. return errorResult('seats 必须是大于等于 1 的整数。');
  99. }
  100. if (!Number.isInteger(months) || months < 1 || months > 12) {
  101. return errorResult('months 必须是 1-12 的整数。');
  102. }
  103. try {
  104. const result = await callFmodeWecomGateway({
  105. gatewayPath: '/subscribe',
  106. body: { ...(seats === undefined ? {} : { seats }), months },
  107. token,
  108. apiBase: readQiweiApiBase(input)
  109. });
  110. const data = result.data || {};
  111. return okResult({
  112. assistantMessage: `企微包月订阅已开通或续费:${data.seats ?? seats ?? 1} 个席位,购买 ${data.months ?? months} 个月,到期时间 ${data.expireAt || '未知'}。`,
  113. summary: {
  114. subscribed: true,
  115. seats: data.seats ?? seats ?? 1,
  116. months: data.months ?? months,
  117. amount: data.amount ?? null,
  118. expireAt: data.expireAt || null,
  119. autoRenew: data.autoRenew ?? null
  120. },
  121. data,
  122. nextActions: ['调用 qiwei_login_start 添加或重新登录企业微信账号']
  123. });
  124. } catch (error) {
  125. return subscriptionError(error, 'subscribe');
  126. }
  127. }
  128. async function qiweiSubscriptionAutoRenew(input = {}) {
  129. const token = readQiweiAuthToken(input);
  130. if (!token) return authRequiredResult();
  131. if (typeof input.autoRenew !== 'boolean') return errorResult('autoRenew 必须是 boolean。');
  132. try {
  133. const result = await callFmodeWecomGateway({
  134. gatewayPath: '/subscribe/autoRenew',
  135. body: { autoRenew: input.autoRenew },
  136. token,
  137. apiBase: readQiweiApiBase(input)
  138. });
  139. const data = result.data || {};
  140. return okResult({
  141. assistantMessage: `企微订阅自动续费已${data.autoRenew ? '开启' : '关闭'}。`,
  142. summary: { autoRenew: Boolean(data.autoRenew) },
  143. data
  144. });
  145. } catch (error) {
  146. return subscriptionError(error, 'subscriptionAutoRenew');
  147. }
  148. }
  149. module.exports = {
  150. qiweiSubscriptionStatus,
  151. qiweiSubscribe,
  152. qiweiSubscriptionAutoRenew
  153. };