@rdlabo/workers-hono-kit 0.6.9 → 0.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,6 +40,18 @@ export interface AppleVerifyReceiptResponse {
40
40
  * - `unknown` — indeterminate (no receipt, or expired with auto-renew on and no retry) → no-op.
41
41
  */
42
42
  export type AppleRenewalState = 'billing_retry' | 'lapsed' | 'active' | 'unknown';
43
+ /** Apple renewal classification plus the exact provider fields used to reach it. */
44
+ export interface AppleRenewalClassification {
45
+ state: AppleRenewalState;
46
+ originalTransactionId?: string;
47
+ expiresDateMs?: string;
48
+ /** Apple verifyReceipt response status (normally 0 for a successfully verified receipt). */
49
+ statusCode?: number;
50
+ /** Value from the pending-renewal entry matched to `originalTransactionId`. */
51
+ billingRetryStatus?: string;
52
+ /** Value from the pending-renewal entry matched to `originalTransactionId`. */
53
+ autoRenewStatus?: string;
54
+ }
43
55
  /**
44
56
  * Classify an Apple verifyReceipt response into an {@link AppleRenewalState}.
45
57
  *
@@ -52,11 +64,7 @@ export type AppleRenewalState = 'billing_retry' | 'lapsed' | 'active' | 'unknown
52
64
  * @param now - Current epoch-ms (`Date.now()`); injected for determinism/testing.
53
65
  * @returns The state plus the latest `original_transaction_id` / `expires_date_ms` (for the row key).
54
66
  */
55
- export declare function classifyAppleRenewal(verify: unknown, now: number): {
56
- state: AppleRenewalState;
57
- originalTransactionId?: string;
58
- expiresDateMs?: string;
59
- };
67
+ export declare function classifyAppleRenewal(verify: unknown, now: number): AppleRenewalClassification;
60
68
  /**
61
69
  * Verify an App Store receipt against Apple, falling back from production to sandbox.
62
70
  *
package/dist/iap/apple.js CHANGED
@@ -23,6 +23,7 @@ const toArray = (v) => (Array.isArray(v) ? v : []);
23
23
  */
24
24
  export function classifyAppleRenewal(verify, now) {
25
25
  const v = asRecord(verify);
26
+ const statusCode = typeof v?.status === 'number' ? v.status : undefined;
26
27
  // 消耗型など expires_date_ms 欠損エントリを除外してから最新(max expires)を選ぶ。
27
28
  // 欠損値を Number() すると NaN で比較が常に false になり、先頭の欠損行が最後まで残って
28
29
  // 誤って 'active'(isExpired=false)を返すため、有効な expires を持つ行だけを対象にする。
@@ -35,7 +36,7 @@ export function classifyAppleRenewal(verify, now) {
35
36
  }, undefined);
36
37
  const originalTransactionId = latest?.original_transaction_id;
37
38
  if (!latest || !originalTransactionId) {
38
- return { state: 'unknown' };
39
+ return { state: 'unknown', statusCode };
39
40
  }
40
41
  // 複数サブスク商品時に別商品の renewal info を読まないよう、latest と同じ original_transaction_id の
41
42
  // pending エントリを優先(無ければ先頭にフォールバック)。
@@ -43,7 +44,13 @@ export function classifyAppleRenewal(verify, now) {
43
44
  const pending = pendingArr.find((p) => p.original_transaction_id === originalTransactionId) ?? pendingArr.at(0);
44
45
  const expiresMs = Number(latest.expires_date_ms);
45
46
  const isExpired = Number.isFinite(expiresMs) && expiresMs < now;
46
- const base = { originalTransactionId, expiresDateMs: latest.expires_date_ms };
47
+ const base = {
48
+ originalTransactionId,
49
+ expiresDateMs: latest.expires_date_ms,
50
+ statusCode,
51
+ billingRetryStatus: pending?.is_in_billing_retry_period,
52
+ autoRenewStatus: pending?.auto_renew_status,
53
+ };
47
54
  if (pending?.is_in_billing_retry_period === '1') {
48
55
  return { state: 'billing_retry', ...base };
49
56
  }
@@ -36,15 +36,21 @@ export interface GoogleSubscriptionPurchase {
36
36
  * a member is semi-breaking for exhaustive `switch` consumers).
37
37
  */
38
38
  export type GoogleSubscriptionState = 'canceled' | 'gone' | 'active' | 'unknown';
39
+ /** Google subscription classification plus provider diagnostic codes used by persistence. */
40
+ export interface GoogleSubscriptionClassification {
41
+ state: GoogleSubscriptionState;
42
+ /** Google error-body code, such as 410. */
43
+ statusCode?: number;
44
+ /** Google cancellation reason (0=user, 1=system, 2=replaced, 3=developer). */
45
+ cancelReason?: number;
46
+ }
39
47
  /**
40
48
  * Classify a Google subscriptions.get response into a {@link GoogleSubscriptionState}.
41
49
  *
42
50
  * @param purchase - The response body (duck-typed); may be `{ error: { code } }`.
43
51
  * @param now - Current epoch-ms (`Date.now()`); injected for determinism/testing.
44
52
  */
45
- export declare function classifyGoogleSubscription(purchase: unknown, now: number): {
46
- state: GoogleSubscriptionState;
47
- };
53
+ export declare function classifyGoogleSubscription(purchase: unknown, now: number): GoogleSubscriptionClassification;
48
54
  /** OAuth service-account credentials for the Android Publisher API (per-app; inject, never hardcode in kit). */
49
55
  export interface GoogleOAuthCredentials {
50
56
  client_id: string;
@@ -22,18 +22,21 @@ export function classifyGoogleSubscription(purchase, now) {
22
22
  const err = asRecord(p.error);
23
23
  if (err) {
24
24
  // 410 = "The subscription purchase is no longer available for query" (expired too long) = churned.
25
- return { state: err.code === 410 ? 'gone' : 'unknown' };
25
+ const statusCode = typeof err.code === 'number' ? err.code : undefined;
26
+ return { state: statusCode === 410 ? 'gone' : 'unknown', statusCode };
26
27
  }
27
28
  const expiryMs = Number(p.expiryTimeMillis);
28
- const isExpired = Number.isFinite(expiryMs) && expiryMs < now;
29
- const willNotRenew = p.autoRenewing === false || p.cancelReason !== undefined;
29
+ const hasValidExpiry = Number.isFinite(expiryMs);
30
+ const isExpired = hasValidExpiry && expiryMs < now;
31
+ const cancelReason = typeof p.cancelReason === 'number' ? p.cancelReason : undefined;
32
+ const willNotRenew = p.autoRenewing === false || cancelReason !== undefined;
30
33
  if (isExpired && willNotRenew) {
31
- return { state: 'canceled' };
34
+ return { state: 'canceled', cancelReason };
32
35
  }
33
- if (!isExpired) {
34
- return { state: 'active' };
36
+ if (hasValidExpiry && !isExpired) {
37
+ return { state: 'active', cancelReason };
35
38
  }
36
- return { state: 'unknown' };
39
+ return { state: 'unknown', cancelReason };
37
40
  }
38
41
  /**
39
42
  * Exchange a refresh token for an Android Publisher access token.
package/dist/index.d.ts CHANGED
@@ -54,9 +54,9 @@ export type { StripeReconcileAction } from './stripe/reconcile.js';
54
54
  export { paymentFailureMessageJa, iapFailureKey, UNRESOLVED_PAYMENT_STATUSES } from './payment/failure.js';
55
55
  export type { PaymentFailureStatus, PaymentFailureType } from './payment/failure.js';
56
56
  export { classifyAppleRenewal, verifyAppleReceipt } from './iap/apple.js';
57
- export type { AppleRenewalState, AppleVerifyReceiptResponse, ApplePendingRenewalInfo, AppleLatestReceiptInfo, } from './iap/apple.js';
57
+ export type { AppleRenewalClassification, AppleRenewalState, AppleVerifyReceiptResponse, ApplePendingRenewalInfo, AppleLatestReceiptInfo, } from './iap/apple.js';
58
58
  export { classifyGoogleSubscription, getGoogleSubscription, googleAccessToken } from './iap/google.js';
59
- export type { GoogleSubscriptionState, GoogleSubscriptionPurchase, GoogleOAuthCredentials } from './iap/google.js';
59
+ export type { GoogleSubscriptionClassification, GoogleSubscriptionState, GoogleSubscriptionPurchase, GoogleOAuthCredentials, } from './iap/google.js';
60
60
  export { retryWhenDeadlock } from './db/retry.js';
61
61
  export { sendInChunks } from './queue/send.js';
62
62
  export type { QueueLike, QueueSendMessage } from './queue/send.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rdlabo/workers-hono-kit",
3
- "version": "0.6.9",
3
+ "version": "0.6.10",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"