@molecule/api-resource-user 1.0.0

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.
Files changed (164) hide show
  1. package/LICENSE +115 -0
  2. package/dist/authorization.d.ts +107 -0
  3. package/dist/authorization.d.ts.map +1 -0
  4. package/dist/authorization.js +375 -0
  5. package/dist/authorization.js.map +1 -0
  6. package/dist/authorizers/auth.d.ts +8 -0
  7. package/dist/authorizers/auth.d.ts.map +1 -0
  8. package/dist/authorizers/auth.js +19 -0
  9. package/dist/authorizers/auth.js.map +1 -0
  10. package/dist/authorizers/authSelf.d.ts +14 -0
  11. package/dist/authorizers/authSelf.d.ts.map +1 -0
  12. package/dist/authorizers/authSelf.js +31 -0
  13. package/dist/authorizers/authSelf.js.map +1 -0
  14. package/dist/authorizers/index.d.ts +5 -0
  15. package/dist/authorizers/index.d.ts.map +1 -0
  16. package/dist/authorizers/index.js +5 -0
  17. package/dist/authorizers/index.js.map +1 -0
  18. package/dist/authorizers/rateLimit.d.ts +84 -0
  19. package/dist/authorizers/rateLimit.d.ts.map +1 -0
  20. package/dist/authorizers/rateLimit.js +144 -0
  21. package/dist/authorizers/rateLimit.js.map +1 -0
  22. package/dist/authorizers/requireWebhookAuthenticity.d.ts +23 -0
  23. package/dist/authorizers/requireWebhookAuthenticity.d.ts.map +1 -0
  24. package/dist/authorizers/requireWebhookAuthenticity.js +76 -0
  25. package/dist/authorizers/requireWebhookAuthenticity.js.map +1 -0
  26. package/dist/browser-guard.d.ts +2 -0
  27. package/dist/browser-guard.d.ts.map +1 -0
  28. package/dist/browser-guard.js +19 -0
  29. package/dist/browser-guard.js.map +1 -0
  30. package/dist/handlers/create.d.ts +52 -0
  31. package/dist/handlers/create.d.ts.map +1 -0
  32. package/dist/handlers/create.js +208 -0
  33. package/dist/handlers/create.js.map +1 -0
  34. package/dist/handlers/del.d.ts +30 -0
  35. package/dist/handlers/del.d.ts.map +1 -0
  36. package/dist/handlers/del.js +55 -0
  37. package/dist/handlers/del.js.map +1 -0
  38. package/dist/handlers/forgotPassword.d.ts +34 -0
  39. package/dist/handlers/forgotPassword.d.ts.map +1 -0
  40. package/dist/handlers/forgotPassword.js +119 -0
  41. package/dist/handlers/forgotPassword.js.map +1 -0
  42. package/dist/handlers/index.d.ts +16 -0
  43. package/dist/handlers/index.d.ts.map +1 -0
  44. package/dist/handlers/index.js +16 -0
  45. package/dist/handlers/index.js.map +1 -0
  46. package/dist/handlers/logIn.d.ts +91 -0
  47. package/dist/handlers/logIn.d.ts.map +1 -0
  48. package/dist/handlers/logIn.js +223 -0
  49. package/dist/handlers/logIn.js.map +1 -0
  50. package/dist/handlers/logInOAuth.d.ts +66 -0
  51. package/dist/handlers/logInOAuth.d.ts.map +1 -0
  52. package/dist/handlers/logInOAuth.js +462 -0
  53. package/dist/handlers/logInOAuth.js.map +1 -0
  54. package/dist/handlers/logout.d.ts +22 -0
  55. package/dist/handlers/logout.d.ts.map +1 -0
  56. package/dist/handlers/logout.js +57 -0
  57. package/dist/handlers/logout.js.map +1 -0
  58. package/dist/handlers/oauthAuthorize.d.ts +44 -0
  59. package/dist/handlers/oauthAuthorize.d.ts.map +1 -0
  60. package/dist/handlers/oauthAuthorize.js +141 -0
  61. package/dist/handlers/oauthAuthorize.js.map +1 -0
  62. package/dist/handlers/payments/handlePaymentNotification.d.ts +28 -0
  63. package/dist/handlers/payments/handlePaymentNotification.d.ts.map +1 -0
  64. package/dist/handlers/payments/handlePaymentNotification.js +217 -0
  65. package/dist/handlers/payments/handlePaymentNotification.js.map +1 -0
  66. package/dist/handlers/payments/registry.d.ts +13 -0
  67. package/dist/handlers/payments/registry.d.ts.map +1 -0
  68. package/dist/handlers/payments/registry.js +13 -0
  69. package/dist/handlers/payments/registry.js.map +1 -0
  70. package/dist/handlers/payments/verifyPayment.d.ts +37 -0
  71. package/dist/handlers/payments/verifyPayment.d.ts.map +1 -0
  72. package/dist/handlers/payments/verifyPayment.js +383 -0
  73. package/dist/handlers/payments/verifyPayment.js.map +1 -0
  74. package/dist/handlers/read.d.ts +17 -0
  75. package/dist/handlers/read.d.ts.map +1 -0
  76. package/dist/handlers/read.js +60 -0
  77. package/dist/handlers/read.js.map +1 -0
  78. package/dist/handlers/readSelf.d.ts +23 -0
  79. package/dist/handlers/readSelf.d.ts.map +1 -0
  80. package/dist/handlers/readSelf.js +77 -0
  81. package/dist/handlers/readSelf.js.map +1 -0
  82. package/dist/handlers/resetPassword.d.ts +40 -0
  83. package/dist/handlers/resetPassword.d.ts.map +1 -0
  84. package/dist/handlers/resetPassword.js +147 -0
  85. package/dist/handlers/resetPassword.js.map +1 -0
  86. package/dist/handlers/update.d.ts +28 -0
  87. package/dist/handlers/update.d.ts.map +1 -0
  88. package/dist/handlers/update.js +250 -0
  89. package/dist/handlers/update.js.map +1 -0
  90. package/dist/handlers/updatePassword.d.ts +35 -0
  91. package/dist/handlers/updatePassword.d.ts.map +1 -0
  92. package/dist/handlers/updatePassword.js +113 -0
  93. package/dist/handlers/updatePassword.js.map +1 -0
  94. package/dist/handlers/updatePlan.d.ts +41 -0
  95. package/dist/handlers/updatePlan.d.ts.map +1 -0
  96. package/dist/handlers/updatePlan.js +242 -0
  97. package/dist/handlers/updatePlan.js.map +1 -0
  98. package/dist/handlers/verifyTwoFactor.d.ts +46 -0
  99. package/dist/handlers/verifyTwoFactor.d.ts.map +1 -0
  100. package/dist/handlers/verifyTwoFactor.js +162 -0
  101. package/dist/handlers/verifyTwoFactor.js.map +1 -0
  102. package/dist/i18n.d.ts +10 -0
  103. package/dist/i18n.d.ts.map +1 -0
  104. package/dist/i18n.js +25 -0
  105. package/dist/i18n.js.map +1 -0
  106. package/dist/index.d.ts +88 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +88 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/requestHandlerMap.d.ts +44 -0
  111. package/dist/requestHandlerMap.d.ts.map +1 -0
  112. package/dist/requestHandlerMap.js +68 -0
  113. package/dist/requestHandlerMap.js.map +1 -0
  114. package/dist/resource.d.ts +22 -0
  115. package/dist/resource.d.ts.map +1 -0
  116. package/dist/resource.js +23 -0
  117. package/dist/resource.js.map +1 -0
  118. package/dist/routes.d.ts +44 -0
  119. package/dist/routes.d.ts.map +1 -0
  120. package/dist/routes.js +121 -0
  121. package/dist/routes.js.map +1 -0
  122. package/dist/schema.d.ts +193 -0
  123. package/dist/schema.d.ts.map +1 -0
  124. package/dist/schema.js +229 -0
  125. package/dist/schema.js.map +1 -0
  126. package/dist/secrets.d.ts +16 -0
  127. package/dist/secrets.d.ts.map +1 -0
  128. package/dist/secrets.js +28 -0
  129. package/dist/secrets.js.map +1 -0
  130. package/dist/types.d.ts +14 -0
  131. package/dist/types.d.ts.map +1 -0
  132. package/dist/types.js +9 -0
  133. package/dist/types.js.map +1 -0
  134. package/dist/utilities/getPlan.d.ts +19 -0
  135. package/dist/utilities/getPlan.d.ts.map +1 -0
  136. package/dist/utilities/getPlan.js +26 -0
  137. package/dist/utilities/getPlan.js.map +1 -0
  138. package/dist/utilities/hashResetToken.d.ts +16 -0
  139. package/dist/utilities/hashResetToken.d.ts.map +1 -0
  140. package/dist/utilities/hashResetToken.js +19 -0
  141. package/dist/utilities/hashResetToken.js.map +1 -0
  142. package/dist/utilities/index.d.ts +5 -0
  143. package/dist/utilities/index.d.ts.map +1 -0
  144. package/dist/utilities/index.js +5 -0
  145. package/dist/utilities/index.js.map +1 -0
  146. package/dist/utilities/invalidateEntitlements.d.ts +24 -0
  147. package/dist/utilities/invalidateEntitlements.d.ts.map +1 -0
  148. package/dist/utilities/invalidateEntitlements.js +41 -0
  149. package/dist/utilities/invalidateEntitlements.js.map +1 -0
  150. package/dist/utilities/normalizeEmail.d.ts +22 -0
  151. package/dist/utilities/normalizeEmail.d.ts.map +1 -0
  152. package/dist/utilities/normalizeEmail.js +28 -0
  153. package/dist/utilities/normalizeEmail.js.map +1 -0
  154. package/dist/utilities/notify.d.ts +21 -0
  155. package/dist/utilities/notify.d.ts.map +1 -0
  156. package/dist/utilities/notify.js +39 -0
  157. package/dist/utilities/notify.js.map +1 -0
  158. package/dist/utilities/stripSensitiveUserColumns.d.ts +25 -0
  159. package/dist/utilities/stripSensitiveUserColumns.d.ts.map +1 -0
  160. package/dist/utilities/stripSensitiveUserColumns.js +43 -0
  161. package/dist/utilities/stripSensitiveUserColumns.js.map +1 -0
  162. package/package.json +94 -0
  163. package/setup/users.sql +22 -0
  164. package/setup/usersSecrets.sql +9 -0
@@ -0,0 +1,141 @@
1
+ import crypto from 'node:crypto';
2
+ import { get } from '@molecule/api-bond';
3
+ import { get as getConfig } from '@molecule/api-config';
4
+ import { t } from '@molecule/api-i18n';
5
+ import * as authorization from '../authorization.js';
6
+ /** TTL for the OAuth `state` + PKCE `verifier` cookies (one flow's lifetime). */
7
+ const OAUTH_COOKIE_MAX_AGE_MS = 5 * 60 * 1000;
8
+ /**
9
+ * Validate a client-supplied return path so the post-login redirect can only
10
+ * land somewhere on the app's own origin. Accepts a same-origin absolute path
11
+ * (`/login`), rejects anything protocol-relative (`//evil.com`), scheme-ful
12
+ * (`https://…`), or backslash-tricky — those would turn the `redirect_uri` we
13
+ * send the provider into an open redirect.
14
+ *
15
+ * @param value - The raw `redirect_to` query value.
16
+ * @returns The validated path, or `''` (the app origin root) when invalid/absent.
17
+ */
18
+ const sanitizeRedirectPath = (value) => {
19
+ if (typeof value !== 'string' || value === '') {
20
+ return '';
21
+ }
22
+ if (!value.startsWith('/') || value.startsWith('//') || value.includes('\\')) {
23
+ return '';
24
+ }
25
+ return value;
26
+ };
27
+ /**
28
+ * OAuth initiation — `GET /users/oauth/:provider`.
29
+ *
30
+ * The missing first half of the OAuth flow whose second half is
31
+ * `logInOAuth` (`POST /users/log-in/oauth`): generates the CSRF `state` and
32
+ * a per-session PKCE S256 verifier, stores BOTH in httpOnly cookies (the
33
+ * exact names + attributes `logInOAuth` reads and clears —
34
+ * `getAuthCookieName('oauth_state')` / `('oauth_verifier')` with
35
+ * `getAuthCookieOptions()`), then 302-redirects the browser to the
36
+ * provider's authorization URL.
37
+ *
38
+ * Provider knowledge stays in the bond: the handler asks the bonded OAuth
39
+ * provider (`get('oauth', :provider)`) for its authorize URL via its
40
+ * optional `getAuthorizeUrl` builder (see `@molecule/api-oauth`). A
41
+ * provider that is not bonded, lacks the builder, or is unconfigured
42
+ * (builder returns `null`, e.g. missing client id) yields a clean 404/503 —
43
+ * never a crash.
44
+ *
45
+ * The `redirect_uri` sent to the provider is `APP_ORIGIN` plus an optional
46
+ * validated same-origin `redirect_to` path from the query string (the page
47
+ * that initiated the flow, e.g. `/login`, where the app's OAuth callback
48
+ * handling is mounted). The path is sanitized so this can never become an
49
+ * open redirect; the state cookie — not `redirect_uri` — is the CSRF
50
+ * control.
51
+ *
52
+ * @returns A request handler that responds `302` with a `Location` header
53
+ * pointing at the provider's authorization URL.
54
+ */
55
+ export const oauthAuthorize = () => {
56
+ return async (req, res) => {
57
+ const provider = req.params?.provider;
58
+ if (!provider) {
59
+ return {
60
+ statusCode: 400,
61
+ body: { error: t('user.error.badRequest'), errorKey: 'user.error.badRequest' },
62
+ };
63
+ }
64
+ const oauthProvider = get('oauth', provider);
65
+ if (!oauthProvider?.getAuthorizeUrl) {
66
+ return {
67
+ statusCode: 404,
68
+ body: {
69
+ error: t('user.error.oauthServerNotConfigured', { server: provider }),
70
+ errorKey: 'user.error.oauthServerNotConfigured',
71
+ },
72
+ };
73
+ }
74
+ // CSRF state — the callback (`logInOAuth`) requires the provider-echoed
75
+ // `state` to equal this cookie (timing-safe), so a forged cross-site
76
+ // callback can't plant a session.
77
+ const state = crypto.randomBytes(32).toString('hex');
78
+ // Per-request PKCE (S256). The verifier binds the authorization code to
79
+ // THIS session — the only control that blocks authorization-code
80
+ // injection (RFC 9700 §2.1); `state` binds the session, NOT the code. The
81
+ // verifier lives in an httpOnly cookie read SERVER-SIDE by `logInOAuth`,
82
+ // never exposed to page JavaScript or trusted from the request body.
83
+ const codeVerifier = crypto.randomBytes(32).toString('base64url');
84
+ const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
85
+ // SET with the exact same name + attribute source `logInOAuth` CLEARS with
86
+ // (one source of truth — a mismatched attribute leaves an undeletable
87
+ // cookie): `__Host-` names in production, `SameSite=None; Partitioned` in
88
+ // the cross-site live preview, plain Lax cookies in top-level dev.
89
+ const cookieOptions = authorization.getAuthCookieOptions();
90
+ res.cookie(authorization.getAuthCookieName('oauth_state'), state, {
91
+ ...cookieOptions,
92
+ httpOnly: true,
93
+ maxAge: OAUTH_COOKIE_MAX_AGE_MS,
94
+ });
95
+ res.cookie(authorization.getAuthCookieName('oauth_verifier'), codeVerifier, {
96
+ ...cookieOptions,
97
+ httpOnly: true,
98
+ maxAge: OAUTH_COOKIE_MAX_AGE_MS,
99
+ });
100
+ // redirect_uri = the app origin + a validated same-origin path. When
101
+ // APP_ORIGIN is unset, omit redirect_uri entirely so the provider falls
102
+ // back to its registered callback URL (standard OAuth behavior).
103
+ const appOrigin = getConfig('APP_ORIGIN', '');
104
+ const redirectTo = sanitizeRedirectPath(req.query?.redirect_to);
105
+ const redirectUri = appOrigin ? `${appOrigin}${redirectTo}` : undefined;
106
+ // Persist the sanitized return path so `logInOAuth` can reconstruct the
107
+ // IDENTICAL redirect_uri at token exchange — RFC 6749 §4.1.3 requires the
108
+ // exchange redirect_uri to equal the authorization one byte-for-byte, and
109
+ // GitHub/Google enforce it (mismatch → `redirect_uri_mismatch`, which used
110
+ // to sink every flow started from a non-root page like /login). httpOnly +
111
+ // one-time-use, same cookie contract as oauth_state/oauth_verifier.
112
+ res.cookie(authorization.getAuthCookieName('oauth_redirect'), redirectTo, {
113
+ ...cookieOptions,
114
+ httpOnly: true,
115
+ maxAge: OAUTH_COOKIE_MAX_AGE_MS,
116
+ });
117
+ const authorizeUrl = oauthProvider.getAuthorizeUrl({
118
+ redirectUri,
119
+ state,
120
+ codeChallenge,
121
+ codeChallengeMethod: 'S256',
122
+ });
123
+ if (!authorizeUrl) {
124
+ // Bonded but unconfigured (e.g. client id env var unset) — an
125
+ // operator problem, not a client one.
126
+ return {
127
+ statusCode: 503,
128
+ body: {
129
+ error: t('user.error.oauthServerNotConfigured', { server: provider }),
130
+ errorKey: 'user.error.oauthServerNotConfigured',
131
+ },
132
+ };
133
+ }
134
+ return {
135
+ statusCode: 302,
136
+ headers: { Location: authorizeUrl },
137
+ body: undefined,
138
+ };
139
+ };
140
+ };
141
+ //# sourceMappingURL=oauthAuthorize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthAuthorize.js","sourceRoot":"","sources":["../../src/handlers/oauthAuthorize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { MoleculeRequest } from '@molecule/api-resource';
2
+ import type * as types from '../../types.js';
3
+ /**
4
+ * Generic payment notification handler that works with any bonded PaymentProvider. Reads the
5
+ * provider name from the `:provider` route parameter and dispatches based on `notificationFlow`:
6
+ * - `'webhook'` (Stripe-style): Calls `handleWebhookEvent()` to parse signed webhook payloads,
7
+ * then looks up the user via PaymentRecordService by customer ID.
8
+ * - Default (Apple/Google-style): Calls `parseNotification()` for server-to-server notifications,
9
+ * then looks up the user via PaymentRecordService by transaction ID.
10
+ *
11
+ * Updates the user's plan on renewal/change or clears it on cancellation/expiry/refund/revocation.
12
+ * Always returns 200 to acknowledge receipt.
13
+ * @param resource - The user resource configuration (name, tableName, schema).
14
+ * @param resource.name - The resource name.
15
+ * @param resource.tableName - The database table name for users.
16
+ * @param resource.schema - The validation schema for user properties.
17
+ * @returns A request handler for payment provider notifications.
18
+ */
19
+ export declare const handlePaymentNotification: ({ name, tableName, schema: _schema }: types.Resource) => (req: MoleculeRequest) => Promise<{
20
+ statusCode: number;
21
+ body: string;
22
+ } | {
23
+ statusCode: number;
24
+ body: {
25
+ error: string;
26
+ };
27
+ }>;
28
+ //# sourceMappingURL=handlePaymentNotification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlePaymentNotification.d.ts","sourceRoot":"","sources":["../../../src/handlers/payments/handlePaymentNotification.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAI7D,OAAO,KAAK,KAAK,KAAK,MAAM,gBAAgB,CAAA;AA6B5C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,yBAAyB,GAAI,sCAAsC,KAAK,CAAC,QAAQ,MAC9E,KAAK,eAAe;;;;;;;;EA6LnC,CAAA"}
@@ -0,0 +1,217 @@
1
+ import { get, getAnalytics, getLogger } from '@molecule/api-bond';
2
+ import { update as resourceUpdate } from '@molecule/api-resource';
3
+ import { updatePlanPropsSchema } from '../../schema.js';
4
+ import { invalidateEntitlementsCacheSafe } from '../../utilities/invalidateEntitlements.js';
5
+ const analytics = getAnalytics();
6
+ const logger = getLogger();
7
+ /**
8
+ * Whether a parsed webhook subscription is in a state that should grant/extend
9
+ * the user's plan.
10
+ *
11
+ * Mirrors the verify path (`verifySubscription`): only an active/trialing
12
+ * subscription confers entitlement. A subscription surfaced as inactive
13
+ * (`isActive === false`) or with a non-granting status (past_due/unpaid/
14
+ * incomplete/incomplete_expired) must NOT advance `planExpiresAt` — this blocks
15
+ * a renewal-payment failure from extending premium for an unpaid cycle. When a
16
+ * provider surfaces no status at all, prior grant behavior is preserved.
17
+ * @param subscription - The parsed webhook subscription details.
18
+ * @returns `true` when the subscription is active/trialing (or status-unknown).
19
+ */
20
+ const webhookSubscriptionGrantsEntitlement = (subscription) => {
21
+ if (subscription.isActive === false)
22
+ return false;
23
+ if (subscription.status !== undefined) {
24
+ return subscription.status === 'active' || subscription.status === 'trialing';
25
+ }
26
+ return true;
27
+ };
28
+ /**
29
+ * Generic payment notification handler that works with any bonded PaymentProvider. Reads the
30
+ * provider name from the `:provider` route parameter and dispatches based on `notificationFlow`:
31
+ * - `'webhook'` (Stripe-style): Calls `handleWebhookEvent()` to parse signed webhook payloads,
32
+ * then looks up the user via PaymentRecordService by customer ID.
33
+ * - Default (Apple/Google-style): Calls `parseNotification()` for server-to-server notifications,
34
+ * then looks up the user via PaymentRecordService by transaction ID.
35
+ *
36
+ * Updates the user's plan on renewal/change or clears it on cancellation/expiry/refund/revocation.
37
+ * Always returns 200 to acknowledge receipt.
38
+ * @param resource - The user resource configuration (name, tableName, schema).
39
+ * @param resource.name - The resource name.
40
+ * @param resource.tableName - The database table name for users.
41
+ * @param resource.schema - The validation schema for user properties.
42
+ * @returns A request handler for payment provider notifications.
43
+ */
44
+ export const handlePaymentNotification = ({ name, tableName, schema: _schema }) => {
45
+ return async (req) => {
46
+ const okResponse = { statusCode: 200, body: 'OK' };
47
+ const providerName = req.params.provider;
48
+ if (!providerName)
49
+ return okResponse;
50
+ try {
51
+ const provider = get('payments', providerName);
52
+ if (!provider)
53
+ return okResponse;
54
+ const plans = get('plans');
55
+ if (!plans)
56
+ return okResponse;
57
+ const records = get('paymentRecords');
58
+ // Use the partial plan-props schema, NOT the full user `schema`. The
59
+ // resource's full schema makes `id`/`createdAt`/`updatedAt` required,
60
+ // which would reject the partial `{ planKey, planExpiresAt, planAutoRenews }`
61
+ // payload we want to apply here.
62
+ const updateUser = resourceUpdate({
63
+ name,
64
+ tableName,
65
+ schema: updatePlanPropsSchema,
66
+ });
67
+ const cancelTypes = ['canceled', 'expired', 'refund', 'revoked'];
68
+ // Dispatch based on notificationFlow (with method-existence fallback).
69
+ if (provider.notificationFlow === 'webhook' ||
70
+ (!provider.notificationFlow && provider.handleWebhookEvent)) {
71
+ // Stripe-style: signed webhook with event type in payload.
72
+ if (!provider.handleWebhookEvent)
73
+ return okResponse;
74
+ const event = await provider.handleWebhookEvent(req);
75
+ if (!event) {
76
+ return { statusCode: 400, body: { error: 'Invalid webhook event' } };
77
+ }
78
+ const subscription = event.subscription;
79
+ if (!subscription?.customerId)
80
+ return okResponse;
81
+ // Find user by customer ID via PaymentRecordService.
82
+ const payment = records
83
+ ? await records.findByCustomerData(providerName, 'customerId', subscription.customerId)
84
+ : null;
85
+ if (!payment) {
86
+ logger.error(`${providerName} webhook: no user found for customer ${subscription.customerId}`);
87
+ return okResponse;
88
+ }
89
+ // Providers report the parent PRODUCT id on subscriptions, but apps
90
+ // register their catalogue with PRICE ids — try both.
91
+ const plan = (subscription.productId ? plans.findPlanByProductId(subscription.productId) : null) ??
92
+ (subscription.priceId ? plans.findPlanByProductId(subscription.priceId) : null);
93
+ analytics
94
+ .track({
95
+ name: 'payment.notification_received',
96
+ userId: payment.userId,
97
+ properties: { provider: providerName, type: event.type },
98
+ })
99
+ .catch(() => { });
100
+ if (cancelTypes.includes(event.type)) {
101
+ await updateUser({
102
+ id: payment.userId,
103
+ props: {
104
+ planKey: '',
105
+ planAutoRenews: false,
106
+ },
107
+ });
108
+ // Drop the cached plan key so the revocation takes effect
109
+ // immediately, not after the entitlements cache TTL.
110
+ invalidateEntitlementsCacheSafe(payment.userId);
111
+ analytics
112
+ .track({
113
+ name: 'payment.subscription_canceled',
114
+ userId: payment.userId,
115
+ properties: { provider: providerName, type: event.type },
116
+ })
117
+ .catch(() => { });
118
+ }
119
+ else if (plan && webhookSubscriptionGrantsEntitlement(subscription)) {
120
+ await updateUser({
121
+ id: payment.userId,
122
+ props: {
123
+ planKey: plan.planKey,
124
+ planExpiresAt: subscription.expiresAt,
125
+ planAutoRenews: subscription.autoRenews,
126
+ },
127
+ });
128
+ // Drop the cached plan key so the grant/renewal takes effect
129
+ // immediately, not after the entitlements cache TTL.
130
+ invalidateEntitlementsCacheSafe(payment.userId);
131
+ analytics
132
+ .track({
133
+ name: 'payment.subscription_renewed',
134
+ userId: payment.userId,
135
+ properties: { provider: providerName, planKey: plan.planKey },
136
+ })
137
+ .catch(() => { });
138
+ }
139
+ else if (plan) {
140
+ // A plan exists but the subscription is not in a granting state
141
+ // (past_due/unpaid/incomplete/incomplete_expired) — e.g. a renewal
142
+ // payment failed. Mirror `verifySubscription`: do NOT advance
143
+ // planExpiresAt; leave the existing entitlement to lapse naturally.
144
+ // Cancellation events are handled by the cancelTypes branch above.
145
+ logger.info(`${providerName} webhook: subscription for user ${payment.userId} is not active ` +
146
+ `(status=${subscription.status ?? 'unknown'}) — not extending entitlement`);
147
+ }
148
+ return okResponse;
149
+ }
150
+ // Apple/Google-style: server-to-server notification.
151
+ if (!provider.parseNotification)
152
+ return okResponse;
153
+ const notification = await provider.parseNotification(req.body);
154
+ if (!notification?.transactionId)
155
+ return okResponse;
156
+ // Find existing payment by transaction ID via PaymentRecordService.
157
+ const existingPayment = records
158
+ ? await records.findByTransaction(providerName, notification.transactionId)
159
+ : null;
160
+ if (!existingPayment)
161
+ return okResponse;
162
+ analytics
163
+ .track({
164
+ name: 'payment.notification_received',
165
+ userId: existingPayment.userId,
166
+ properties: { provider: providerName, type: notification.type },
167
+ })
168
+ .catch(() => { });
169
+ const plan = notification.productId ? plans.findPlanByProductId(notification.productId) : null;
170
+ if (cancelTypes.includes(notification.type)) {
171
+ await updateUser({
172
+ id: existingPayment.userId,
173
+ props: {
174
+ planKey: '',
175
+ planAutoRenews: false,
176
+ },
177
+ });
178
+ // Drop the cached plan key so the revocation takes effect
179
+ // immediately, not after the entitlements cache TTL.
180
+ invalidateEntitlementsCacheSafe(existingPayment.userId);
181
+ analytics
182
+ .track({
183
+ name: 'payment.subscription_canceled',
184
+ userId: existingPayment.userId,
185
+ properties: { provider: providerName, type: notification.type },
186
+ })
187
+ .catch(() => { });
188
+ }
189
+ else if (plan && notification.expiresAt) {
190
+ await updateUser({
191
+ id: existingPayment.userId,
192
+ props: {
193
+ planKey: plan.planKey,
194
+ planExpiresAt: notification.expiresAt,
195
+ planAutoRenews: notification.autoRenews ?? true,
196
+ },
197
+ });
198
+ // Drop the cached plan key so the grant/renewal takes effect
199
+ // immediately, not after the entitlements cache TTL.
200
+ invalidateEntitlementsCacheSafe(existingPayment.userId);
201
+ analytics
202
+ .track({
203
+ name: 'payment.subscription_renewed',
204
+ userId: existingPayment.userId,
205
+ properties: { provider: providerName, planKey: plan.planKey },
206
+ })
207
+ .catch(() => { });
208
+ }
209
+ return okResponse;
210
+ }
211
+ catch (error) {
212
+ logger.error(`${providerName} handlePaymentNotification error:`, error);
213
+ return okResponse;
214
+ }
215
+ };
216
+ };
217
+ //# sourceMappingURL=handlePaymentNotification.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlePaymentNotification.js","sourceRoot":"","sources":["../../../src/handlers/payments/handlePaymentNotification.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAQjE,OAAO,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAEvD,OAAO,EAAE,+BAA+B,EAAE,MAAM,2CAA2C,CAAA;AAE3F,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;AAChC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;AAE1B;;;;;;;;;;;;GAYG;AACH,MAAM,oCAAoC,GAAG,CAC3C,YAAuD,EAC9C,EAAE;IACX,IAAI,YAAY,CAAC,QAAQ,KAAK,KAAK;QAAE,OAAO,KAAK,CAAA;IACjD,IAAI,YAAY,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO,YAAY,CAAC,MAAM,KAAK,QAAQ,IAAI,YAAY,CAAC,MAAM,KAAK,UAAU,CAAA;IAC/E,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAkB,EAAE,EAAE;IAChG,OAAO,KAAK,EAAE,GAAoB,EAAE,EAAE;QACpC,MAAM,UAAU,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;QAClD,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,QAAkB,CAAA;QAElD,IAAI,CAAC,YAAY;YAAE,OAAO,UAAU,CAAA;QAEpC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,GAAG,CAAkB,UAAU,EAAE,YAAY,CAAC,CAAA;YAC/D,IAAI,CAAC,QAAQ;gBAAE,OAAO,UAAU,CAAA;YAEhC,MAAM,KAAK,GAAG,GAAG,CAAc,OAAO,CAAC,CAAA;YACvC,IAAI,CAAC,KAAK;gBAAE,OAAO,UAAU,CAAA;YAE7B,MAAM,OAAO,GAAG,GAAG,CAAuB,gBAAgB,CAAC,CAAA;YAE3D,qEAAqE;YACrE,sEAAsE;YACtE,8EAA8E;YAC9E,iCAAiC;YACjC,MAAM,UAAU,GAAG,cAAc,CAAC;gBAChC,IAAI;gBACJ,SAAS;gBACT,MAAM,EAAE,qBAA4D;aACrE,CAAC,CAAA;YAEF,MAAM,WAAW,GAAG,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAA;YAEhE,uEAAuE;YACvE,IACE,QAAQ,CAAC,gBAAgB,KAAK,SAAS;gBACvC,CAAC,CAAC,QAAQ,CAAC,gBAAgB,IAAI,QAAQ,CAAC,kBAAkB,CAAC,EAC3D,CAAC;gBACD,2DAA2D;gBAC3D,IAAI,CAAC,QAAQ,CAAC,kBAAkB;oBAAE,OAAO,UAAU,CAAA;gBAEnD,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAA;gBACpD,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,uBAAuB,EAAE,EAAE,CAAA;gBACtE,CAAC;gBAED,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,CAAA;gBACvC,IAAI,CAAC,YAAY,EAAE,UAAU;oBAAE,OAAO,UAAU,CAAA;gBAEhD,qDAAqD;gBACrD,MAAM,OAAO,GAAG,OAAO;oBACrB,CAAC,CAAC,MAAM,OAAO,CAAC,kBAAkB,CAAC,YAAY,EAAE,YAAY,EAAE,YAAY,CAAC,UAAU,CAAC;oBACvF,CAAC,CAAC,IAAI,CAAA;gBAER,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,MAAM,CAAC,KAAK,CACV,GAAG,YAAY,wCAAwC,YAAY,CAAC,UAAU,EAAE,CACjF,CAAA;oBACD,OAAO,UAAU,CAAA;gBACnB,CAAC;gBAED,oEAAoE;gBACpE,sDAAsD;gBACtD,MAAM,IAAI,GACR,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;oBACnF,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;gBAEjF,SAAS;qBACN,KAAK,CAAC;oBACL,IAAI,EAAE,+BAA+B;oBACrC,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,UAAU,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;iBACzD,CAAC;qBACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;gBAElB,IAAI,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;oBACrC,MAAM,UAAU,CAAC;wBACf,EAAE,EAAE,OAAO,CAAC,MAAM;wBAClB,KAAK,EAAE;4BACL,OAAO,EAAE,EAAE;4BACX,cAAc,EAAE,KAAK;yBACtB;qBACF,CAAC,CAAA;oBACF,0DAA0D;oBAC1D,qDAAqD;oBACrD,+BAA+B,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;oBAC/C,SAAS;yBACN,KAAK,CAAC;wBACL,IAAI,EAAE,+BAA+B;wBACrC,MAAM,EAAE,OAAO,CAAC,MAAM;wBACtB,UAAU,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;qBACzD,CAAC;yBACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;gBACpB,CAAC;qBAAM,IAAI,IAAI,IAAI,oCAAoC,CAAC,YAAY,CAAC,EAAE,CAAC;oBACtE,MAAM,UAAU,CAAC;wBACf,EAAE,EAAE,OAAO,CAAC,MAAM;wBAClB,KAAK,EAAE;4BACL,OAAO,EAAE,IAAI,CAAC,OAAO;4BACrB,aAAa,EAAE,YAAY,CAAC,SAAS;4BACrC,cAAc,EAAE,YAAY,CAAC,UAAU;yBACxC;qBACF,CAAC,CAAA;oBACF,6DAA6D;oBAC7D,qDAAqD;oBACrD,+BAA+B,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;oBAC/C,SAAS;yBACN,KAAK,CAAC;wBACL,IAAI,EAAE,8BAA8B;wBACpC,MAAM,EAAE,OAAO,CAAC,MAAM;wBACtB,UAAU,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;qBAC9D,CAAC;yBACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;gBACpB,CAAC;qBAAM,IAAI,IAAI,EAAE,CAAC;oBAChB,gEAAgE;oBAChE,mEAAmE;oBACnE,8DAA8D;oBAC9D,oEAAoE;oBACpE,mEAAmE;oBACnE,MAAM,CAAC,IAAI,CACT,GAAG,YAAY,mCAAmC,OAAO,CAAC,MAAM,iBAAiB;wBAC/E,WAAW,YAAY,CAAC,MAAM,IAAI,SAAS,+BAA+B,CAC7E,CAAA;gBACH,CAAC;gBAED,OAAO,UAAU,CAAA;YACnB,CAAC;YAED,qDAAqD;YACrD,IAAI,CAAC,QAAQ,CAAC,iBAAiB;gBAAE,OAAO,UAAU,CAAA;YAElD,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;YAC/D,IAAI,CAAC,YAAY,EAAE,aAAa;gBAAE,OAAO,UAAU,CAAA;YAEnD,oEAAoE;YACpE,MAAM,eAAe,GAAG,OAAO;gBAC7B,CAAC,CAAC,MAAM,OAAO,CAAC,iBAAiB,CAAC,YAAY,EAAE,YAAY,CAAC,aAAa,CAAC;gBAC3E,CAAC,CAAC,IAAI,CAAA;YAER,IAAI,CAAC,eAAe;gBAAE,OAAO,UAAU,CAAA;YAEvC,SAAS;iBACN,KAAK,CAAC;gBACL,IAAI,EAAE,+BAA+B;gBACrC,MAAM,EAAE,eAAe,CAAC,MAAM;gBAC9B,UAAU,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,CAAC,IAAI,EAAE;aAChE,CAAC;iBACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;YAElB,MAAM,IAAI,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;YAE9F,IAAI,WAAW,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5C,MAAM,UAAU,CAAC;oBACf,EAAE,EAAE,eAAe,CAAC,MAAM;oBAC1B,KAAK,EAAE;wBACL,OAAO,EAAE,EAAE;wBACX,cAAc,EAAE,KAAK;qBACtB;iBACF,CAAC,CAAA;gBACF,0DAA0D;gBAC1D,qDAAqD;gBACrD,+BAA+B,CAAC,eAAe,CAAC,MAAM,CAAC,CAAA;gBACvD,SAAS;qBACN,KAAK,CAAC;oBACL,IAAI,EAAE,+BAA+B;oBACrC,MAAM,EAAE,eAAe,CAAC,MAAM;oBAC9B,UAAU,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,CAAC,IAAI,EAAE;iBAChE,CAAC;qBACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;YACpB,CAAC;iBAAM,IAAI,IAAI,IAAI,YAAY,CAAC,SAAS,EAAE,CAAC;gBAC1C,MAAM,UAAU,CAAC;oBACf,EAAE,EAAE,eAAe,CAAC,MAAM;oBAC1B,KAAK,EAAE;wBACL,OAAO,EAAE,IAAI,CAAC,OAAO;wBACrB,aAAa,EAAE,YAAY,CAAC,SAAS;wBACrC,cAAc,EAAE,YAAY,CAAC,UAAU,IAAI,IAAI;qBAChD;iBACF,CAAC,CAAA;gBACF,6DAA6D;gBAC7D,qDAAqD;gBACrD,+BAA+B,CAAC,eAAe,CAAC,MAAM,CAAC,CAAA;gBACvD,SAAS;qBACN,KAAK,CAAC;oBACL,IAAI,EAAE,8BAA8B;oBACpC,MAAM,EAAE,eAAe,CAAC,MAAM;oBAC9B,UAAU,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;iBAC9D,CAAC;qBACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;YACpB,CAAC;YAED,OAAO,UAAU,CAAA;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,GAAG,YAAY,mCAAmC,EAAE,KAAK,CAAC,CAAA;YACvE,OAAO,UAAU,CAAA;QACnB,CAAC;IACH,CAAC,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Payment handler registry.
3
+ *
4
+ * Exports generic payment handlers that work with any bonded payment
5
+ * provider via route parameters. No static imports of provider-specific
6
+ * code needed — adding a new payment provider requires only a new bond
7
+ * package and a registry entry.
8
+ *
9
+ * @module
10
+ */
11
+ export { handlePaymentNotification } from './handlePaymentNotification.js';
12
+ export { verifyPayment } from './verifyPayment.js';
13
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/handlers/payments/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAA;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Payment handler registry.
3
+ *
4
+ * Exports generic payment handlers that work with any bonded payment
5
+ * provider via route parameters. No static imports of provider-specific
6
+ * code needed — adding a new payment provider requires only a new bond
7
+ * package and a registry entry.
8
+ *
9
+ * @module
10
+ */
11
+ export { handlePaymentNotification } from './handlePaymentNotification.js';
12
+ export { verifyPayment } from './verifyPayment.js';
13
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/handlers/payments/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAA;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,37 @@
1
+ import type { MoleculeRequest } from '@molecule/api-resource';
2
+ import type * as types from '../../types.js';
3
+ /**
4
+ * Generic payment verification handler that works with any bonded PaymentProvider. Reads the
5
+ * provider name from the `:provider` route parameter and dispatches based on `verifyFlow`:
6
+ * - `'subscription'` (Stripe-style): Calls `verifySubscription(subscriptionId)`.
7
+ * - Default (Apple/Google-style): Calls `verifyReceipt()` or `verifyPurchase()` with a receipt
8
+ * string and the plan's platform product ID.
9
+ *
10
+ * On successful verification, updates the user's plan (planKey, planExpiresAt, planAutoRenews)
11
+ * and stores the payment record via the bonded PaymentRecordService.
12
+ * @param resource - The user resource configuration (name, tableName, schema).
13
+ * @param resource.name - The resource name.
14
+ * @param resource.tableName - The database table name for users.
15
+ * @param resource.schema - The validation schema for user properties.
16
+ * @returns A request handler for payment verification.
17
+ */
18
+ export declare const verifyPayment: ({ name, tableName, schema: _schema }: types.Resource) => (req: MoleculeRequest) => Promise<{
19
+ statusCode: number;
20
+ body: {
21
+ error: string;
22
+ errorKey: string;
23
+ props?: undefined;
24
+ };
25
+ } | {
26
+ statusCode: number;
27
+ body: {
28
+ props: Pick<import("@molecule/api-resource/types").Props, "updatedAt"> & {
29
+ planKey?: string | undefined;
30
+ planExpiresAt?: string | undefined;
31
+ planAutoRenews?: boolean | undefined;
32
+ };
33
+ error?: undefined;
34
+ errorKey?: undefined;
35
+ };
36
+ }>;
37
+ //# sourceMappingURL=verifyPayment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifyPayment.d.ts","sourceRoot":"","sources":["../../../src/handlers/payments/verifyPayment.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAI7D,OAAO,KAAK,KAAK,KAAK,MAAM,gBAAgB,CAAA;AAM5C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,GAAI,sCAAsC,KAAK,CAAC,QAAQ,MAClE,KAAK,eAAe;;;;;aAGlB,CAAC;;;;;;;;;;;;;EAoZlB,CAAA"}