backend-manager 5.0.145 → 5.0.147

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.145",
3
+ "version": "5.0.147",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -19,13 +19,13 @@ const fetch = require('wonderful-fetch');
19
19
  * Track payment events across analytics platforms (non-blocking)
20
20
  * Fires GA4, Meta, and TikTok independently with per-platform payloads
21
21
  */
22
- function trackPayment({ category, transitionName, eventType, unified, uid, processor, assistant }) {
22
+ function trackPayment({ category, transitionName, eventType, unified, order, uid, processor, assistant }) {
23
23
  const Manager = assistant.Manager;
24
24
  const config = Manager.config;
25
25
 
26
26
  try {
27
27
  // Resolve what kind of payment event this is
28
- const resolved = resolvePaymentEvent(category, transitionName, eventType, unified);
28
+ const resolved = resolvePaymentEvent(category, transitionName, eventType, unified, order);
29
29
 
30
30
  if (!resolved) {
31
31
  assistant.log(`trackPayment: skipped — no trackable event (category=${category}, transition=${transitionName || 'null'}, eventType=${eventType})`);
@@ -53,13 +53,16 @@ function trackPayment({ category, transitionName, eventType, unified, uid, proce
53
53
  * Determine what kind of payment event occurred and extract common fields
54
54
  * Returns null if nothing should be tracked
55
55
  */
56
- function resolvePaymentEvent(category, transitionName, eventType, unified) {
56
+ function resolvePaymentEvent(category, transitionName, eventType, unified, order) {
57
57
  const productId = unified.product?.id;
58
58
  const productName = unified.product?.name;
59
59
  const frequency = unified.payment?.frequency || null;
60
60
  const isTrial = unified.trial?.claimed === true;
61
61
  const resourceId = unified.payment?.resourceId;
62
- const price = unified.payment?.price || 0;
62
+ const price = parseFloat(unified.payment?.price || 0);
63
+
64
+ // Compute actual amount paid (accounting for trial and discount)
65
+ const value = resolveActualValue(price, isTrial, order?.discount);
63
66
 
64
67
  const base = { productId, productName, frequency, resourceId, isTrial };
65
68
 
@@ -70,7 +73,7 @@ function resolvePaymentEvent(category, transitionName, eventType, unified) {
70
73
  }
71
74
 
72
75
  if (transitionName === 'new-subscription') {
73
- return { ...base, reason: 'first-purchase', value: price, isRecurring: false };
76
+ return { ...base, reason: 'first-purchase', value, isRecurring: false };
74
77
  }
75
78
 
76
79
  if (transitionName === 'payment-recovered') {
@@ -78,6 +81,7 @@ function resolvePaymentEvent(category, transitionName, eventType, unified) {
78
81
  }
79
82
 
80
83
  // No transition but a payment event fired (renewal)
84
+ // Renewals always use full price (discount is one-time only)
81
85
  if (!transitionName && isPaymentEvent(eventType) && price > 0) {
82
86
  return { ...base, reason: 'renewal', value: price, isRecurring: true };
83
87
  }
@@ -88,7 +92,7 @@ function resolvePaymentEvent(category, transitionName, eventType, unified) {
88
92
  // --- One-time transitions ---
89
93
  if (category === 'one-time') {
90
94
  if (transitionName === 'purchase-completed') {
91
- return { ...base, reason: 'one-time-purchase', value: price, isRecurring: false, productId: productId || 'unknown', productName: productName || 'Unknown' };
95
+ return { ...base, reason: 'one-time-purchase', value, isRecurring: false, productId: productId || 'unknown', productName: productName || 'Unknown' };
92
96
  }
93
97
 
94
98
  return null;
@@ -97,6 +101,22 @@ function resolvePaymentEvent(category, transitionName, eventType, unified) {
97
101
  return null;
98
102
  }
99
103
 
104
+ /**
105
+ * Compute the actual amount paid, accounting for trial and promo discount
106
+ * Trial = $0, discount = price - savings, otherwise full price
107
+ */
108
+ function resolveActualValue(price, isTrial, discount) {
109
+ if (isTrial) {
110
+ return 0;
111
+ }
112
+
113
+ if (discount?.valid === true && discount?.percent > 0) {
114
+ return parseFloat((price - (price * discount.percent / 100)).toFixed(2));
115
+ }
116
+
117
+ return price;
118
+ }
119
+
100
120
  /**
101
121
  * Check if a webhook event type represents a payment being made
102
122
  */
@@ -281,7 +281,7 @@ async function processPaymentEvent({ category, library, resource, resourceType,
281
281
  // Track payment analytics (non-blocking)
282
282
  // Fires independently of transitions — renewals have no transition but still need tracking
283
283
  if (shouldRunHandlers) {
284
- trackPayment({ category, transitionName, eventType, unified, uid, processor, assistant });
284
+ trackPayment({ category, transitionName, eventType, unified, order, uid, processor, assistant });
285
285
  }
286
286
 
287
287
  // Write unified subscription to user doc (subscriptions only)
@@ -10,7 +10,12 @@ module.exports = async function ({ before, after, order, uid, userDoc, assistant
10
10
  const brandName = assistant.Manager.config.brand?.name || '';
11
11
  const planName = after.product?.name || '';
12
12
 
13
- assistant.log(`Transition [subscription/new-subscription]: uid=${uid}, product=${after.product?.id}, frequency=${after.payment?.frequency}, trial=${isTrial}`);
13
+ // Pre-compute discount values for the email template
14
+ const price = parseFloat(order.unified?.payment?.price || 0);
15
+ const discount = order.discount;
16
+ const hasPromoDiscount = discount?.valid === true && discount?.percent > 0;
17
+
18
+ assistant.log(`Transition [subscription/new-subscription]: uid=${uid}, product=${after.product?.id}, frequency=${after.payment?.frequency}, trial=${isTrial}, discount=${hasPromoDiscount ? discount.code : 'none'}`);
14
19
 
15
20
  sendOrderEmail({
16
21
  template: 'main/order/confirmation',
@@ -26,6 +31,20 @@ module.exports = async function ({ before, after, order, uid, userDoc, assistant
26
31
  ...(isTrial && after.trial?.expires?.timestamp && {
27
32
  trialExpires: formatDate(after.trial.expires.timestamp),
28
33
  }),
34
+ ...(hasPromoDiscount && {
35
+ promoCode: discount.code,
36
+ promoPercent: discount.percent,
37
+ promoSavings: (price * discount.percent / 100).toFixed(2),
38
+ }),
39
+ // Amount charged on the first real payment (after trial if applicable)
40
+ firstChargeAmount: hasPromoDiscount
41
+ ? (price - (price * discount.percent / 100)).toFixed(2)
42
+ : price.toFixed(2),
43
+ totalToday: isTrial
44
+ ? '0.00'
45
+ : hasPromoDiscount
46
+ ? (price - (price * discount.percent / 100)).toFixed(2)
47
+ : price.toFixed(2),
29
48
  },
30
49
  },
31
50
  },