backend-manager 5.0.197 → 5.0.198
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/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/src/manager/events/firestore/payments-disputes/processors/stripe.js +5 -0
- package/src/manager/libraries/payment/processors/stripe.js +6 -1
- package/src/manager/routes/payments/intent/processors/stripe.js +5 -0
- package/src/manager/routes/payments/refund/processors/stripe.js +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
14
14
|
- `Fixed` for any bug fixes.
|
|
15
15
|
- `Security` in case of vulnerabilities.
|
|
16
16
|
|
|
17
|
+
# [5.0.198] - 2026-04-10
|
|
18
|
+
### Security
|
|
19
|
+
- Added Stripe idempotency keys on all Stripe write operations to prevent duplicate charges, refunds, customers, and coupons from webhook retries, concurrent requests, or user double-clicks. Keys are scoped to stable resource identifiers and Stripe caches responses for 24 hours.
|
|
20
|
+
- `bem-dispute-refund-{chargeId}` on auto-refunds from dispute alert Firestore triggers
|
|
21
|
+
- `bem-customer-create-{uid}` on Stripe customer creation
|
|
22
|
+
- `bem-coupon-{couponId}` on coupon creation in the intent route
|
|
23
|
+
- `bem-refund-{resourceId}` on manual subscription refunds
|
|
24
|
+
|
|
17
25
|
# [5.0.197] - 2026-04-10
|
|
18
26
|
### Added
|
|
19
27
|
- `--retry=N` flag on `npx mgr setup` — re-runs the full setup sequence up to N times, stopping early as soon as all checks pass. Useful for test cases that only succeed after a prior run creates fixtures or indexes propagate.
|
package/package.json
CHANGED
|
@@ -121,9 +121,14 @@ async function processDispute(match, alert, assistant) {
|
|
|
121
121
|
// Issue full refund
|
|
122
122
|
if (match.chargeId) {
|
|
123
123
|
try {
|
|
124
|
+
// Idempotency key scoped to the charge prevents double-refund when the
|
|
125
|
+
// Firestore trigger fires more than once (retries, re-delivered webhooks,
|
|
126
|
+
// etc.). Stripe caches the response for 24 hours.
|
|
124
127
|
const refund = await stripe.refunds.create({
|
|
125
128
|
charge: match.chargeId,
|
|
126
129
|
amount: amountCents,
|
|
130
|
+
}, {
|
|
131
|
+
idempotencyKey: `bem-dispute-refund-${match.chargeId}`,
|
|
127
132
|
});
|
|
128
133
|
|
|
129
134
|
result.refundId = refund.id;
|
|
@@ -206,6 +206,9 @@ const Stripe = {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
// Create new customer
|
|
209
|
+
// Use an idempotency key scoped to the uid so concurrent creates (e.g. user
|
|
210
|
+
// double-clicks checkout) return the same customer instead of duplicates.
|
|
211
|
+
// Stripe caches the response under this key for 24 hours.
|
|
209
212
|
const params = {
|
|
210
213
|
metadata: { uid },
|
|
211
214
|
};
|
|
@@ -214,7 +217,9 @@ const Stripe = {
|
|
|
214
217
|
params.email = email;
|
|
215
218
|
}
|
|
216
219
|
|
|
217
|
-
const customer = await stripe.customers.create(params
|
|
220
|
+
const customer = await stripe.customers.create(params, {
|
|
221
|
+
idempotencyKey: `bem-customer-create-${uid}`,
|
|
222
|
+
});
|
|
218
223
|
assistant.log(`Created new Stripe customer: ${customer.id}`);
|
|
219
224
|
return customer;
|
|
220
225
|
},
|
|
@@ -153,11 +153,16 @@ async function resolveStripeCoupon(stripe, discount, assistant) {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
// Create the coupon
|
|
156
|
+
// Idempotency key uses the deterministic couponId so concurrent requests for
|
|
157
|
+
// the same discount code don't race each other into a duplicate-create error.
|
|
158
|
+
// Stripe returns the cached response for 24 hours.
|
|
156
159
|
await stripe.coupons.create({
|
|
157
160
|
id: couponId,
|
|
158
161
|
percent_off: discount.percent,
|
|
159
162
|
duration: 'once',
|
|
160
163
|
name: `${discount.code} (${discount.percent}% off first payment)`,
|
|
164
|
+
}, {
|
|
165
|
+
idempotencyKey: `bem-coupon-${couponId}`,
|
|
161
166
|
});
|
|
162
167
|
|
|
163
168
|
assistant.log(`Stripe coupon created: ${couponId}`);
|
|
@@ -79,10 +79,16 @@ module.exports = {
|
|
|
79
79
|
? invoice.payment_intent
|
|
80
80
|
: invoice.payment_intent.id;
|
|
81
81
|
|
|
82
|
+
// Idempotency key scoped to the subscription prevents double-refund from
|
|
83
|
+
// a user double-clicking the refund button. Stripe caches the response for
|
|
84
|
+
// 24 hours — any concurrent or repeated refund request for the same sub
|
|
85
|
+
// within that window returns the original refund instead of issuing a new one.
|
|
82
86
|
const refund = await stripe.refunds.create({
|
|
83
87
|
payment_intent: paymentIntentId,
|
|
84
88
|
amount: refundAmount,
|
|
85
89
|
reason: 'requested_by_customer',
|
|
90
|
+
}, {
|
|
91
|
+
idempotencyKey: `bem-refund-${resourceId}`,
|
|
86
92
|
});
|
|
87
93
|
|
|
88
94
|
assistant.log(`Stripe refund created: refundId=${refund.id}, amount=${refundAmount}, full=${isFullRefund}, uid=${uid}`);
|