backend-manager 5.0.86 → 5.0.87
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/CLAUDE.md +53 -1
- package/package.json +1 -1
- package/src/cli/commands/base-command.js +5 -1
- package/src/cli/commands/serve.js +1 -2
- package/src/manager/cron/daily/ghostii-auto-publisher.js +10 -19
- package/src/manager/events/firestore/payments-webhooks/on-write.js +351 -56
- package/src/manager/events/firestore/payments-webhooks/transitions/index.js +148 -0
- package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +16 -0
- package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +15 -0
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +15 -0
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +18 -0
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +15 -0
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +14 -0
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +16 -0
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +16 -0
- package/src/manager/index.js +26 -36
- package/src/manager/libraries/{stripe.js → payment-processors/stripe.js} +57 -2
- package/src/manager/libraries/payment-processors/test.js +141 -0
- package/src/manager/routes/app/get.js +5 -22
- package/src/manager/routes/payments/intent/post.js +38 -23
- package/src/manager/routes/payments/intent/processors/stripe.js +96 -48
- package/src/manager/routes/payments/intent/processors/test.js +139 -76
- package/src/manager/routes/payments/webhook/post.js +14 -5
- package/src/manager/routes/payments/webhook/processors/stripe.js +75 -9
- package/src/manager/schemas/payments/intent/post.js +1 -1
- package/src/test/test-accounts.js +10 -1
- package/templates/backend-manager-config.json +16 -4
- package/test/events/payments/journey-payments-cancel.js +6 -0
- package/test/events/payments/journey-payments-failure.js +114 -0
- package/test/events/payments/journey-payments-suspend.js +6 -0
- package/test/events/payments/journey-payments-trial.js +12 -0
- package/test/events/payments/journey-payments-upgrade.js +17 -0
- package/test/fixtures/stripe/checkout-session-completed.json +130 -0
- package/test/fixtures/stripe/invoice-payment-failed.json +148 -0
- package/test/fixtures/stripe/invoice-subscription-payment-failed.json +28 -0
- package/test/helpers/stripe-parse-webhook.js +447 -0
- package/test/helpers/stripe-to-unified.js +59 -59
- package/test/routes/payments/intent.js +3 -3
- package/test/routes/payments/webhook.js +2 -2
- package/src/manager/libraries/test.js +0 -27
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment transition detection and dispatch
|
|
3
|
+
*
|
|
4
|
+
* Compares subscription state before and after a webhook to detect meaningful
|
|
5
|
+
* transitions (e.g., new subscription, payment failed, cancellation).
|
|
6
|
+
* Dispatches to individual handler files for each transition type.
|
|
7
|
+
*/
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect what transition occurred based on category and before/after state
|
|
12
|
+
*
|
|
13
|
+
* @param {string} category - 'subscription' or 'one-time'
|
|
14
|
+
* @param {object|null} before - Previous state (null for new users / one-time)
|
|
15
|
+
* @param {object} after - New unified state about to be written
|
|
16
|
+
* @param {string} eventType - Original webhook event type (used for one-time detection)
|
|
17
|
+
* @returns {string|null} Transition name or null if no meaningful change
|
|
18
|
+
*/
|
|
19
|
+
function detectTransition(category, before, after, eventType) {
|
|
20
|
+
if (category === 'subscription') {
|
|
21
|
+
return detectSubscriptionTransition(before, after);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (category === 'one-time') {
|
|
25
|
+
return detectOneTimeTransition(eventType);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Detect subscription state transitions by comparing before and after
|
|
33
|
+
*
|
|
34
|
+
* Checks are ordered by specificity — most specific first to avoid misclassification.
|
|
35
|
+
*
|
|
36
|
+
* @param {object|null} before - Previous users/{uid}.subscription (null/undefined for new users)
|
|
37
|
+
* @param {object} after - New unified subscription
|
|
38
|
+
* @returns {string|null} Transition name
|
|
39
|
+
*/
|
|
40
|
+
function detectSubscriptionTransition(before, after) {
|
|
41
|
+
if (!after) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const beforeStatus = before?.status;
|
|
46
|
+
const afterStatus = after.status;
|
|
47
|
+
|
|
48
|
+
// 1. new-subscription: basic/null → active paid (handler checks after.trial.claimed for trial info)
|
|
49
|
+
if (isBasicOrNull(before) && afterStatus === 'active' && isPaid(after)) {
|
|
50
|
+
return 'new-subscription';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 2. payment-failed: active → suspended
|
|
54
|
+
if (beforeStatus === 'active' && afterStatus === 'suspended') {
|
|
55
|
+
return 'payment-failed';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 3. payment-recovered: suspended → active
|
|
59
|
+
if (beforeStatus === 'suspended' && afterStatus === 'active') {
|
|
60
|
+
return 'payment-recovered';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 4. cancellation-requested: pending flips from false → true while still active
|
|
64
|
+
if (afterStatus === 'active' && !before?.cancellation?.pending && after.cancellation?.pending) {
|
|
65
|
+
return 'cancellation-requested';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 5. subscription-cancelled: any non-cancelled → cancelled
|
|
69
|
+
if (beforeStatus !== 'cancelled' && afterStatus === 'cancelled') {
|
|
70
|
+
return 'subscription-cancelled';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 6. plan-changed: both active, both paid, different product
|
|
74
|
+
if (
|
|
75
|
+
beforeStatus === 'active'
|
|
76
|
+
&& afterStatus === 'active'
|
|
77
|
+
&& isPaid(before)
|
|
78
|
+
&& isPaid(after)
|
|
79
|
+
&& before.product.id !== after.product.id
|
|
80
|
+
) {
|
|
81
|
+
return 'plan-changed';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Detect one-time payment transitions from event type
|
|
89
|
+
* Simpler than subscriptions — no before/after comparison needed
|
|
90
|
+
*
|
|
91
|
+
* @param {string} eventType - Webhook event type
|
|
92
|
+
* @returns {string|null} Transition name
|
|
93
|
+
*/
|
|
94
|
+
function detectOneTimeTransition(eventType) {
|
|
95
|
+
if (eventType === 'checkout.session.completed') {
|
|
96
|
+
return 'purchase-completed';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (eventType === 'invoice.payment_failed') {
|
|
100
|
+
return 'purchase-failed';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Dispatch a transition handler (fire-and-forget)
|
|
108
|
+
*
|
|
109
|
+
* @param {string} transitionName - e.g., 'new-subscription', 'payment-failed'
|
|
110
|
+
* @param {string} category - 'subscription' or 'one-time'
|
|
111
|
+
* @param {object} context - Full context passed to the handler
|
|
112
|
+
*/
|
|
113
|
+
function dispatch(transitionName, category, context) {
|
|
114
|
+
const { assistant } = context;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const handlerPath = path.join(__dirname, category, `${transitionName}.js`);
|
|
118
|
+
const handler = require(handlerPath);
|
|
119
|
+
|
|
120
|
+
// Fire-and-forget — don't block the main webhook processing
|
|
121
|
+
Promise.resolve(handler(context)).catch((e) => {
|
|
122
|
+
assistant.error(`Transition handler [${category}/${transitionName}] failed: ${e.message}`, e);
|
|
123
|
+
});
|
|
124
|
+
} catch (e) {
|
|
125
|
+
// Handler file doesn't exist or can't be loaded — log but don't fail
|
|
126
|
+
assistant.error(`Transition handler [${category}/${transitionName}] not found: ${e.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ─── Helpers ───
|
|
131
|
+
|
|
132
|
+
function isBasicOrNull(sub) {
|
|
133
|
+
return !sub || !sub.product || sub.product.id === 'basic';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isPaid(sub) {
|
|
137
|
+
return sub && sub.product && sub.product.id !== 'basic';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = {
|
|
141
|
+
detectTransition,
|
|
142
|
+
detectSubscriptionTransition,
|
|
143
|
+
detectOneTimeTransition,
|
|
144
|
+
dispatch,
|
|
145
|
+
// Exported for testing
|
|
146
|
+
isBasicOrNull,
|
|
147
|
+
isPaid,
|
|
148
|
+
};
|
package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition: purchase-completed
|
|
3
|
+
* Triggered when a one-time payment checkout completes (checkout.session.completed with mode=payment)
|
|
4
|
+
*
|
|
5
|
+
* Use cases:
|
|
6
|
+
* - Send purchase receipt/confirmation email
|
|
7
|
+
* - Deliver digital goods or credits
|
|
8
|
+
* - Fire analytics event for purchase
|
|
9
|
+
*/
|
|
10
|
+
module.exports = async function ({ before, after, uid, userDoc, admin, assistant, Manager, eventType, eventId }) {
|
|
11
|
+
assistant.log(`Transition [one-time/purchase-completed]: uid=${uid}, resourceId=${after.id}`);
|
|
12
|
+
|
|
13
|
+
// TODO: Send purchase confirmation email
|
|
14
|
+
// TODO: Deliver digital goods
|
|
15
|
+
// TODO: Fire analytics event
|
|
16
|
+
};
|
package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition: purchase-failed
|
|
3
|
+
* Triggered when a one-time payment fails (invoice.payment_failed with billing_reason=manual)
|
|
4
|
+
*
|
|
5
|
+
* Use cases:
|
|
6
|
+
* - Send payment failure notification
|
|
7
|
+
* - Include retry link or alternative payment method
|
|
8
|
+
* - Fire analytics event
|
|
9
|
+
*/
|
|
10
|
+
module.exports = async function ({ before, after, uid, userDoc, admin, assistant, Manager, eventType, eventId }) {
|
|
11
|
+
assistant.log(`Transition [one-time/purchase-failed]: uid=${uid}, resourceId=${after.id}`);
|
|
12
|
+
|
|
13
|
+
// TODO: Send payment failure email with retry link
|
|
14
|
+
// TODO: Fire analytics event
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition: cancellation-requested
|
|
3
|
+
* Triggered when a user requests cancellation at period end (cancellation.pending flips to true)
|
|
4
|
+
*
|
|
5
|
+
* Use cases:
|
|
6
|
+
* - Send cancellation confirmation email with period end date
|
|
7
|
+
* - Include win-back offer or feedback survey link
|
|
8
|
+
* - Fire analytics event for churn intent
|
|
9
|
+
*/
|
|
10
|
+
module.exports = async function ({ before, after, uid, userDoc, admin, assistant, Manager, eventType, eventId }) {
|
|
11
|
+
assistant.log(`Transition [subscription/cancellation-requested]: uid=${uid}, product=${after.product.id}, cancelDate=${after.cancellation.date.timestamp}`);
|
|
12
|
+
|
|
13
|
+
// TODO: Send cancellation confirmation email with end date
|
|
14
|
+
// TODO: Fire analytics event
|
|
15
|
+
};
|
package/src/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition: new-subscription
|
|
3
|
+
* Triggered when a user subscribes for the first time (basic/null → active paid)
|
|
4
|
+
* Check after.trial.claimed to determine if this is a trial subscription
|
|
5
|
+
*
|
|
6
|
+
* Use cases:
|
|
7
|
+
* - Send order confirmation email with plan details (include trial info if applicable)
|
|
8
|
+
* - Fire analytics event for new subscriber
|
|
9
|
+
* - Update marketing lists
|
|
10
|
+
*/
|
|
11
|
+
module.exports = async function ({ before, after, uid, userDoc, admin, assistant, Manager, eventType, eventId }) {
|
|
12
|
+
const isTrial = after.trial?.claimed === true;
|
|
13
|
+
|
|
14
|
+
assistant.log(`Transition [subscription/new-subscription]: uid=${uid}, product=${after.product.id}, frequency=${after.payment.frequency}, trial=${isTrial}`);
|
|
15
|
+
|
|
16
|
+
// TODO: Send order confirmation email (modify content if isTrial)
|
|
17
|
+
// TODO: Fire analytics event
|
|
18
|
+
};
|
package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition: payment-failed
|
|
3
|
+
* Triggered when a subscription payment fails (active → suspended)
|
|
4
|
+
*
|
|
5
|
+
* Use cases:
|
|
6
|
+
* - Send payment failure notification email
|
|
7
|
+
* - Include link to update payment method
|
|
8
|
+
* - Fire analytics event for churn risk
|
|
9
|
+
*/
|
|
10
|
+
module.exports = async function ({ before, after, uid, userDoc, admin, assistant, Manager, eventType, eventId }) {
|
|
11
|
+
assistant.log(`Transition [subscription/payment-failed]: uid=${uid}, product=${after.product.id}, previousStatus=${before?.status}`);
|
|
12
|
+
|
|
13
|
+
// TODO: Send payment failure email with update-payment link
|
|
14
|
+
// TODO: Fire analytics event
|
|
15
|
+
};
|
package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition: payment-recovered
|
|
3
|
+
* Triggered when a suspended subscription is recovered (suspended → active)
|
|
4
|
+
*
|
|
5
|
+
* Use cases:
|
|
6
|
+
* - Send payment recovered confirmation email
|
|
7
|
+
* - Fire analytics event for recovered subscriber
|
|
8
|
+
*/
|
|
9
|
+
module.exports = async function ({ before, after, uid, userDoc, admin, assistant, Manager, eventType, eventId }) {
|
|
10
|
+
assistant.log(`Transition [subscription/payment-recovered]: uid=${uid}, product=${after.product.id}`);
|
|
11
|
+
|
|
12
|
+
// TODO: Send payment recovered email
|
|
13
|
+
// TODO: Fire analytics event
|
|
14
|
+
};
|
package/src/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition: plan-changed
|
|
3
|
+
* Triggered when a user upgrades or downgrades their plan (product A → product B, both active + paid)
|
|
4
|
+
*
|
|
5
|
+
* Use cases:
|
|
6
|
+
* - Send plan change confirmation email
|
|
7
|
+
* - Include new plan details and what changed
|
|
8
|
+
* - Fire analytics event for upgrade/downgrade
|
|
9
|
+
*/
|
|
10
|
+
module.exports = async function ({ before, after, uid, userDoc, admin, assistant, Manager, eventType, eventId }) {
|
|
11
|
+
const direction = after.product.id > before.product.id ? 'upgrade' : 'downgrade';
|
|
12
|
+
assistant.log(`Transition [subscription/plan-changed]: uid=${uid}, ${before.product.id} → ${after.product.id} (${direction})`);
|
|
13
|
+
|
|
14
|
+
// TODO: Send plan change email with new plan details
|
|
15
|
+
// TODO: Fire analytics event
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition: subscription-cancelled
|
|
3
|
+
* Triggered when a subscription is fully cancelled (any non-cancelled → cancelled)
|
|
4
|
+
*
|
|
5
|
+
* Use cases:
|
|
6
|
+
* - Send final cancellation email
|
|
7
|
+
* - Include reactivation link or win-back offer
|
|
8
|
+
* - Fire analytics event for churned subscriber
|
|
9
|
+
* - Clean up any subscription-specific resources
|
|
10
|
+
*/
|
|
11
|
+
module.exports = async function ({ before, after, uid, userDoc, admin, assistant, Manager, eventType, eventId }) {
|
|
12
|
+
assistant.log(`Transition [subscription/subscription-cancelled]: uid=${uid}, previousProduct=${before?.product?.id}, previousStatus=${before?.status}`);
|
|
13
|
+
|
|
14
|
+
// TODO: Send cancellation email with reactivation link
|
|
15
|
+
// TODO: Fire analytics event
|
|
16
|
+
};
|
package/src/manager/index.js
CHANGED
|
@@ -709,6 +709,14 @@ Manager.prototype.debug = function () {
|
|
|
709
709
|
// Setup functions
|
|
710
710
|
Manager.prototype.setupFunctions = function (exporter, options) {
|
|
711
711
|
const self = this;
|
|
712
|
+
const resourceZone = options.resourceZone;
|
|
713
|
+
|
|
714
|
+
// Helper to create a function builder with region + runtime options
|
|
715
|
+
function fn(runtimeOptions) {
|
|
716
|
+
return self.libraries.functions
|
|
717
|
+
.runWith(runtimeOptions)
|
|
718
|
+
.region(resourceZone);
|
|
719
|
+
}
|
|
712
720
|
|
|
713
721
|
// Log
|
|
714
722
|
if (options.log) {
|
|
@@ -717,8 +725,7 @@ Manager.prototype.setupFunctions = function (exporter, options) {
|
|
|
717
725
|
|
|
718
726
|
// Setup functions
|
|
719
727
|
exporter.bm_api =
|
|
720
|
-
|
|
721
|
-
.runWith({memory: '256MB', timeoutSeconds: 60 * 5})
|
|
728
|
+
fn({memory: '256MB', timeoutSeconds: 60 * 5})
|
|
722
729
|
.https.onRequest(async (req, res) => {
|
|
723
730
|
const route = self.BemRouter(req, res).resolve();
|
|
724
731
|
|
|
@@ -734,8 +741,7 @@ Manager.prototype.setupFunctions = function (exporter, options) {
|
|
|
734
741
|
// Setup legacy functions
|
|
735
742
|
if (options.setupFunctionsLegacy) {
|
|
736
743
|
exporter.bm_signUpHandler =
|
|
737
|
-
|
|
738
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
744
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
739
745
|
.https.onRequest(async (req, res) => {
|
|
740
746
|
const Module = require(`${_legacy}/actions/sign-up-handler.js`);
|
|
741
747
|
Module.init(self, { req: req, res: res, });
|
|
@@ -750,8 +756,7 @@ Manager.prototype.setupFunctions = function (exporter, options) {
|
|
|
750
756
|
|
|
751
757
|
// Admin
|
|
752
758
|
exporter.bm_createPost =
|
|
753
|
-
|
|
754
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
759
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
755
760
|
.https.onRequest(async (req, res) => {
|
|
756
761
|
const Module = require(`${_legacy}/admin/create-post.js`);
|
|
757
762
|
Module.init(self, { req: req, res: res, });
|
|
@@ -765,8 +770,7 @@ Manager.prototype.setupFunctions = function (exporter, options) {
|
|
|
765
770
|
});
|
|
766
771
|
|
|
767
772
|
exporter.bm_firestoreWrite =
|
|
768
|
-
|
|
769
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
773
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
770
774
|
.https.onRequest(async (req, res) => {
|
|
771
775
|
const Module = require(`${_legacy}/admin/firestore-write.js`);
|
|
772
776
|
Module.init(self, { req: req, res: res, });
|
|
@@ -780,8 +784,7 @@ Manager.prototype.setupFunctions = function (exporter, options) {
|
|
|
780
784
|
});
|
|
781
785
|
|
|
782
786
|
exporter.bm_getStats =
|
|
783
|
-
|
|
784
|
-
.runWith({memory: '256MB', timeoutSeconds: 420})
|
|
787
|
+
fn({memory: '256MB', timeoutSeconds: 420})
|
|
785
788
|
.https.onRequest(async (req, res) => {
|
|
786
789
|
const Module = require(`${_legacy}/admin/get-stats.js`);
|
|
787
790
|
Module.init(self, { req: req, res: res, });
|
|
@@ -795,8 +798,7 @@ Manager.prototype.setupFunctions = function (exporter, options) {
|
|
|
795
798
|
});
|
|
796
799
|
|
|
797
800
|
exporter.bm_sendNotification =
|
|
798
|
-
|
|
799
|
-
.runWith({memory: '1GB', timeoutSeconds: 420})
|
|
801
|
+
fn({memory: '1GB', timeoutSeconds: 420})
|
|
800
802
|
.https.onRequest(async (req, res) => {
|
|
801
803
|
const Module = require(`${_legacy}/admin/send-notification.js`);
|
|
802
804
|
Module.init(self, { req: req, res: res, });
|
|
@@ -810,8 +812,7 @@ Manager.prototype.setupFunctions = function (exporter, options) {
|
|
|
810
812
|
});
|
|
811
813
|
|
|
812
814
|
exporter.bm_query =
|
|
813
|
-
|
|
814
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
815
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
815
816
|
.https.onRequest(async (req, res) => {
|
|
816
817
|
const Module = require(`${_legacy}/admin/query.js`);
|
|
817
818
|
Module.init(self, { req: req, res: res, });
|
|
@@ -825,8 +826,7 @@ Manager.prototype.setupFunctions = function (exporter, options) {
|
|
|
825
826
|
});
|
|
826
827
|
|
|
827
828
|
exporter.bm_createPostHandler =
|
|
828
|
-
|
|
829
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
829
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
830
830
|
.https.onRequest(async (req, res) => {
|
|
831
831
|
const Module = require(`${_legacy}/actions/create-post-handler.js`);
|
|
832
832
|
Module.init(self, { req: req, res: res, });
|
|
@@ -840,8 +840,7 @@ Manager.prototype.setupFunctions = function (exporter, options) {
|
|
|
840
840
|
});
|
|
841
841
|
|
|
842
842
|
exporter.bm_generateUuid =
|
|
843
|
-
|
|
844
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
843
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
845
844
|
.https.onRequest(async (req, res) => {
|
|
846
845
|
const Module = require(`${_legacy}/actions/generate-uuid.js`);
|
|
847
846
|
Module.init(self, { req: req, res: res, });
|
|
@@ -856,8 +855,7 @@ Manager.prototype.setupFunctions = function (exporter, options) {
|
|
|
856
855
|
|
|
857
856
|
// Test
|
|
858
857
|
exporter.bm_test_authenticate =
|
|
859
|
-
|
|
860
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
858
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
861
859
|
.https.onRequest(async (req, res) => {
|
|
862
860
|
const Module = require(`${_legacy}/test/authenticate.js`);
|
|
863
861
|
Module.init(self, { req: req, res: res, });
|
|
@@ -871,8 +869,7 @@ Manager.prototype.setupFunctions = function (exporter, options) {
|
|
|
871
869
|
});
|
|
872
870
|
|
|
873
871
|
exporter.bm_test_webhook =
|
|
874
|
-
|
|
875
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
872
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
876
873
|
.https.onRequest(async (req, res) => {
|
|
877
874
|
const Module = require(`${_legacy}/test/webhook.js`);
|
|
878
875
|
Module.init(self, { req: req, res: res, });
|
|
@@ -889,47 +886,40 @@ Manager.prototype.setupFunctions = function (exporter, options) {
|
|
|
889
886
|
// Setup identity functions
|
|
890
887
|
if (options.setupFunctionsIdentity) {
|
|
891
888
|
exporter.bm_authBeforeCreate =
|
|
892
|
-
|
|
893
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
889
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
894
890
|
.auth.user()
|
|
895
891
|
.beforeCreate((user, context) => self.EventMiddleware({ user, context }).run(`${events}/auth/before-create.js`));
|
|
896
892
|
|
|
897
893
|
exporter.bm_authBeforeSignIn =
|
|
898
|
-
|
|
899
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
894
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
900
895
|
.auth.user()
|
|
901
896
|
.beforeSignIn((user, context) => self.EventMiddleware({ user, context }).run(`${events}/auth/before-signin.js`));
|
|
902
897
|
}
|
|
903
898
|
|
|
904
899
|
// Setup events
|
|
905
900
|
exporter.bm_authOnCreate =
|
|
906
|
-
|
|
907
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
901
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
908
902
|
.auth.user()
|
|
909
903
|
.onCreate((user, context) => self.EventMiddleware({ user, context }).run(`${events}/auth/on-create.js`));
|
|
910
904
|
|
|
911
905
|
exporter.bm_authOnDelete =
|
|
912
|
-
|
|
913
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
906
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
914
907
|
.auth.user()
|
|
915
908
|
.onDelete((user, context) => self.EventMiddleware({ user, context }).run(`${events}/auth/on-delete.js`));
|
|
916
909
|
|
|
917
910
|
exporter.bm_notificationsOnWrite =
|
|
918
|
-
|
|
919
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
911
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
920
912
|
.firestore.document('notifications/{token}')
|
|
921
913
|
.onWrite((change, context) => self.EventMiddleware({ change, context }).run(`${events}/firestore/notifications/on-write.js`));
|
|
922
914
|
|
|
923
915
|
exporter.bm_paymentsWebhookOnWrite =
|
|
924
|
-
|
|
925
|
-
.runWith({memory: '256MB', timeoutSeconds: 60})
|
|
916
|
+
fn({memory: '256MB', timeoutSeconds: 60})
|
|
926
917
|
.firestore.document('payments-webhooks/{eventId}')
|
|
927
918
|
.onWrite((change, context) => self.EventMiddleware({ change, context }).run(`${events}/firestore/payments-webhooks/on-write.js`));
|
|
928
919
|
|
|
929
920
|
// Setup cron jobs
|
|
930
921
|
exporter.bm_cronDaily =
|
|
931
|
-
|
|
932
|
-
.runWith({ memory: '256MB', timeoutSeconds: 60 * 5})
|
|
922
|
+
fn({memory: '256MB', timeoutSeconds: 60 * 5})
|
|
933
923
|
.pubsub.schedule('0 0 * * *')
|
|
934
924
|
.onRun((context) => self.EventMiddleware({ context }).run(`${cron}/daily.js`));
|
|
935
925
|
};
|
|
@@ -5,7 +5,7 @@ let stripeInstance = null;
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Stripe shared library
|
|
8
|
-
* Provides SDK initialization and unified
|
|
8
|
+
* Provides SDK initialization, resource fetching, and unified transformations
|
|
9
9
|
*/
|
|
10
10
|
const Stripe = {
|
|
11
11
|
/**
|
|
@@ -27,6 +27,33 @@ const Stripe = {
|
|
|
27
27
|
return stripeInstance;
|
|
28
28
|
},
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Fetch the latest resource from Stripe's API
|
|
32
|
+
*
|
|
33
|
+
* @param {string} resourceType - 'subscription' | 'invoice' | 'session'
|
|
34
|
+
* @param {string} resourceId - Stripe resource ID
|
|
35
|
+
* @param {object} rawFallback - Fallback data from webhook payload (unused for Stripe — fetches live)
|
|
36
|
+
* @param {object} context - Additional context (e.g., { admin }) — unused for Stripe
|
|
37
|
+
* @returns {object} Full Stripe resource object
|
|
38
|
+
*/
|
|
39
|
+
async fetchResource(resourceType, resourceId, rawFallback, context) {
|
|
40
|
+
const stripe = this.init();
|
|
41
|
+
|
|
42
|
+
if (resourceType === 'subscription') {
|
|
43
|
+
return stripe.subscriptions.retrieve(resourceId);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (resourceType === 'invoice') {
|
|
47
|
+
return stripe.invoices.retrieve(resourceId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (resourceType === 'session') {
|
|
51
|
+
return stripe.checkout.sessions.retrieve(resourceId);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
throw new Error(`Unknown resource type: ${resourceType}`);
|
|
55
|
+
},
|
|
56
|
+
|
|
30
57
|
/**
|
|
31
58
|
* Transform a raw Stripe subscription object into the unified subscription shape
|
|
32
59
|
* This produces the exact same object stored in users/{uid}.subscription
|
|
@@ -38,7 +65,7 @@ const Stripe = {
|
|
|
38
65
|
* @param {string} options.eventId - ID of the webhook event (e.g., 'evt_xxx')
|
|
39
66
|
* @returns {object} Unified subscription object
|
|
40
67
|
*/
|
|
41
|
-
|
|
68
|
+
toUnifiedSubscription(rawSubscription, options) {
|
|
42
69
|
options = options || {};
|
|
43
70
|
const config = options.config || {};
|
|
44
71
|
|
|
@@ -94,6 +121,34 @@ const Stripe = {
|
|
|
94
121
|
},
|
|
95
122
|
};
|
|
96
123
|
},
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Transform a raw Stripe one-time payment resource into a unified shape
|
|
127
|
+
* Stub for now — will be fully implemented when one-time purchases are built out
|
|
128
|
+
*
|
|
129
|
+
* @param {object} rawResource - Raw Stripe resource (session, invoice, etc.)
|
|
130
|
+
* @param {object} options
|
|
131
|
+
* @returns {object} Unified one-time payment object
|
|
132
|
+
*/
|
|
133
|
+
toUnifiedOneTime(rawResource, options) {
|
|
134
|
+
options = options || {};
|
|
135
|
+
|
|
136
|
+
const now = powertools.timestamp(new Date(), { output: 'string' });
|
|
137
|
+
const nowUNIX = powertools.timestamp(now, { output: 'unix' });
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
id: rawResource.id || null,
|
|
141
|
+
processor: 'stripe',
|
|
142
|
+
status: rawResource.status || 'unknown',
|
|
143
|
+
raw: rawResource,
|
|
144
|
+
metadata: {
|
|
145
|
+
created: {
|
|
146
|
+
timestamp: now,
|
|
147
|
+
timestampUNIX: nowUNIX,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
},
|
|
97
152
|
};
|
|
98
153
|
|
|
99
154
|
/**
|