backend-manager 5.0.197 → 5.0.199

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 CHANGED
@@ -14,6 +14,18 @@ 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.199] - 2026-04-23
18
+ ### Fixed
19
+ - Storage helper (`Manager.storage().get()` / `.set()`) was referencing `_.get` and `_.set` without a lodash namespace import — lodash is destructured at the top of the file, so `_` was undefined and every call crashed. Destructured `get` and `set` (aliased as `_get` / `_set`) and updated both call sites.
20
+
21
+ # [5.0.198] - 2026-04-10
22
+ ### Security
23
+ - 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.
24
+ - `bem-dispute-refund-{chargeId}` on auto-refunds from dispute alert Firestore triggers
25
+ - `bem-customer-create-{uid}` on Stripe customer creation
26
+ - `bem-coupon-{couponId}` on coupon creation in the intent route
27
+ - `bem-refund-{resourceId}` on manual subscription refunds
28
+
17
29
  # [5.0.197] - 2026-04-10
18
30
  ### Added
19
31
  - `--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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.197",
3
+ "version": "5.0.199",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -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;
@@ -1,6 +1,6 @@
1
1
  // Libraries
2
2
  const path = require('path');
3
- const { mergeWith, isArray } = require('lodash');
3
+ const { mergeWith, isArray, get: _get, set: _set } = require('lodash');
4
4
  const jetpack = require('fs-jetpack');
5
5
  const JSON5 = require('json5');
6
6
  const EventEmitter = require('events');
@@ -657,10 +657,10 @@ Manager.prototype.storage = function (options) {
657
657
  _db: db,
658
658
  _location: location,
659
659
  get(path, defaultValue) {
660
- const result = _.get(db.data, path, defaultValue);
660
+ const result = _get(db.data, path, defaultValue);
661
661
  return { value() { return result; } };
662
662
  },
663
- set(path, value) { _.set(db.data, path, value); return this; },
663
+ set(path, value) { _set(db.data, path, value); return this; },
664
664
  write() { db.write(); return this; },
665
665
  getState() { return db.data; },
666
666
  setState(data) { db.data = data; return this; },
@@ -214,6 +214,7 @@
214
214
  "a45.in",
215
215
  "a7996.com",
216
216
  "aa5zy64.com",
217
+ "aachendate.de",
217
218
  "aakk.link",
218
219
  "aakkmail.com",
219
220
  "aaqwe.ru",
@@ -670,6 +671,7 @@
670
671
  "bltiwd.com",
671
672
  "bluedumpling.info",
672
673
  "bluewerks.com",
674
+ "bmoar.com",
673
675
  "bnote.com",
674
676
  "boatmail.us",
675
677
  "bobgf.ru",
@@ -711,6 +713,7 @@
711
713
  "boxomail.live",
712
714
  "boxtemp.com.br",
713
715
  "boyaga.com",
716
+ "bpotogo.com",
714
717
  "bptfp.net",
715
718
  "brand-app.biz",
716
719
  "brandallday.net",
@@ -784,6 +787,7 @@
784
787
  "cafesui.com",
785
788
  "caftee.com",
786
789
  "calendro.fr.nf",
790
+ "california.edu.pl",
787
791
  "californiafitnessdeals.com",
788
792
  "calima.asso.st",
789
793
  "cam4you.cc",
@@ -853,6 +857,7 @@
853
857
  "chaocosen.com",
854
858
  "chapsmail.com",
855
859
  "chasefreedomactivate.com",
860
+ "chatgptmail.shop",
856
861
  "chatgptuk.pp.ua",
857
862
  "chatich.com",
858
863
  "chatworkstation.com",
@@ -887,6 +892,7 @@
887
892
  "chuan.info",
888
893
  "chumpstakingdumps.com",
889
894
  "cigar-auctions.com",
895
+ "cimario.com",
890
896
  "citmo.net",
891
897
  "civikli.com",
892
898
  "civx.org",
@@ -971,6 +977,7 @@
971
977
  "consumerriot.com",
972
978
  "contaco.org",
973
979
  "contact.infos.st",
980
+ "contactbox.work",
974
981
  "contbay.com",
975
982
  "cood.food",
976
983
  "cooh-2.site",
@@ -980,6 +987,7 @@
980
987
  "copyhome.win",
981
988
  "coreclip.com",
982
989
  "corhash.net",
990
+ "cosdas.com",
983
991
  "cosmorph.com",
984
992
  "cosoinan.com",
985
993
  "cotasen.com",
@@ -1114,6 +1122,7 @@
1114
1122
  "delikkt.de",
1115
1123
  "delivrmail.com",
1116
1124
  "delorex.com",
1125
+ "deltajohnsons.com",
1117
1126
  "deluxmail.com",
1118
1127
  "demen.ml",
1119
1128
  "dengekibunko.ga",
@@ -1148,6 +1157,8 @@
1148
1157
  "diemhenvn.com",
1149
1158
  "digdig.org",
1150
1159
  "digi-value.fr",
1160
+ "digibeast.my",
1161
+ "digibeast.store",
1151
1162
  "diginey.com",
1152
1163
  "digital-message.com",
1153
1164
  "digitalesbusiness.info",
@@ -1263,6 +1274,7 @@
1263
1274
  "dpmurt.my",
1264
1275
  "dpptd.com",
1265
1276
  "dr69.site",
1277
+ "draughtier.com",
1266
1278
  "drdrb.com",
1267
1279
  "drdrb.net",
1268
1280
  "dreamclarify.org",
@@ -1313,6 +1325,7 @@
1313
1325
  "dv2.host",
1314
1326
  "dvdpit.com",
1315
1327
  "dwse.edu.pl",
1328
+ "dwseal.com",
1316
1329
  "dyceroprojects.com",
1317
1330
  "dygovil.com",
1318
1331
  "dz17.net",
@@ -1321,6 +1334,7 @@
1321
1334
  "e-marketstore.ru",
1322
1335
  "e-pool.co.uk",
1323
1336
  "e-pool.uk",
1337
+ "e-postkasten.de",
1324
1338
  "e-record.com",
1325
1339
  "e-tomarigi.com",
1326
1340
  "e3z.de",
@@ -1406,6 +1420,7 @@
1406
1420
  "emailaoa.pro",
1407
1421
  "emailate.com",
1408
1422
  "emailawb.pro",
1423
+ "emailax.pro",
1409
1424
  "emailbbox.pro",
1410
1425
  "emailbin.net",
1411
1426
  "emailboxer.one",
@@ -1424,6 +1439,7 @@
1424
1439
  "emailgen.uk",
1425
1440
  "emailgenerator.de",
1426
1441
  "emailgo.de",
1442
+ "emailhook.site",
1427
1443
  "emailias.com",
1428
1444
  "emailigo.de",
1429
1445
  "emailinfive.com",
@@ -1431,6 +1447,7 @@
1431
1447
  "emailkp.com",
1432
1448
  "emaillime.com",
1433
1449
  "emailmiser.com",
1450
+ "emailmuaqat.shop",
1434
1451
  "emailna.co",
1435
1452
  "emailnator.com",
1436
1453
  "emailnax.com",
@@ -1508,6 +1525,7 @@
1508
1525
  "epb.ro",
1509
1526
  "epbox.ru",
1510
1527
  "epbox.store",
1528
+ "epetsoft.com",
1511
1529
  "ephemail.net",
1512
1530
  "ephemeral.email",
1513
1531
  "eposta.buzz",
@@ -1629,6 +1647,7 @@
1629
1647
  "fansworldwide.de",
1630
1648
  "fantastu.com",
1631
1649
  "fantasymail.de",
1650
+ "fanymail.com",
1632
1651
  "farah.rip",
1633
1652
  "farrse.co.uk",
1634
1653
  "fasssd.ru",
@@ -1696,6 +1715,7 @@
1696
1715
  "fingso.com",
1697
1716
  "fir.hk",
1698
1717
  "fira.my",
1718
+ "firemail.com.br",
1699
1719
  "firemailbox.club",
1700
1720
  "firstlawyer.org",
1701
1721
  "fitnesrezink.ru",
@@ -1970,6 +1990,7 @@
1970
1990
  "go2usa.info",
1971
1991
  "go2vpn.net",
1972
1992
  "goatmail.uk",
1993
+ "gob.re",
1973
1994
  "godfare.com",
1974
1995
  "goemailgo.com",
1975
1996
  "goeschman.com",
@@ -2154,6 +2175,7 @@
2154
2175
  "hook2ad.com",
2155
2176
  "hooooooo.store",
2156
2177
  "hopemail.biz",
2178
+ "hopesx.com",
2157
2179
  "horizonspost.com",
2158
2180
  "hornyalwary.top",
2159
2181
  "host1s.com",
@@ -2220,6 +2242,7 @@
2220
2242
  "icmans.com",
2221
2243
  "iconmal.com",
2222
2244
  "icousd.com",
2245
+ "icubik.com",
2223
2246
  "icx.in",
2224
2247
  "icx.ro",
2225
2248
  "icznn.com",
@@ -2418,6 +2441,7 @@
2418
2441
  "jasonbella.online",
2419
2442
  "javadmin.com",
2420
2443
  "javaemail.com",
2444
+ "jbsze.com",
2421
2445
  "jcnorris.com",
2422
2446
  "jdmadventures.com",
2423
2447
  "jdz.ro",
@@ -2489,6 +2513,7 @@
2489
2513
  "kagi.be",
2490
2514
  "kakadua.net",
2491
2515
  "kakao-mail.com",
2516
+ "kakator.com",
2492
2517
  "kakslsie.store",
2493
2518
  "kalapi.org",
2494
2519
  "kamen-market.ru",
@@ -2509,6 +2534,7 @@
2509
2534
  "kasmail.com",
2510
2535
  "kaspop.com",
2511
2536
  "katztube.com",
2537
+ "kayilo.com",
2512
2538
  "kazelink.ml",
2513
2539
  "kbox.li",
2514
2540
  "kcoporation.com",
@@ -2966,6 +2992,7 @@
2966
2992
  "mailshell.com",
2967
2993
  "mailshiv.com",
2968
2994
  "mailshou.com",
2995
+ "mailshun.com",
2969
2996
  "mailsiphon.com",
2970
2997
  "mailslapping.com",
2971
2998
  "mailslite.com",
@@ -3159,6 +3186,7 @@
3159
3186
  "moondyal.com",
3160
3187
  "moonfee.com",
3161
3188
  "moonwake.com",
3189
+ "mooo.com",
3162
3190
  "moot.es",
3163
3191
  "moreawesomethanyou.com",
3164
3192
  "moreorcs.com",
@@ -3290,6 +3318,8 @@
3290
3318
  "nanonym.ch",
3291
3319
  "nanopools.info",
3292
3320
  "naobk.com",
3321
+ "narsub.online",
3322
+ "narsub.shop",
3293
3323
  "naslazhdai.ru",
3294
3324
  "nationalgardeningclub.com",
3295
3325
  "nautonk.com",
@@ -3300,6 +3330,7 @@
3300
3330
  "naxx.dev",
3301
3331
  "naylonksosmed.com",
3302
3332
  "naymedia.com",
3333
+ "nazisat.com",
3303
3334
  "nbmbb.com",
3304
3335
  "nbzmr.com",
3305
3336
  "ncien.com",
@@ -3515,6 +3546,7 @@
3515
3546
  "onheb.com",
3516
3547
  "onionred.com",
3517
3548
  "onlatedotcom.info",
3549
+ "onldm.net",
3518
3550
  "online.ms",
3519
3551
  "onlineidea.info",
3520
3552
  "onlyapp.net",
@@ -3596,6 +3628,7 @@
3596
3628
  "paplease.com",
3597
3629
  "para2019.ru",
3598
3630
  "parlimentpetitioner.tk",
3631
+ "parsitv.com",
3599
3632
  "pastebitch.com",
3600
3633
  "pastryofistanbul.com",
3601
3634
  "patity.com",
@@ -3948,6 +3981,7 @@
3948
3981
  "rotaniliam.com",
3949
3982
  "rotomails.co.uk",
3950
3983
  "rotomails.com",
3984
+ "route64.de",
3951
3985
  "routerboardvietnam.com",
3952
3986
  "rover.info",
3953
3987
  "rowdydow.com",
@@ -4031,6 +4065,7 @@
4031
4065
  "secure-mail.cc",
4032
4066
  "secured-link.net",
4033
4067
  "securehost.com.es",
4068
+ "seduck.com",
4034
4069
  "seedspeed.site",
4035
4070
  "seekapps.com",
4036
4071
  "seekjobs4u.com",
@@ -4043,6 +4078,7 @@
4043
4078
  "send4.uk",
4044
4079
  "sendapp.uk",
4045
4080
  "sendfree.org",
4081
+ "sendgrid.ovh",
4046
4082
  "sendingspecialflyers.com",
4047
4083
  "sendnow.win",
4048
4084
  "sendos.fr.nf",
@@ -4064,6 +4100,7 @@
4064
4100
  "sgm.ovh",
4065
4101
  "shadap.org",
4066
4102
  "shalar.net",
4103
+ "sharebot.net",
4067
4104
  "sharedmailbox.org",
4068
4105
  "sharkfaces.com",
4069
4106
  "sharklasers.com",
@@ -4342,6 +4379,7 @@
4342
4379
  "storiqax.top",
4343
4380
  "storj99.com",
4344
4381
  "storj99.top",
4382
+ "strayhood.org",
4345
4383
  "streetwisemail.com",
4346
4384
  "stromox.com",
4347
4385
  "stuckmail.com",
@@ -4423,6 +4461,7 @@
4423
4461
  "taxibmt.net",
4424
4462
  "tb-on-line.net",
4425
4463
  "tbgroupconsultants.com",
4464
+ "tbr.fr.nf",
4426
4465
  "tcwlm.com",
4427
4466
  "tcwlx.com",
4428
4467
  "tdtda.com",
@@ -4502,6 +4541,7 @@
4502
4541
  "tempmailo.com",
4503
4542
  "tempmailr.com",
4504
4543
  "tempmails.net",
4544
+ "tempmailto.com",
4505
4545
  "tempmailyo.org",
4506
4546
  "tempomail.fr",
4507
4547
  "tempomail.org",
@@ -4546,6 +4586,7 @@
4546
4586
  "the23app.com",
4547
4587
  "theaviors.com",
4548
4588
  "thebearshark.com",
4589
+ "thebest73.shop",
4549
4590
  "thecarinformation.com",
4550
4591
  "thechildrensfocus.com",
4551
4592
  "thecity.biz",
@@ -4564,6 +4605,7 @@
4564
4605
  "thereddoors.online",
4565
4606
  "theroyalweb.club",
4566
4607
  "thescrappermovie.com",
4608
+ "these.cc",
4567
4609
  "thespamfather.com",
4568
4610
  "theteastory.info",
4569
4611
  "thetechnext.net",
@@ -4841,6 +4883,7 @@
4841
4883
  "upphim.net",
4842
4884
  "upsnab.net",
4843
4885
  "urfunktion.se",
4886
+ "urgentmail.ovh",
4844
4887
  "urhen.com",
4845
4888
  "uroid.com",
4846
4889
  "us.af",
@@ -4990,6 +5033,7 @@
4990
5033
  "voxelcore.com",
4991
5034
  "voxinh.net",
4992
5035
  "vpn.st",
5036
+ "vpn64.de",
4993
5037
  "vpnseat.com",
4994
5038
  "vps30.com",
4995
5039
  "vps911.net",
@@ -4999,6 +5043,7 @@
4999
5043
  "vsimcard.com",
5000
5044
  "vsmailpro.com",
5001
5045
  "vssms.com",
5046
+ "vtmpj.net",
5002
5047
  "vtxmail.us",
5003
5048
  "vubby.com",
5004
5049
  "vuiy.pw",
@@ -5231,6 +5276,7 @@
5231
5276
  "yarnpedia.ga",
5232
5277
  "ycare.de",
5233
5278
  "ycn.ro",
5279
+ "ydns.eu",
5234
5280
  "ye.vc",
5235
5281
  "yecp.ru",
5236
5282
  "yecp.store",
@@ -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}`);