@teleporthq/teleport-plugin-next-workflows 0.43.39 → 0.43.40
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/__tests__/ecommerce-customhandler-rewriter.test.ts +65 -32
- package/__tests__/payment-charge-user-handler-serialization.test.ts +94 -0
- package/__tests__/stock-decrement-audit.test.ts +19 -0
- package/dist/cjs/ecommerce/stock-decrement.d.ts.map +1 -1
- package/dist/cjs/ecommerce/stock-decrement.js +45 -36
- package/dist/cjs/ecommerce/stock-decrement.js.map +1 -1
- package/dist/cjs/nodes/payment/payment-charge-user.d.ts.map +1 -1
- package/dist/cjs/nodes/payment/payment-charge-user.js +40 -32
- package/dist/cjs/nodes/payment/payment-charge-user.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/ecommerce/stock-decrement.d.ts.map +1 -1
- package/dist/esm/ecommerce/stock-decrement.js +45 -36
- package/dist/esm/ecommerce/stock-decrement.js.map +1 -1
- package/dist/esm/nodes/payment/payment-charge-user.d.ts.map +1 -1
- package/dist/esm/nodes/payment/payment-charge-user.js +40 -32
- package/dist/esm/nodes/payment/payment-charge-user.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/ecommerce/stock-decrement.ts +51 -36
- package/src/nodes/payment/payment-charge-user.ts +40 -34
|
@@ -58,6 +58,21 @@ export const looksLikeStockDecrementBuilder = (code: string): boolean => {
|
|
|
58
58
|
return false
|
|
59
59
|
}
|
|
60
60
|
if (code.indexOf(STOCK_DECREMENT_MARKER) >= 0) {
|
|
61
|
+
// Already our rewrite. A prior rewriter version (the
|
|
62
|
+
// `GREATEST(0, COALESCE(quantity, 0) - …)` shape below) permanently
|
|
63
|
+
// floored an unlimited-stock (NULL quantity) product to 0 the first
|
|
64
|
+
// time an order touched it — turning "infinite stock" into
|
|
65
|
+
// "out of stock" forever. Re-match that legacy shape so the next
|
|
66
|
+
// regeneration upgrades already-rewritten projects to the current,
|
|
67
|
+
// NULL-preserving SQL (`buildStockDecrementBuilder` below). A rewrite
|
|
68
|
+
// that already carries the NULL guard is up to date — leave it alone.
|
|
69
|
+
if (
|
|
70
|
+
code.indexOf('teleport_products') >= 0 &&
|
|
71
|
+
/quantity\s*=\s*GREATEST\s*\(\s*\d+\s*,\s*COALESCE\s*\(\s*quantity/i.test(code) &&
|
|
72
|
+
code.indexOf('WHEN quantity IS NULL THEN NULL') < 0
|
|
73
|
+
) {
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
61
76
|
return false
|
|
62
77
|
}
|
|
63
78
|
if (code.indexOf('teleport_products') < 0) {
|
|
@@ -188,31 +203,27 @@ ${STOCK_DECREMENT_HELPERS}
|
|
|
188
203
|
// the row AFTER the SET clause runs (so bare \`quantity\` would
|
|
189
204
|
// be the new value, not the old).
|
|
190
205
|
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
var setClause = "quantity = GREATEST(0, COALESCE(quantity, 0) - CASE id" + caseExpr + " ELSE 0 END), updated_at = NOW()";
|
|
208
|
-
// \`old_quantity\` in RETURNING reconstructs the pre-update value.
|
|
209
|
-
// After the GREATEST clamp the SET value is no longer
|
|
210
|
-
// "old - delta" — it might be 0 because of the floor. So we can't
|
|
206
|
+
// \`teleport_products.quantity\` is NULL for every product the
|
|
207
|
+
// merchant hasn't given a finite count — that NULL means
|
|
208
|
+
// "unlimited stock", not "zero stock waiting to be bootstrapped".
|
|
209
|
+
// The SET clause's \`CASE WHEN quantity IS NULL THEN NULL …\` guard
|
|
210
|
+
// leaves those rows untouched forever: an unlimited product must
|
|
211
|
+
// stay unlimited across every order, not silently become
|
|
212
|
+
// out-of-stock (0) the first time someone buys it. Only a row
|
|
213
|
+
// that already carries a real, finite count is decremented, and
|
|
214
|
+
// \`GREATEST(0, …)\` floors THAT arithmetic at zero so we never
|
|
215
|
+
// persist a negative quantity for a tracked product.
|
|
216
|
+
var setClause = "quantity = CASE WHEN quantity IS NULL THEN NULL ELSE GREATEST(0, quantity - (CASE id" + caseExpr + " ELSE 0 END)) END, updated_at = NOW()";
|
|
217
|
+
// \`old_quantity\` in RETURNING reconstructs the pre-update value. A
|
|
218
|
+
// row whose (post-update) \`new_quantity\` is NULL was NULL before too
|
|
219
|
+
// (the SET clause never changes a NULL row), so \`old_quantity\` is
|
|
220
|
+
// NULL as well. For a tracked row, the GREATEST clamp means the SET
|
|
221
|
+
// value is no longer "old - delta" once it floors at 0, so we can't
|
|
211
222
|
// just invert the SET expression; instead we expose 0 as a
|
|
212
223
|
// conservative lower bound for what was there before. Downstream
|
|
213
224
|
// (low-stock SELECT) only uses \`new_quantity\` for the threshold
|
|
214
225
|
// comparison, so this approximation is fine.
|
|
215
|
-
var returningClause = "RETURNING id, quantity AS new_quantity, GREATEST(0,
|
|
226
|
+
var returningClause = "RETURNING id, quantity AS new_quantity, (CASE WHEN quantity IS NULL THEN NULL ELSE GREATEST(0, quantity + (CASE id" + caseExpr + " ELSE 0 END)) END) AS old_quantity";
|
|
216
227
|
|
|
217
228
|
// When backorders are DISALLOWED we add a per-row guard:
|
|
218
229
|
// AND (quantity IS NULL OR quantity >= CASE id ... END)
|
|
@@ -223,17 +234,12 @@ ${STOCK_DECREMENT_HELPERS}
|
|
|
223
234
|
// would otherwise allow two simultaneous orders to drive stock
|
|
224
235
|
// below zero.
|
|
225
236
|
//
|
|
226
|
-
// The "quantity IS NULL OR" clause
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
// decrement the row holds an integer (now floored at 0 by the
|
|
233
|
-
// GREATEST clamp in the SET clause) and from then on the normal
|
|
234
|
-
// \`quantity >= delta\` guard is enforced — so the merchant sees
|
|
235
|
-
// the zero value in the admin panel and gets a clear "set initial
|
|
236
|
-
// stock for this product" signal.
|
|
237
|
+
// The "quantity IS NULL OR" clause lets unlimited-stock products
|
|
238
|
+
// through the guard so the row is still touched (its RETURNING row
|
|
239
|
+
// still surfaces to the caller, e.g. for the low-stock SELECT) — the
|
|
240
|
+
// SET clause above then leaves the value itself untouched, so an
|
|
241
|
+
// unlimited product stays unlimited no matter how many orders are
|
|
242
|
+
// placed against it, with or without backorders enabled.
|
|
237
243
|
var whereClause;
|
|
238
244
|
if (ALLOW_BACKORDERS) {
|
|
239
245
|
whereClause = "WHERE id IN (" + quotedIds + ")";
|
|
@@ -328,10 +334,12 @@ const looksLikeOrderDecrementCustomHandler = (code: string): boolean => {
|
|
|
328
334
|
// Belt-and-braces: even when the marker is present, the code must
|
|
329
335
|
// actually be a stock-decrement (not, say, an unrelated rewritten
|
|
330
336
|
// node that happens to live next to teleport_products references).
|
|
331
|
-
// Accept
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
//
|
|
337
|
+
// Accept every shape `buildStockDecrementBuilder` has emitted over
|
|
338
|
+
// time: the legacy concat shape (`quantity = quantity - CASE id …`),
|
|
339
|
+
// the COALESCE safety-wrapped shape (`quantity = GREATEST(0,
|
|
340
|
+
// COALESCE(quantity, 0) - CASE id …)`), and the current
|
|
341
|
+
// NULL-preserving shape (`quantity = CASE WHEN quantity IS NULL
|
|
342
|
+
// THEN NULL ELSE GREATEST(0, quantity - CASE id …) END`).
|
|
335
343
|
if (code.indexOf('teleport_products') >= 0) {
|
|
336
344
|
if (/quantity\s*=\s*quantity\s*-/.test(code)) {
|
|
337
345
|
return true
|
|
@@ -339,6 +347,13 @@ const looksLikeOrderDecrementCustomHandler = (code: string): boolean => {
|
|
|
339
347
|
if (/quantity\s*=\s*GREATEST\s*\(\s*\d+\s*,\s*COALESCE\s*\(\s*quantity/i.test(code)) {
|
|
340
348
|
return true
|
|
341
349
|
}
|
|
350
|
+
if (
|
|
351
|
+
/quantity\s*=\s*CASE\s+WHEN\s+quantity\s+IS\s+NULL\s+THEN\s+NULL\s+ELSE\s+GREATEST/i.test(
|
|
352
|
+
code
|
|
353
|
+
)
|
|
354
|
+
) {
|
|
355
|
+
return true
|
|
356
|
+
}
|
|
342
357
|
}
|
|
343
358
|
return false
|
|
344
359
|
}
|
|
@@ -1,31 +1,5 @@
|
|
|
1
1
|
import { NodeHandlerGenerator, handlerToString } from '../types'
|
|
2
2
|
|
|
3
|
-
// Stripe treats these currencies as zero-decimal (no fractional units). The
|
|
4
|
-
// amount passed to the Stripe API must be the whole-currency integer, not
|
|
5
|
-
// multiplied by 100. See https://stripe.com/docs/currencies#zero-decimal
|
|
6
|
-
const STRIPE_ZERO_DECIMAL_CURRENCIES = [
|
|
7
|
-
'BIF',
|
|
8
|
-
'CLP',
|
|
9
|
-
'DJF',
|
|
10
|
-
'GNF',
|
|
11
|
-
'JPY',
|
|
12
|
-
'KMF',
|
|
13
|
-
'KRW',
|
|
14
|
-
'MGA',
|
|
15
|
-
'PYG',
|
|
16
|
-
'RWF',
|
|
17
|
-
'UGX',
|
|
18
|
-
'VND',
|
|
19
|
-
'VUV',
|
|
20
|
-
'XAF',
|
|
21
|
-
'XOF',
|
|
22
|
-
'XPF',
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
// Currencies where the smallest unit is 1/1000 of the major unit. Stripe
|
|
26
|
-
// expects amounts in the smallest unit (e.g. 1.500 JOD -> 1500).
|
|
27
|
-
const STRIPE_THREE_DECIMAL_CURRENCIES = ['BHD', 'IQD', 'JOD', 'KWD', 'LYD', 'OMR', 'TND']
|
|
28
|
-
|
|
29
3
|
// Contract for the `amount` config field:
|
|
30
4
|
// A floating-point number in the MAJOR currency unit (e.g. 99.99 USD,
|
|
31
5
|
// 9999 JPY). Individual line items use the same `unitAmount` semantics.
|
|
@@ -157,16 +131,54 @@ async function payment_charge_user(config: any, _context: Record<string, unknown
|
|
|
157
131
|
// smallest-unit integer. Zero-decimal currencies pass through rounded;
|
|
158
132
|
// three-decimal currencies multiply by 1000; everything else multiplies by
|
|
159
133
|
// 100. Always returns a safe non-negative integer.
|
|
134
|
+
//
|
|
135
|
+
// The currency lists are declared LOCAL to this function rather than as
|
|
136
|
+
// shared top-level consts. `generateHandler()` below assembles the final
|
|
137
|
+
// handler by calling `.toString()` on several functions independently and
|
|
138
|
+
// concatenating the source; a top-level const is fine in an unminified
|
|
139
|
+
// build, but when this package itself is bundled + minified by a consumer
|
|
140
|
+
// (e.g. teleport-gui's browser packer, built with webpack/Terser), the
|
|
141
|
+
// minifier is free to rename the top-level const's declaration — nothing
|
|
142
|
+
// in the bundle calls it by name, only this runtime `.toString()` read
|
|
143
|
+
// does, which is invisible to the minifier. `toStripeMinorUnits.toString()`
|
|
144
|
+
// would then embed a reference to a name (e.g. a mangled `Oo`) that is
|
|
145
|
+
// never declared anywhere in the generated workflow segment file, throwing
|
|
146
|
+
// "<mangled name> is not defined" the first time a Stripe charge runs.
|
|
147
|
+
// Declaring the lists inside the function keeps the declaration and every
|
|
148
|
+
// reference to it in the SAME `.toString()` snapshot, so a consistent
|
|
149
|
+
// rename by the minifier can never separate them. See `resolveProviderSecret`
|
|
150
|
+
// above for the same class of bug with the `process` global.
|
|
160
151
|
function toStripeMinorUnits(major: any, currency: string): number {
|
|
152
|
+
const zeroDecimalCurrencies = [
|
|
153
|
+
'BIF',
|
|
154
|
+
'CLP',
|
|
155
|
+
'DJF',
|
|
156
|
+
'GNF',
|
|
157
|
+
'JPY',
|
|
158
|
+
'KMF',
|
|
159
|
+
'KRW',
|
|
160
|
+
'MGA',
|
|
161
|
+
'PYG',
|
|
162
|
+
'RWF',
|
|
163
|
+
'UGX',
|
|
164
|
+
'VND',
|
|
165
|
+
'VUV',
|
|
166
|
+
'XAF',
|
|
167
|
+
'XOF',
|
|
168
|
+
'XPF',
|
|
169
|
+
]
|
|
170
|
+
// Currencies where the smallest unit is 1/1000 of the major unit. Stripe
|
|
171
|
+
// expects amounts in the smallest unit (e.g. 1.500 JOD -> 1500).
|
|
172
|
+
const threeDecimalCurrencies = ['BHD', 'IQD', 'JOD', 'KWD', 'LYD', 'OMR', 'TND']
|
|
161
173
|
const amt = Number(major)
|
|
162
174
|
if (!isFinite(amt) || amt <= 0) {
|
|
163
175
|
return 0
|
|
164
176
|
}
|
|
165
177
|
const upper = String(currency || '').toUpperCase()
|
|
166
|
-
if (
|
|
178
|
+
if (zeroDecimalCurrencies.indexOf(upper) >= 0) {
|
|
167
179
|
return Math.round(amt)
|
|
168
180
|
}
|
|
169
|
-
if (
|
|
181
|
+
if (threeDecimalCurrencies.indexOf(upper) >= 0) {
|
|
170
182
|
return Math.round(amt * 1000)
|
|
171
183
|
}
|
|
172
184
|
return Math.round(amt * 100)
|
|
@@ -470,12 +482,6 @@ export const paymentChargeUser: NodeHandlerGenerator = {
|
|
|
470
482
|
generateHandler(): string {
|
|
471
483
|
return (
|
|
472
484
|
handlerToString(payment_charge_user) +
|
|
473
|
-
'\nvar STRIPE_ZERO_DECIMAL_CURRENCIES = ' +
|
|
474
|
-
JSON.stringify(STRIPE_ZERO_DECIMAL_CURRENCIES) +
|
|
475
|
-
';' +
|
|
476
|
-
'\nvar STRIPE_THREE_DECIMAL_CURRENCIES = ' +
|
|
477
|
-
JSON.stringify(STRIPE_THREE_DECIMAL_CURRENCIES) +
|
|
478
|
-
';' +
|
|
479
485
|
'\n' +
|
|
480
486
|
resolveProviderSecret.toString() +
|
|
481
487
|
'\n' +
|