backend-manager 5.0.88 → 5.0.91
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 +133 -2
- package/README.md +1 -1
- package/package.json +5 -3
- package/src/cli/index.js +11 -0
- package/src/manager/events/firestore/payments-webhooks/analytics.js +170 -0
- package/src/manager/events/firestore/payments-webhooks/on-write.js +75 -315
- package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +20 -10
- package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +4 -8
- package/src/manager/events/firestore/payments-webhooks/transitions/send-email.js +67 -0
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +23 -9
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +22 -8
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +19 -8
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +19 -7
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +27 -8
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +25 -9
- package/src/manager/helpers/user.js +2 -0
- package/src/manager/libraries/payment-processors/order-id.js +18 -0
- package/src/manager/libraries/payment-processors/resolve-price-id.js +19 -0
- package/src/manager/libraries/payment-processors/stripe.js +88 -47
- package/src/manager/libraries/payment-processors/test.js +14 -11
- package/src/manager/routes/payments/intent/post.js +61 -7
- package/src/manager/routes/payments/intent/processors/stripe.js +18 -50
- package/src/manager/routes/payments/intent/processors/test.js +18 -22
- package/src/manager/routes/payments/webhook/post.js +1 -1
- package/src/test/runner.js +11 -0
- package/src/test/test-accounts.js +20 -2
- package/templates/backend-manager-config.json +31 -12
- package/test/events/payments/journey-payments-cancel.js +2 -0
- package/test/events/payments/journey-payments-failure.js +2 -0
- package/test/events/payments/journey-payments-one-time-failure.js +105 -0
- package/test/events/payments/journey-payments-one-time.js +128 -0
- package/test/events/payments/journey-payments-plan-change.js +126 -0
- package/test/events/payments/journey-payments-suspend.js +2 -0
- package/test/events/payments/journey-payments-trial.js +4 -0
- package/test/events/payments/journey-payments-upgrade.js +20 -10
- package/test/helpers/stripe-to-unified.js +17 -0
- package/test/helpers/user.js +1 -0
- package/test/routes/payments/intent.js +10 -7
- /package/bin/{bem → backend-manager} +0 -0
package/CLAUDE.md
CHANGED
|
@@ -51,13 +51,19 @@ src/
|
|
|
51
51
|
index.js # Main Manager class
|
|
52
52
|
helpers/ # Helper classes
|
|
53
53
|
assistant.js # Request/response handling
|
|
54
|
-
user.js # User property structure
|
|
54
|
+
user.js # User property structure + schema
|
|
55
55
|
analytics.js # GA4 integration
|
|
56
56
|
usage.js # Rate limiting
|
|
57
57
|
middleware.js # Request pipeline
|
|
58
58
|
settings.js # Schema validation
|
|
59
59
|
utilities.js # Batch operations
|
|
60
60
|
metadata.js # Timestamps/tags
|
|
61
|
+
libraries/
|
|
62
|
+
payment-processors/ # Shared payment processor utilities
|
|
63
|
+
stripe.js # Stripe SDK init, fetchResource, toUnified*
|
|
64
|
+
test.js # Test processor (delegates to Stripe shapes)
|
|
65
|
+
order-id.js # Order ID generation (XXXX-XXXX-XXXX)
|
|
66
|
+
resolve-price-id.js # Shared price ID resolver from config
|
|
61
67
|
functions/core/ # Built-in functions
|
|
62
68
|
actions/
|
|
63
69
|
api.js # Main bm_api handler
|
|
@@ -65,14 +71,35 @@ src/
|
|
|
65
71
|
events/
|
|
66
72
|
auth/ # Auth event handlers
|
|
67
73
|
firestore/ # Firestore triggers
|
|
74
|
+
payments-webhooks/ # Webhook processing pipeline
|
|
75
|
+
on-write.js # Orchestrator: fetch→transform→transition→write
|
|
76
|
+
analytics.js # Payment analytics tracking (GA4, Meta, TikTok)
|
|
77
|
+
transitions/ # State transition detection + handlers
|
|
78
|
+
index.js # Transition detection logic
|
|
79
|
+
send-email.js # Shared email helper for handlers
|
|
80
|
+
subscription/ # Subscription transition handlers
|
|
81
|
+
one-time/ # One-time payment transition handlers
|
|
68
82
|
cron/
|
|
69
83
|
daily.js # Daily cron runner
|
|
70
84
|
daily/{job}.js # Individual cron jobs
|
|
71
85
|
routes/ # Built-in routes
|
|
86
|
+
payments/
|
|
87
|
+
intent/ # POST /payments/intent
|
|
88
|
+
post.js # Intent creation orchestrator
|
|
89
|
+
processors/ # Per-processor intent creators
|
|
90
|
+
stripe.js # Stripe Checkout Session creation
|
|
91
|
+
test.js # Test processor (auto-fires webhooks)
|
|
92
|
+
webhook/ # POST /payments/webhook
|
|
93
|
+
post.js # Webhook ingestion + Firestore write
|
|
94
|
+
processors/ # Per-processor webhook parsers
|
|
95
|
+
stripe.js # Stripe event parsing + categorization
|
|
96
|
+
test.js # Test processor (delegates to Stripe)
|
|
72
97
|
schemas/ # Built-in schemas
|
|
73
98
|
cli/
|
|
74
99
|
index.js # CLI entry point
|
|
75
100
|
commands/ # CLI commands
|
|
101
|
+
test/
|
|
102
|
+
test-accounts.js # Test account definitions (static + journey)
|
|
76
103
|
templates/
|
|
77
104
|
backend-manager-config.json # Config template
|
|
78
105
|
```
|
|
@@ -594,8 +621,10 @@ subscription: {
|
|
|
594
621
|
},
|
|
595
622
|
payment: {
|
|
596
623
|
processor: null, // 'stripe' | 'paypal' | etc.
|
|
624
|
+
orderId: null, // BEM order ID (e.g., '1234-5678-9012')
|
|
597
625
|
resourceId: null, // provider subscription ID (e.g., 'sub_xxx')
|
|
598
626
|
frequency: null, // 'monthly' | 'annually'
|
|
627
|
+
price: 0, // resolved from config (number, e.g., 4.99)
|
|
599
628
|
startDate: { timestamp, timestampUNIX },
|
|
600
629
|
updatedBy: {
|
|
601
630
|
event: { name: null, id: null },
|
|
@@ -638,13 +667,14 @@ The `transitions/index.js` module compares the **before** state (current `users/
|
|
|
638
667
|
| Transition | Before → After | File |
|
|
639
668
|
|---|---|---|
|
|
640
669
|
| `new-subscription` | basic/null → active paid | `transitions/subscription/new-subscription.js` |
|
|
641
|
-
| `trial-started` | basic/null → active paid with trial | `transitions/subscription/trial-started.js` |
|
|
642
670
|
| `payment-failed` | active → suspended | `transitions/subscription/payment-failed.js` |
|
|
643
671
|
| `payment-recovered` | suspended → active | `transitions/subscription/payment-recovered.js` |
|
|
644
672
|
| `cancellation-requested` | pending=false → pending=true | `transitions/subscription/cancellation-requested.js` |
|
|
645
673
|
| `subscription-cancelled` | non-cancelled → cancelled | `transitions/subscription/subscription-cancelled.js` |
|
|
646
674
|
| `plan-changed` | active product A → active product B | `transitions/subscription/plan-changed.js` |
|
|
647
675
|
|
|
676
|
+
Note: Trials are NOT a separate transition. The `new-subscription` handler checks `after.trial.claimed` to determine if the subscription started with a trial.
|
|
677
|
+
|
|
648
678
|
### One-Time Transitions
|
|
649
679
|
|
|
650
680
|
| Transition | Event Type | File |
|
|
@@ -672,6 +702,107 @@ module.exports = async function ({ before, after, uid, userDoc, admin, assistant
|
|
|
672
702
|
2. Create handler file in `transitions/{category}/{name}.js`
|
|
673
703
|
3. Handler receives full context — use `assistant.log()` for logging, `Manager.project.apiUrl` for API calls
|
|
674
704
|
|
|
705
|
+
## Payment System Architecture
|
|
706
|
+
|
|
707
|
+
### Pipeline
|
|
708
|
+
|
|
709
|
+
The payment system follows a linear pipeline: **Intent → Webhook → On-Write → Transition**.
|
|
710
|
+
|
|
711
|
+
1. **Intent** (`POST /payments/intent`): Client requests a payment session. BEM validates the product, generates an order ID (`XXXX-XXXX-XXXX`), and delegates to the processor module (e.g., Stripe creates a Checkout Session). Saves to `payments-intents/{orderId}`.
|
|
712
|
+
|
|
713
|
+
2. **Webhook** (`POST /payments/webhook?processor=X&key=Y`): Processor sends event data. BEM parses and categorizes the event (`subscription` or `one-time`), extracts the UID, and saves to `payments-webhooks/{eventId}` with `status: 'pending'`.
|
|
714
|
+
|
|
715
|
+
3. **On-Write** (Firestore trigger on `payments-webhooks/{eventId}`): Fetches the latest resource from the processor API (not stale webhook data), transforms it into a unified object, detects state transitions, dispatches handlers, tracks analytics, and writes to `users/{uid}.subscription` (subscriptions) and `payments-orders/{orderId}`.
|
|
716
|
+
|
|
717
|
+
4. **Transitions** (fire-and-forget): Handler files run asynchronously after detection. Failures never block webhook processing. Skipped during tests unless `TEST_EXTENDED_MODE` is set.
|
|
718
|
+
|
|
719
|
+
### Processor Interface
|
|
720
|
+
|
|
721
|
+
Each processor implements two modules:
|
|
722
|
+
|
|
723
|
+
**Intent processor** (`routes/payments/intent/processors/{processor}.js`):
|
|
724
|
+
```javascript
|
|
725
|
+
module.exports = {
|
|
726
|
+
async createIntent({ uid, orderId, product, productId, frequency, trial, confirmationUrl, cancelUrl, Manager, assistant }) {
|
|
727
|
+
return { id, url, raw };
|
|
728
|
+
},
|
|
729
|
+
};
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
**Webhook processor** (`routes/payments/webhook/processors/{processor}.js`):
|
|
733
|
+
```javascript
|
|
734
|
+
module.exports = {
|
|
735
|
+
isSupported(eventType) { return boolean; },
|
|
736
|
+
parseWebhook(req) { return { eventId, eventType, category, resourceType, resourceId, raw, uid }; },
|
|
737
|
+
};
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
**Shared library** (`libraries/payment-processors/{processor}.js`):
|
|
741
|
+
```javascript
|
|
742
|
+
module.exports = {
|
|
743
|
+
init() { /* return SDK instance */ },
|
|
744
|
+
async fetchResource(resourceType, resourceId, rawFallback, context) { /* return resource */ },
|
|
745
|
+
toUnifiedSubscription(rawSubscription, options) { /* return unified object */ },
|
|
746
|
+
toUnifiedOneTime(rawResource, options) { /* return unified object */ },
|
|
747
|
+
};
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### Product Configuration
|
|
751
|
+
|
|
752
|
+
Products are defined in `backend-manager-config.json` under `payment.products`:
|
|
753
|
+
|
|
754
|
+
```javascript
|
|
755
|
+
payment: {
|
|
756
|
+
products: [
|
|
757
|
+
{
|
|
758
|
+
id: 'basic', // Free tier (no prices)
|
|
759
|
+
name: 'Basic',
|
|
760
|
+
type: 'subscription',
|
|
761
|
+
limits: { requests: 100 },
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
id: 'premium', // Paid subscription
|
|
765
|
+
name: 'Premium',
|
|
766
|
+
type: 'subscription',
|
|
767
|
+
limits: { requests: 1000 },
|
|
768
|
+
trial: { days: 14 }, // Optional trial period
|
|
769
|
+
prices: {
|
|
770
|
+
monthly: { amount: 4.99, stripe: 'price_xxx', paypal: null },
|
|
771
|
+
annually: { amount: 49.99, stripe: 'price_yyy', paypal: null },
|
|
772
|
+
},
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
id: 'credits-100', // One-time purchase
|
|
776
|
+
name: '100 Credits',
|
|
777
|
+
type: 'one-time',
|
|
778
|
+
prices: {
|
|
779
|
+
once: { amount: 9.99, stripe: 'price_zzz' },
|
|
780
|
+
},
|
|
781
|
+
},
|
|
782
|
+
],
|
|
783
|
+
}
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
Key rules:
|
|
787
|
+
- `type` is `'subscription'` (default) or `'one-time'`
|
|
788
|
+
- Subscription prices are keyed by frequency: `monthly`, `annually`
|
|
789
|
+
- One-time prices are keyed as `once`
|
|
790
|
+
- Each price object has processor-specific IDs (`stripe`, `paypal`, etc.)
|
|
791
|
+
- `basic` product has no `prices` — it's the free tier
|
|
792
|
+
|
|
793
|
+
### Firestore Collections
|
|
794
|
+
|
|
795
|
+
| Collection | Key | Purpose |
|
|
796
|
+
|---|---|---|
|
|
797
|
+
| `payments-intents/{orderId}` | Order ID | Intent metadata (processor, product, status) |
|
|
798
|
+
| `payments-webhooks/{eventId}` | Processor event ID | Webhook processing state + transition result |
|
|
799
|
+
| `payments-orders/{orderId}` | Order ID | Unified order data (single source of truth for orders) |
|
|
800
|
+
| `users/{uid}.subscription` | User UID | Current subscription state (subscriptions only) |
|
|
801
|
+
|
|
802
|
+
### Test Processor
|
|
803
|
+
|
|
804
|
+
The `test` processor generates Stripe-shaped data and auto-fires webhooks to the local server. Only available in non-production environments. Use `processor: 'test'` in intent requests during testing. The test webhook processor delegates to Stripe's parser since it generates Stripe-shaped payloads.
|
|
805
|
+
|
|
675
806
|
## Common Mistakes to Avoid
|
|
676
807
|
|
|
677
808
|
1. **Don't modify Manager internals directly** - Use factory methods and public APIs
|
package/README.md
CHANGED
|
@@ -886,7 +886,7 @@ BEM includes a built-in payment/subscription system with Stripe integration (ext
|
|
|
886
886
|
|
|
887
887
|
### Unified Subscription Object
|
|
888
888
|
|
|
889
|
-
The same subscription shape is stored in `users/{uid}.subscription` and `payments-
|
|
889
|
+
The same subscription shape is stored in `users/{uid}.subscription` and `payments-orders/{orderId}.subscription`:
|
|
890
890
|
|
|
891
891
|
```javascript
|
|
892
892
|
subscription: {
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backend-manager",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.91",
|
|
4
4
|
"description": "Quick tools for developing Firebase functions",
|
|
5
5
|
"main": "src/manager/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
7
|
+
"bm": "./bin/backend-manager",
|
|
8
|
+
"bem": "./bin/backend-manager",
|
|
9
|
+
"backend-manager": "./bin/backend-manager",
|
|
10
|
+
"mgr": "./bin/backend-manager"
|
|
9
11
|
},
|
|
10
12
|
"scripts": {
|
|
11
13
|
"start": "node src/manager/index.js",
|
package/src/cli/index.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const path = require('path');
|
|
1
3
|
const argv = require('yargs').argv;
|
|
2
4
|
const _ = require('lodash');
|
|
3
5
|
|
|
6
|
+
// Abort if running from ~/node_modules (accidental home directory install)
|
|
7
|
+
const _homeDir = os.homedir();
|
|
8
|
+
if (__dirname.startsWith(path.join(_homeDir, 'node_modules'))) {
|
|
9
|
+
console.error(`\nERROR: BEM is running from ~/node_modules (home directory install).`);
|
|
10
|
+
console.error(`This shadows the local project copy. Fix:`);
|
|
11
|
+
console.error(` rm -rf ~/node_modules ~/package.json ~/package-lock.json\n`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
// Import commands
|
|
5
16
|
const VersionCommand = require('./commands/version');
|
|
6
17
|
const ClearCommand = require('./commands/clear');
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment analytics tracking
|
|
3
|
+
* Fires server-side events for GA4, Meta Conversions API, and TikTok Events API
|
|
4
|
+
*
|
|
5
|
+
* Maps transitions to standard platform events:
|
|
6
|
+
* new-subscription (no trial) → purchase / Purchase / CompletePayment
|
|
7
|
+
* new-subscription (trial) → start_trial / StartTrial / Subscribe
|
|
8
|
+
* payment-recovered → purchase / Subscribe / Subscribe (recurring)
|
|
9
|
+
* purchase-completed → purchase / Purchase / CompletePayment
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Track payment events across analytics platforms (non-blocking)
|
|
14
|
+
*
|
|
15
|
+
* @param {object} options
|
|
16
|
+
* @param {string} options.category - 'subscription' or 'one-time'
|
|
17
|
+
* @param {string} options.transitionName - Detected transition (e.g., 'new-subscription', 'purchase-completed')
|
|
18
|
+
* @param {object} options.unified - Unified subscription or one-time object
|
|
19
|
+
* @param {string} options.uid - User ID
|
|
20
|
+
* @param {string} options.processor - Payment processor (e.g., 'stripe', 'paypal')
|
|
21
|
+
* @param {object} options.assistant - Assistant instance
|
|
22
|
+
* @param {object} options.Manager - Manager instance
|
|
23
|
+
*/
|
|
24
|
+
function trackPayment({ category, transitionName, unified, uid, processor, assistant, Manager }) {
|
|
25
|
+
try {
|
|
26
|
+
// Resolve the analytics event to fire based on transition
|
|
27
|
+
const event = resolvePaymentEvent(category, transitionName, unified, Manager.config);
|
|
28
|
+
|
|
29
|
+
if (!event) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
assistant.log(`trackPayment: event=${event.ga4}, value=${event.value}, currency=${event.currency}, product=${event.productId}, uid=${uid}`);
|
|
34
|
+
|
|
35
|
+
// GA4 via Measurement Protocol
|
|
36
|
+
Manager.Analytics({ assistant, uuid: uid }).event(event.ga4, {
|
|
37
|
+
transaction_id: event.transactionId,
|
|
38
|
+
value: event.value,
|
|
39
|
+
currency: event.currency,
|
|
40
|
+
items: [{
|
|
41
|
+
item_id: event.productId,
|
|
42
|
+
item_name: event.productName,
|
|
43
|
+
price: event.value,
|
|
44
|
+
quantity: 1,
|
|
45
|
+
}],
|
|
46
|
+
payment_processor: processor,
|
|
47
|
+
payment_frequency: event.frequency,
|
|
48
|
+
is_trial: event.isTrial,
|
|
49
|
+
is_recurring: event.isRecurring,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// TODO: Meta Conversions API
|
|
53
|
+
// Event name: event.meta (e.g., 'Purchase', 'StartTrial', 'Subscribe')
|
|
54
|
+
// https://developers.facebook.com/docs/marketing-api/conversions-api
|
|
55
|
+
|
|
56
|
+
// TODO: TikTok Events API
|
|
57
|
+
// Event name: event.tiktok (e.g., 'CompletePayment', 'Subscribe')
|
|
58
|
+
// https://business-api.tiktok.com/portal/docs?id=1771100865818625
|
|
59
|
+
} catch (e) {
|
|
60
|
+
assistant.error(`trackPayment failed: ${e.message}`, e);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolve which analytics event to fire based on transition + unified data
|
|
66
|
+
* Returns null if the transition doesn't warrant an analytics event
|
|
67
|
+
*/
|
|
68
|
+
function resolvePaymentEvent(category, transitionName, unified, config) {
|
|
69
|
+
if (category === 'subscription') {
|
|
70
|
+
return resolveSubscriptionEvent(transitionName, unified, config);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (category === 'one-time') {
|
|
74
|
+
return resolveOneTimeEvent(transitionName, unified, config);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Map subscription transitions to analytics events
|
|
82
|
+
*/
|
|
83
|
+
function resolveSubscriptionEvent(transitionName, unified, config) {
|
|
84
|
+
const productId = unified.product?.id;
|
|
85
|
+
const productName = unified.product?.name;
|
|
86
|
+
const frequency = unified.payment?.frequency;
|
|
87
|
+
const isTrial = unified.trial?.claimed === true;
|
|
88
|
+
const resourceId = unified.payment?.resourceId;
|
|
89
|
+
const price = unified.payment?.price || 0;
|
|
90
|
+
|
|
91
|
+
if (transitionName === 'new-subscription' && isTrial) {
|
|
92
|
+
return {
|
|
93
|
+
ga4: 'start_trial',
|
|
94
|
+
meta: 'StartTrial',
|
|
95
|
+
tiktok: 'Subscribe',
|
|
96
|
+
value: 0,
|
|
97
|
+
currency: config.payment?.currency || 'USD',
|
|
98
|
+
productId,
|
|
99
|
+
productName,
|
|
100
|
+
frequency,
|
|
101
|
+
isTrial: true,
|
|
102
|
+
isRecurring: false,
|
|
103
|
+
transactionId: resourceId,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (transitionName === 'new-subscription') {
|
|
108
|
+
return {
|
|
109
|
+
ga4: 'purchase',
|
|
110
|
+
meta: 'Purchase',
|
|
111
|
+
tiktok: 'CompletePayment',
|
|
112
|
+
value: price,
|
|
113
|
+
currency: config.payment?.currency || 'USD',
|
|
114
|
+
productId,
|
|
115
|
+
productName,
|
|
116
|
+
frequency,
|
|
117
|
+
isTrial: false,
|
|
118
|
+
isRecurring: false,
|
|
119
|
+
transactionId: resourceId,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (transitionName === 'payment-recovered') {
|
|
124
|
+
return {
|
|
125
|
+
ga4: 'purchase',
|
|
126
|
+
meta: 'Subscribe',
|
|
127
|
+
tiktok: 'Subscribe',
|
|
128
|
+
value: price,
|
|
129
|
+
currency: config.payment?.currency || 'USD',
|
|
130
|
+
productId,
|
|
131
|
+
productName,
|
|
132
|
+
frequency,
|
|
133
|
+
isTrial: false,
|
|
134
|
+
isRecurring: true,
|
|
135
|
+
transactionId: resourceId,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Map one-time transitions to analytics events
|
|
144
|
+
*/
|
|
145
|
+
function resolveOneTimeEvent(transitionName, unified, config) {
|
|
146
|
+
if (transitionName !== 'purchase-completed') {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const productId = unified.product?.id;
|
|
151
|
+
const productName = unified.product?.name;
|
|
152
|
+
const price = unified.payment?.price || 0;
|
|
153
|
+
const resourceId = unified.payment?.resourceId;
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
ga4: 'purchase',
|
|
157
|
+
meta: 'Purchase',
|
|
158
|
+
tiktok: 'CompletePayment',
|
|
159
|
+
value: price,
|
|
160
|
+
currency: config.payment?.currency || 'USD',
|
|
161
|
+
productId: productId || 'unknown',
|
|
162
|
+
productName: productName || 'Unknown',
|
|
163
|
+
frequency: null,
|
|
164
|
+
isTrial: false,
|
|
165
|
+
isRecurring: false,
|
|
166
|
+
transactionId: resourceId,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = { trackPayment };
|