@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.
- package/LICENSE +115 -0
- package/dist/authorization.d.ts +107 -0
- package/dist/authorization.d.ts.map +1 -0
- package/dist/authorization.js +375 -0
- package/dist/authorization.js.map +1 -0
- package/dist/authorizers/auth.d.ts +8 -0
- package/dist/authorizers/auth.d.ts.map +1 -0
- package/dist/authorizers/auth.js +19 -0
- package/dist/authorizers/auth.js.map +1 -0
- package/dist/authorizers/authSelf.d.ts +14 -0
- package/dist/authorizers/authSelf.d.ts.map +1 -0
- package/dist/authorizers/authSelf.js +31 -0
- package/dist/authorizers/authSelf.js.map +1 -0
- package/dist/authorizers/index.d.ts +5 -0
- package/dist/authorizers/index.d.ts.map +1 -0
- package/dist/authorizers/index.js +5 -0
- package/dist/authorizers/index.js.map +1 -0
- package/dist/authorizers/rateLimit.d.ts +84 -0
- package/dist/authorizers/rateLimit.d.ts.map +1 -0
- package/dist/authorizers/rateLimit.js +144 -0
- package/dist/authorizers/rateLimit.js.map +1 -0
- package/dist/authorizers/requireWebhookAuthenticity.d.ts +23 -0
- package/dist/authorizers/requireWebhookAuthenticity.d.ts.map +1 -0
- package/dist/authorizers/requireWebhookAuthenticity.js +76 -0
- package/dist/authorizers/requireWebhookAuthenticity.js.map +1 -0
- package/dist/browser-guard.d.ts +2 -0
- package/dist/browser-guard.d.ts.map +1 -0
- package/dist/browser-guard.js +19 -0
- package/dist/browser-guard.js.map +1 -0
- package/dist/handlers/create.d.ts +52 -0
- package/dist/handlers/create.d.ts.map +1 -0
- package/dist/handlers/create.js +208 -0
- package/dist/handlers/create.js.map +1 -0
- package/dist/handlers/del.d.ts +30 -0
- package/dist/handlers/del.d.ts.map +1 -0
- package/dist/handlers/del.js +55 -0
- package/dist/handlers/del.js.map +1 -0
- package/dist/handlers/forgotPassword.d.ts +34 -0
- package/dist/handlers/forgotPassword.d.ts.map +1 -0
- package/dist/handlers/forgotPassword.js +119 -0
- package/dist/handlers/forgotPassword.js.map +1 -0
- package/dist/handlers/index.d.ts +16 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +16 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/logIn.d.ts +91 -0
- package/dist/handlers/logIn.d.ts.map +1 -0
- package/dist/handlers/logIn.js +223 -0
- package/dist/handlers/logIn.js.map +1 -0
- package/dist/handlers/logInOAuth.d.ts +66 -0
- package/dist/handlers/logInOAuth.d.ts.map +1 -0
- package/dist/handlers/logInOAuth.js +462 -0
- package/dist/handlers/logInOAuth.js.map +1 -0
- package/dist/handlers/logout.d.ts +22 -0
- package/dist/handlers/logout.d.ts.map +1 -0
- package/dist/handlers/logout.js +57 -0
- package/dist/handlers/logout.js.map +1 -0
- package/dist/handlers/oauthAuthorize.d.ts +44 -0
- package/dist/handlers/oauthAuthorize.d.ts.map +1 -0
- package/dist/handlers/oauthAuthorize.js +141 -0
- package/dist/handlers/oauthAuthorize.js.map +1 -0
- package/dist/handlers/payments/handlePaymentNotification.d.ts +28 -0
- package/dist/handlers/payments/handlePaymentNotification.d.ts.map +1 -0
- package/dist/handlers/payments/handlePaymentNotification.js +217 -0
- package/dist/handlers/payments/handlePaymentNotification.js.map +1 -0
- package/dist/handlers/payments/registry.d.ts +13 -0
- package/dist/handlers/payments/registry.d.ts.map +1 -0
- package/dist/handlers/payments/registry.js +13 -0
- package/dist/handlers/payments/registry.js.map +1 -0
- package/dist/handlers/payments/verifyPayment.d.ts +37 -0
- package/dist/handlers/payments/verifyPayment.d.ts.map +1 -0
- package/dist/handlers/payments/verifyPayment.js +383 -0
- package/dist/handlers/payments/verifyPayment.js.map +1 -0
- package/dist/handlers/read.d.ts +17 -0
- package/dist/handlers/read.d.ts.map +1 -0
- package/dist/handlers/read.js +60 -0
- package/dist/handlers/read.js.map +1 -0
- package/dist/handlers/readSelf.d.ts +23 -0
- package/dist/handlers/readSelf.d.ts.map +1 -0
- package/dist/handlers/readSelf.js +77 -0
- package/dist/handlers/readSelf.js.map +1 -0
- package/dist/handlers/resetPassword.d.ts +40 -0
- package/dist/handlers/resetPassword.d.ts.map +1 -0
- package/dist/handlers/resetPassword.js +147 -0
- package/dist/handlers/resetPassword.js.map +1 -0
- package/dist/handlers/update.d.ts +28 -0
- package/dist/handlers/update.d.ts.map +1 -0
- package/dist/handlers/update.js +250 -0
- package/dist/handlers/update.js.map +1 -0
- package/dist/handlers/updatePassword.d.ts +35 -0
- package/dist/handlers/updatePassword.d.ts.map +1 -0
- package/dist/handlers/updatePassword.js +113 -0
- package/dist/handlers/updatePassword.js.map +1 -0
- package/dist/handlers/updatePlan.d.ts +41 -0
- package/dist/handlers/updatePlan.d.ts.map +1 -0
- package/dist/handlers/updatePlan.js +242 -0
- package/dist/handlers/updatePlan.js.map +1 -0
- package/dist/handlers/verifyTwoFactor.d.ts +46 -0
- package/dist/handlers/verifyTwoFactor.d.ts.map +1 -0
- package/dist/handlers/verifyTwoFactor.js +162 -0
- package/dist/handlers/verifyTwoFactor.js.map +1 -0
- package/dist/i18n.d.ts +10 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +25 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/requestHandlerMap.d.ts +44 -0
- package/dist/requestHandlerMap.d.ts.map +1 -0
- package/dist/requestHandlerMap.js +68 -0
- package/dist/requestHandlerMap.js.map +1 -0
- package/dist/resource.d.ts +22 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +23 -0
- package/dist/resource.js.map +1 -0
- package/dist/routes.d.ts +44 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +121 -0
- package/dist/routes.js.map +1 -0
- package/dist/schema.d.ts +193 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +229 -0
- package/dist/schema.js.map +1 -0
- package/dist/secrets.d.ts +16 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +28 -0
- package/dist/secrets.js.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/utilities/getPlan.d.ts +19 -0
- package/dist/utilities/getPlan.d.ts.map +1 -0
- package/dist/utilities/getPlan.js +26 -0
- package/dist/utilities/getPlan.js.map +1 -0
- package/dist/utilities/hashResetToken.d.ts +16 -0
- package/dist/utilities/hashResetToken.d.ts.map +1 -0
- package/dist/utilities/hashResetToken.js +19 -0
- package/dist/utilities/hashResetToken.js.map +1 -0
- package/dist/utilities/index.d.ts +5 -0
- package/dist/utilities/index.d.ts.map +1 -0
- package/dist/utilities/index.js +5 -0
- package/dist/utilities/index.js.map +1 -0
- package/dist/utilities/invalidateEntitlements.d.ts +24 -0
- package/dist/utilities/invalidateEntitlements.d.ts.map +1 -0
- package/dist/utilities/invalidateEntitlements.js +41 -0
- package/dist/utilities/invalidateEntitlements.js.map +1 -0
- package/dist/utilities/normalizeEmail.d.ts +22 -0
- package/dist/utilities/normalizeEmail.d.ts.map +1 -0
- package/dist/utilities/normalizeEmail.js +28 -0
- package/dist/utilities/normalizeEmail.js.map +1 -0
- package/dist/utilities/notify.d.ts +21 -0
- package/dist/utilities/notify.d.ts.map +1 -0
- package/dist/utilities/notify.js +39 -0
- package/dist/utilities/notify.js.map +1 -0
- package/dist/utilities/stripSensitiveUserColumns.d.ts +25 -0
- package/dist/utilities/stripSensitiveUserColumns.d.ts.map +1 -0
- package/dist/utilities/stripSensitiveUserColumns.js +43 -0
- package/dist/utilities/stripSensitiveUserColumns.js.map +1 -0
- package/package.json +94 -0
- package/setup/users.sql +22 -0
- 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":"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"}
|
|
@@ -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"}
|