@shware/purchase 1.8.5 → 1.8.7
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/dist/stripe/index.cjs +9 -2
- package/dist/stripe/index.cjs.map +1 -1
- package/dist/stripe/index.d.cts +1 -0
- package/dist/stripe/index.d.ts +1 -0
- package/dist/stripe/index.mjs +5 -1
- package/dist/stripe/index.mjs.map +1 -1
- package/dist/stripe/pending-checkout.cjs +74 -0
- package/dist/stripe/pending-checkout.cjs.map +1 -0
- package/dist/stripe/pending-checkout.d.cts +25 -0
- package/dist/stripe/pending-checkout.d.ts +25 -0
- package/dist/stripe/pending-checkout.mjs +47 -0
- package/dist/stripe/pending-checkout.mjs.map +1 -0
- package/package.json +7 -4
package/dist/stripe/index.cjs
CHANGED
|
@@ -23,9 +23,11 @@ __export(stripe_exports, {
|
|
|
23
23
|
METADATA_KEYS: () => import_metadata.METADATA_KEYS,
|
|
24
24
|
StripeConfig: () => import_config.StripeConfig,
|
|
25
25
|
StripeEventHandler: () => import_handler.StripeEventHandler,
|
|
26
|
+
addPendingCheckout: () => import_pending_checkout.addPendingCheckout,
|
|
26
27
|
cancellationDetailsSchema: () => import_schema.cancellationDetailsSchema,
|
|
27
28
|
createCheckoutSessionSchema: () => import_schema.createCheckoutSessionSchema,
|
|
28
29
|
getBeginCheckoutProperties: () => import_mapper.getBeginCheckoutProperties,
|
|
30
|
+
getPendingCheckouts: () => import_pending_checkout.getPendingCheckouts,
|
|
29
31
|
getPurchaseProperties: () => import_mapper.getPurchaseProperties,
|
|
30
32
|
mapCharge: () => import_mapper.mapCharge,
|
|
31
33
|
mapCheckoutSession: () => import_mapper.mapCheckoutSession,
|
|
@@ -36,7 +38,8 @@ __export(stripe_exports, {
|
|
|
36
38
|
mapSubscriptionStatus: () => import_mapper.mapSubscriptionStatus,
|
|
37
39
|
mapTime: () => import_mapper.mapTime,
|
|
38
40
|
minorUnits: () => import_mapper.minorUnits,
|
|
39
|
-
price: () => import_mapper.price
|
|
41
|
+
price: () => import_mapper.price,
|
|
42
|
+
removePendingCheckout: () => import_pending_checkout.removePendingCheckout
|
|
40
43
|
});
|
|
41
44
|
module.exports = __toCommonJS(stripe_exports);
|
|
42
45
|
var import_mapper = require("./mapper.cjs");
|
|
@@ -44,14 +47,17 @@ var import_handler = require("./handler.cjs");
|
|
|
44
47
|
var import_schema = require("./schema.cjs");
|
|
45
48
|
var import_metadata = require("./metadata.cjs");
|
|
46
49
|
var import_config = require("./config.cjs");
|
|
50
|
+
var import_pending_checkout = require("./pending-checkout.cjs");
|
|
47
51
|
// Annotate the CommonJS export names for ESM import in node:
|
|
48
52
|
0 && (module.exports = {
|
|
49
53
|
METADATA_KEYS,
|
|
50
54
|
StripeConfig,
|
|
51
55
|
StripeEventHandler,
|
|
56
|
+
addPendingCheckout,
|
|
52
57
|
cancellationDetailsSchema,
|
|
53
58
|
createCheckoutSessionSchema,
|
|
54
59
|
getBeginCheckoutProperties,
|
|
60
|
+
getPendingCheckouts,
|
|
55
61
|
getPurchaseProperties,
|
|
56
62
|
mapCharge,
|
|
57
63
|
mapCheckoutSession,
|
|
@@ -62,6 +68,7 @@ var import_config = require("./config.cjs");
|
|
|
62
68
|
mapSubscriptionStatus,
|
|
63
69
|
mapTime,
|
|
64
70
|
minorUnits,
|
|
65
|
-
price
|
|
71
|
+
price,
|
|
72
|
+
removePendingCheckout
|
|
66
73
|
});
|
|
67
74
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/stripe/index.ts"],"sourcesContent":["export {\n mapTime,\n mapPrice,\n mapCharge,\n mapInvoice,\n mapLineItem,\n mapPaymentIntent,\n mapCheckoutSession,\n mapSubscriptionStatus,\n minorUnits,\n price,\n getPurchaseProperties,\n getBeginCheckoutProperties,\n type Price,\n type CheckoutSession,\n type PaymentStatus,\n type PurchaseProperties,\n type ProductPrice,\n type BeginCheckoutProperties,\n} from './mapper';\nexport { StripeEventHandler } from './handler';\nexport {\n cancellationDetailsSchema,\n createCheckoutSessionSchema,\n type CancellationDetails,\n type CreateCheckoutSessionDTO,\n} from './schema';\nexport { METADATA_KEYS } from './metadata';\nexport { StripeConfig, type PriceId } from './config';\nexport type { Stripe } from 'stripe';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAmBO;AACP,qBAAmC;AACnC,oBAKO;AACP,sBAA8B;AAC9B,oBAA2C;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/stripe/index.ts"],"sourcesContent":["export {\n mapTime,\n mapPrice,\n mapCharge,\n mapInvoice,\n mapLineItem,\n mapPaymentIntent,\n mapCheckoutSession,\n mapSubscriptionStatus,\n minorUnits,\n price,\n getPurchaseProperties,\n getBeginCheckoutProperties,\n type Price,\n type CheckoutSession,\n type PaymentStatus,\n type PurchaseProperties,\n type ProductPrice,\n type BeginCheckoutProperties,\n} from './mapper';\nexport { StripeEventHandler } from './handler';\nexport {\n cancellationDetailsSchema,\n createCheckoutSessionSchema,\n type CancellationDetails,\n type CreateCheckoutSessionDTO,\n} from './schema';\nexport { METADATA_KEYS } from './metadata';\nexport { StripeConfig, type PriceId } from './config';\nexport { addPendingCheckout, removePendingCheckout, getPendingCheckouts } from './pending-checkout';\n\nexport type { Stripe } from 'stripe';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAmBO;AACP,qBAAmC;AACnC,oBAKO;AACP,sBAA8B;AAC9B,oBAA2C;AAC3C,8BAA+E;","names":[]}
|
package/dist/stripe/index.d.cts
CHANGED
|
@@ -3,6 +3,7 @@ export { StripeEventHandler } from './handler.cjs';
|
|
|
3
3
|
export { CancellationDetails, CreateCheckoutSessionDTO, cancellationDetailsSchema, createCheckoutSessionSchema } from './schema.cjs';
|
|
4
4
|
export { METADATA_KEYS } from './metadata.cjs';
|
|
5
5
|
export { PriceId, StripeConfig } from './config.cjs';
|
|
6
|
+
export { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from './pending-checkout.cjs';
|
|
6
7
|
export { Stripe } from 'stripe';
|
|
7
8
|
import '../subscription/index.cjs';
|
|
8
9
|
import 'zod/v4/core';
|
package/dist/stripe/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { StripeEventHandler } from './handler.js';
|
|
|
3
3
|
export { CancellationDetails, CreateCheckoutSessionDTO, cancellationDetailsSchema, createCheckoutSessionSchema } from './schema.js';
|
|
4
4
|
export { METADATA_KEYS } from './metadata.js';
|
|
5
5
|
export { PriceId, StripeConfig } from './config.js';
|
|
6
|
+
export { addPendingCheckout, getPendingCheckouts, removePendingCheckout } from './pending-checkout.js';
|
|
6
7
|
export { Stripe } from 'stripe';
|
|
7
8
|
import '../subscription/index.js';
|
|
8
9
|
import 'zod/v4/core';
|
package/dist/stripe/index.mjs
CHANGED
|
@@ -20,13 +20,16 @@ import {
|
|
|
20
20
|
} from "./schema.mjs";
|
|
21
21
|
import { METADATA_KEYS } from "./metadata.mjs";
|
|
22
22
|
import { StripeConfig } from "./config.mjs";
|
|
23
|
+
import { addPendingCheckout, removePendingCheckout, getPendingCheckouts } from "./pending-checkout.mjs";
|
|
23
24
|
export {
|
|
24
25
|
METADATA_KEYS,
|
|
25
26
|
StripeConfig,
|
|
26
27
|
StripeEventHandler,
|
|
28
|
+
addPendingCheckout,
|
|
27
29
|
cancellationDetailsSchema,
|
|
28
30
|
createCheckoutSessionSchema,
|
|
29
31
|
getBeginCheckoutProperties,
|
|
32
|
+
getPendingCheckouts,
|
|
30
33
|
getPurchaseProperties,
|
|
31
34
|
mapCharge,
|
|
32
35
|
mapCheckoutSession,
|
|
@@ -37,6 +40,7 @@ export {
|
|
|
37
40
|
mapSubscriptionStatus,
|
|
38
41
|
mapTime,
|
|
39
42
|
minorUnits,
|
|
40
|
-
price
|
|
43
|
+
price,
|
|
44
|
+
removePendingCheckout
|
|
41
45
|
};
|
|
42
46
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/stripe/index.ts"],"sourcesContent":["export {\n mapTime,\n mapPrice,\n mapCharge,\n mapInvoice,\n mapLineItem,\n mapPaymentIntent,\n mapCheckoutSession,\n mapSubscriptionStatus,\n minorUnits,\n price,\n getPurchaseProperties,\n getBeginCheckoutProperties,\n type Price,\n type CheckoutSession,\n type PaymentStatus,\n type PurchaseProperties,\n type ProductPrice,\n type BeginCheckoutProperties,\n} from './mapper';\nexport { StripeEventHandler } from './handler';\nexport {\n cancellationDetailsSchema,\n createCheckoutSessionSchema,\n type CancellationDetails,\n type CreateCheckoutSessionDTO,\n} from './schema';\nexport { METADATA_KEYS } from './metadata';\nexport { StripeConfig, type PriceId } from './config';\nexport type { Stripe } from 'stripe';\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAOK;AACP,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,qBAAqB;AAC9B,SAAS,oBAAkC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/stripe/index.ts"],"sourcesContent":["export {\n mapTime,\n mapPrice,\n mapCharge,\n mapInvoice,\n mapLineItem,\n mapPaymentIntent,\n mapCheckoutSession,\n mapSubscriptionStatus,\n minorUnits,\n price,\n getPurchaseProperties,\n getBeginCheckoutProperties,\n type Price,\n type CheckoutSession,\n type PaymentStatus,\n type PurchaseProperties,\n type ProductPrice,\n type BeginCheckoutProperties,\n} from './mapper';\nexport { StripeEventHandler } from './handler';\nexport {\n cancellationDetailsSchema,\n createCheckoutSessionSchema,\n type CancellationDetails,\n type CreateCheckoutSessionDTO,\n} from './schema';\nexport { METADATA_KEYS } from './metadata';\nexport { StripeConfig, type PriceId } from './config';\nexport { addPendingCheckout, removePendingCheckout, getPendingCheckouts } from './pending-checkout';\n\nexport type { Stripe } from 'stripe';\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAOK;AACP,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,qBAAqB;AAC9B,SAAS,oBAAkC;AAC3C,SAAS,oBAAoB,uBAAuB,2BAA2B;","names":[]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/stripe/pending-checkout.ts
|
|
21
|
+
var pending_checkout_exports = {};
|
|
22
|
+
__export(pending_checkout_exports, {
|
|
23
|
+
addPendingCheckout: () => addPendingCheckout,
|
|
24
|
+
getPendingCheckouts: () => getPendingCheckouts,
|
|
25
|
+
removePendingCheckout: () => removePendingCheckout
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(pending_checkout_exports);
|
|
28
|
+
var KEY = "pending-checkout";
|
|
29
|
+
var TTL = 60 * 60 * 1e3;
|
|
30
|
+
var MAX = 5;
|
|
31
|
+
function isFresh(entry) {
|
|
32
|
+
return Date.now() - entry.ts <= TTL;
|
|
33
|
+
}
|
|
34
|
+
function read() {
|
|
35
|
+
try {
|
|
36
|
+
const raw = localStorage.getItem(KEY);
|
|
37
|
+
if (!raw) return [];
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
if (!Array.isArray(parsed)) return [];
|
|
40
|
+
return parsed.filter(
|
|
41
|
+
(e) => !!e && typeof e.sessionId === "string" && typeof e.ts === "number"
|
|
42
|
+
);
|
|
43
|
+
} catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function write(entries) {
|
|
48
|
+
try {
|
|
49
|
+
if (entries.length === 0) localStorage.removeItem(KEY);
|
|
50
|
+
else localStorage.setItem(KEY, JSON.stringify(entries.slice(-MAX)));
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function addPendingCheckout(sessionId) {
|
|
55
|
+
const entries = read().filter((e) => isFresh(e) && e.sessionId !== sessionId);
|
|
56
|
+
entries.push({ sessionId, ts: Date.now() });
|
|
57
|
+
write(entries);
|
|
58
|
+
}
|
|
59
|
+
function getPendingCheckouts() {
|
|
60
|
+
const all = read();
|
|
61
|
+
const fresh = all.filter(isFresh);
|
|
62
|
+
if (fresh.length !== all.length) write(fresh);
|
|
63
|
+
return fresh.map((e) => e.sessionId);
|
|
64
|
+
}
|
|
65
|
+
function removePendingCheckout(sessionId) {
|
|
66
|
+
write(read().filter((e) => isFresh(e) && e.sessionId !== sessionId));
|
|
67
|
+
}
|
|
68
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
69
|
+
0 && (module.exports = {
|
|
70
|
+
addPendingCheckout,
|
|
71
|
+
getPendingCheckouts,
|
|
72
|
+
removePendingCheckout
|
|
73
|
+
});
|
|
74
|
+
//# sourceMappingURL=pending-checkout.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/stripe/pending-checkout.ts"],"sourcesContent":["/**\n * Tracks checkouts that have been started but may not have been confirmed\n * yet — e.g. the user paid on Stripe and closed the tab before being\n * redirected back to the success page (where `confirmCheckoutSession` runs).\n *\n * IMPORTANT: this stores only the *session ids to retry confirmation on*, a\n * best-effort retry hint — NOT the user's paid/balance state. The server\n * remains the single source of truth for entitlements; on the next app boot\n * we simply re-run the (idempotent, ownership-checked) confirm for each\n * session so the credit balance reflects the purchase without waiting on the\n * async Stripe webhook. Scoped to a single browser; cross-device is covered by\n * the webhook backstop.\n *\n * A *list* (not a single value) is kept on purpose: localStorage is shared\n * across tabs, so two concurrent checkouts — or a second checkout started\n * before the first was reconciled — must not clobber each other.\n */\n\nconst KEY = 'pending-checkout';\n\n/** Drop a hint after this long — an abandoned/unpaid checkout shouldn't retry forever. */\nconst TTL = 60 * 60 * 1000; // 1 hour\n\n/** Cap the list so a string of abandoned checkouts can't grow it unbounded. */\nconst MAX = 5;\n\ntype Entry = { sessionId: string; ts: number };\n\nfunction isFresh(entry: Entry): boolean {\n return Date.now() - entry.ts <= TTL;\n}\n\nfunction read(): Entry[] {\n try {\n const raw = localStorage.getItem(KEY);\n if (!raw) return [];\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(\n (e): e is Entry => !!e && typeof e.sessionId === 'string' && typeof e.ts === 'number'\n );\n } catch {\n return [];\n }\n}\n\nfunction write(entries: Entry[]) {\n try {\n if (entries.length === 0) localStorage.removeItem(KEY);\n else localStorage.setItem(KEY, JSON.stringify(entries.slice(-MAX)));\n } catch {\n // ignore — storage unavailable / quota; the webhook still reconciles.\n }\n}\n\n/** Record a started checkout to retry confirmation on later. Dedupes by session id. */\nexport function addPendingCheckout(sessionId: string) {\n const entries = read().filter((e) => isFresh(e) && e.sessionId !== sessionId);\n entries.push({ sessionId, ts: Date.now() });\n write(entries);\n}\n\n/** Fresh session ids awaiting confirmation. Persists the pruned list as a side effect. */\nexport function getPendingCheckouts(): string[] {\n const all = read();\n const fresh = all.filter(isFresh);\n if (fresh.length !== all.length) write(fresh);\n return fresh.map((e) => e.sessionId);\n}\n\n/** Drop one session once it's confirmed (or known not to belong to this user). */\nexport function removePendingCheckout(sessionId: string) {\n write(read().filter((e) => isFresh(e) && e.sessionId !== sessionId));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBA,IAAM,MAAM;AAGZ,IAAM,MAAM,KAAK,KAAK;AAGtB,IAAM,MAAM;AAIZ,SAAS,QAAQ,OAAuB;AACtC,SAAO,KAAK,IAAI,IAAI,MAAM,MAAM;AAClC;AAEA,SAAS,OAAgB;AACvB,MAAI;AACF,UAAM,MAAM,aAAa,QAAQ,GAAG;AACpC,QAAI,CAAC,IAAK,QAAO,CAAC;AAClB,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO,CAAC;AACpC,WAAO,OAAO;AAAA,MACZ,CAAC,MAAkB,CAAC,CAAC,KAAK,OAAO,EAAE,cAAc,YAAY,OAAO,EAAE,OAAO;AAAA,IAC/E;AAAA,EACF,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,MAAM,SAAkB;AAC/B,MAAI;AACF,QAAI,QAAQ,WAAW,EAAG,cAAa,WAAW,GAAG;AAAA,QAChD,cAAa,QAAQ,KAAK,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,CAAC,CAAC;AAAA,EACpE,QAAQ;AAAA,EAER;AACF;AAGO,SAAS,mBAAmB,WAAmB;AACpD,QAAM,UAAU,KAAK,EAAE,OAAO,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,cAAc,SAAS;AAC5E,UAAQ,KAAK,EAAE,WAAW,IAAI,KAAK,IAAI,EAAE,CAAC;AAC1C,QAAM,OAAO;AACf;AAGO,SAAS,sBAAgC;AAC9C,QAAM,MAAM,KAAK;AACjB,QAAM,QAAQ,IAAI,OAAO,OAAO;AAChC,MAAI,MAAM,WAAW,IAAI,OAAQ,OAAM,KAAK;AAC5C,SAAO,MAAM,IAAI,CAAC,MAAM,EAAE,SAAS;AACrC;AAGO,SAAS,sBAAsB,WAAmB;AACvD,QAAM,KAAK,EAAE,OAAO,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,cAAc,SAAS,CAAC;AACrE;","names":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks checkouts that have been started but may not have been confirmed
|
|
3
|
+
* yet — e.g. the user paid on Stripe and closed the tab before being
|
|
4
|
+
* redirected back to the success page (where `confirmCheckoutSession` runs).
|
|
5
|
+
*
|
|
6
|
+
* IMPORTANT: this stores only the *session ids to retry confirmation on*, a
|
|
7
|
+
* best-effort retry hint — NOT the user's paid/balance state. The server
|
|
8
|
+
* remains the single source of truth for entitlements; on the next app boot
|
|
9
|
+
* we simply re-run the (idempotent, ownership-checked) confirm for each
|
|
10
|
+
* session so the credit balance reflects the purchase without waiting on the
|
|
11
|
+
* async Stripe webhook. Scoped to a single browser; cross-device is covered by
|
|
12
|
+
* the webhook backstop.
|
|
13
|
+
*
|
|
14
|
+
* A *list* (not a single value) is kept on purpose: localStorage is shared
|
|
15
|
+
* across tabs, so two concurrent checkouts — or a second checkout started
|
|
16
|
+
* before the first was reconciled — must not clobber each other.
|
|
17
|
+
*/
|
|
18
|
+
/** Record a started checkout to retry confirmation on later. Dedupes by session id. */
|
|
19
|
+
declare function addPendingCheckout(sessionId: string): void;
|
|
20
|
+
/** Fresh session ids awaiting confirmation. Persists the pruned list as a side effect. */
|
|
21
|
+
declare function getPendingCheckouts(): string[];
|
|
22
|
+
/** Drop one session once it's confirmed (or known not to belong to this user). */
|
|
23
|
+
declare function removePendingCheckout(sessionId: string): void;
|
|
24
|
+
|
|
25
|
+
export { addPendingCheckout, getPendingCheckouts, removePendingCheckout };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks checkouts that have been started but may not have been confirmed
|
|
3
|
+
* yet — e.g. the user paid on Stripe and closed the tab before being
|
|
4
|
+
* redirected back to the success page (where `confirmCheckoutSession` runs).
|
|
5
|
+
*
|
|
6
|
+
* IMPORTANT: this stores only the *session ids to retry confirmation on*, a
|
|
7
|
+
* best-effort retry hint — NOT the user's paid/balance state. The server
|
|
8
|
+
* remains the single source of truth for entitlements; on the next app boot
|
|
9
|
+
* we simply re-run the (idempotent, ownership-checked) confirm for each
|
|
10
|
+
* session so the credit balance reflects the purchase without waiting on the
|
|
11
|
+
* async Stripe webhook. Scoped to a single browser; cross-device is covered by
|
|
12
|
+
* the webhook backstop.
|
|
13
|
+
*
|
|
14
|
+
* A *list* (not a single value) is kept on purpose: localStorage is shared
|
|
15
|
+
* across tabs, so two concurrent checkouts — or a second checkout started
|
|
16
|
+
* before the first was reconciled — must not clobber each other.
|
|
17
|
+
*/
|
|
18
|
+
/** Record a started checkout to retry confirmation on later. Dedupes by session id. */
|
|
19
|
+
declare function addPendingCheckout(sessionId: string): void;
|
|
20
|
+
/** Fresh session ids awaiting confirmation. Persists the pruned list as a side effect. */
|
|
21
|
+
declare function getPendingCheckouts(): string[];
|
|
22
|
+
/** Drop one session once it's confirmed (or known not to belong to this user). */
|
|
23
|
+
declare function removePendingCheckout(sessionId: string): void;
|
|
24
|
+
|
|
25
|
+
export { addPendingCheckout, getPendingCheckouts, removePendingCheckout };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/stripe/pending-checkout.ts
|
|
2
|
+
var KEY = "pending-checkout";
|
|
3
|
+
var TTL = 60 * 60 * 1e3;
|
|
4
|
+
var MAX = 5;
|
|
5
|
+
function isFresh(entry) {
|
|
6
|
+
return Date.now() - entry.ts <= TTL;
|
|
7
|
+
}
|
|
8
|
+
function read() {
|
|
9
|
+
try {
|
|
10
|
+
const raw = localStorage.getItem(KEY);
|
|
11
|
+
if (!raw) return [];
|
|
12
|
+
const parsed = JSON.parse(raw);
|
|
13
|
+
if (!Array.isArray(parsed)) return [];
|
|
14
|
+
return parsed.filter(
|
|
15
|
+
(e) => !!e && typeof e.sessionId === "string" && typeof e.ts === "number"
|
|
16
|
+
);
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function write(entries) {
|
|
22
|
+
try {
|
|
23
|
+
if (entries.length === 0) localStorage.removeItem(KEY);
|
|
24
|
+
else localStorage.setItem(KEY, JSON.stringify(entries.slice(-MAX)));
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function addPendingCheckout(sessionId) {
|
|
29
|
+
const entries = read().filter((e) => isFresh(e) && e.sessionId !== sessionId);
|
|
30
|
+
entries.push({ sessionId, ts: Date.now() });
|
|
31
|
+
write(entries);
|
|
32
|
+
}
|
|
33
|
+
function getPendingCheckouts() {
|
|
34
|
+
const all = read();
|
|
35
|
+
const fresh = all.filter(isFresh);
|
|
36
|
+
if (fresh.length !== all.length) write(fresh);
|
|
37
|
+
return fresh.map((e) => e.sessionId);
|
|
38
|
+
}
|
|
39
|
+
function removePendingCheckout(sessionId) {
|
|
40
|
+
write(read().filter((e) => isFresh(e) && e.sessionId !== sessionId));
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
addPendingCheckout,
|
|
44
|
+
getPendingCheckouts,
|
|
45
|
+
removePendingCheckout
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=pending-checkout.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/stripe/pending-checkout.ts"],"sourcesContent":["/**\n * Tracks checkouts that have been started but may not have been confirmed\n * yet — e.g. the user paid on Stripe and closed the tab before being\n * redirected back to the success page (where `confirmCheckoutSession` runs).\n *\n * IMPORTANT: this stores only the *session ids to retry confirmation on*, a\n * best-effort retry hint — NOT the user's paid/balance state. The server\n * remains the single source of truth for entitlements; on the next app boot\n * we simply re-run the (idempotent, ownership-checked) confirm for each\n * session so the credit balance reflects the purchase without waiting on the\n * async Stripe webhook. Scoped to a single browser; cross-device is covered by\n * the webhook backstop.\n *\n * A *list* (not a single value) is kept on purpose: localStorage is shared\n * across tabs, so two concurrent checkouts — or a second checkout started\n * before the first was reconciled — must not clobber each other.\n */\n\nconst KEY = 'pending-checkout';\n\n/** Drop a hint after this long — an abandoned/unpaid checkout shouldn't retry forever. */\nconst TTL = 60 * 60 * 1000; // 1 hour\n\n/** Cap the list so a string of abandoned checkouts can't grow it unbounded. */\nconst MAX = 5;\n\ntype Entry = { sessionId: string; ts: number };\n\nfunction isFresh(entry: Entry): boolean {\n return Date.now() - entry.ts <= TTL;\n}\n\nfunction read(): Entry[] {\n try {\n const raw = localStorage.getItem(KEY);\n if (!raw) return [];\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(\n (e): e is Entry => !!e && typeof e.sessionId === 'string' && typeof e.ts === 'number'\n );\n } catch {\n return [];\n }\n}\n\nfunction write(entries: Entry[]) {\n try {\n if (entries.length === 0) localStorage.removeItem(KEY);\n else localStorage.setItem(KEY, JSON.stringify(entries.slice(-MAX)));\n } catch {\n // ignore — storage unavailable / quota; the webhook still reconciles.\n }\n}\n\n/** Record a started checkout to retry confirmation on later. Dedupes by session id. */\nexport function addPendingCheckout(sessionId: string) {\n const entries = read().filter((e) => isFresh(e) && e.sessionId !== sessionId);\n entries.push({ sessionId, ts: Date.now() });\n write(entries);\n}\n\n/** Fresh session ids awaiting confirmation. Persists the pruned list as a side effect. */\nexport function getPendingCheckouts(): string[] {\n const all = read();\n const fresh = all.filter(isFresh);\n if (fresh.length !== all.length) write(fresh);\n return fresh.map((e) => e.sessionId);\n}\n\n/** Drop one session once it's confirmed (or known not to belong to this user). */\nexport function removePendingCheckout(sessionId: string) {\n write(read().filter((e) => isFresh(e) && e.sessionId !== sessionId));\n}\n"],"mappings":";AAkBA,IAAM,MAAM;AAGZ,IAAM,MAAM,KAAK,KAAK;AAGtB,IAAM,MAAM;AAIZ,SAAS,QAAQ,OAAuB;AACtC,SAAO,KAAK,IAAI,IAAI,MAAM,MAAM;AAClC;AAEA,SAAS,OAAgB;AACvB,MAAI;AACF,UAAM,MAAM,aAAa,QAAQ,GAAG;AACpC,QAAI,CAAC,IAAK,QAAO,CAAC;AAClB,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO,CAAC;AACpC,WAAO,OAAO;AAAA,MACZ,CAAC,MAAkB,CAAC,CAAC,KAAK,OAAO,EAAE,cAAc,YAAY,OAAO,EAAE,OAAO;AAAA,IAC/E;AAAA,EACF,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,MAAM,SAAkB;AAC/B,MAAI;AACF,QAAI,QAAQ,WAAW,EAAG,cAAa,WAAW,GAAG;AAAA,QAChD,cAAa,QAAQ,KAAK,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,CAAC,CAAC;AAAA,EACpE,QAAQ;AAAA,EAER;AACF;AAGO,SAAS,mBAAmB,WAAmB;AACpD,QAAM,UAAU,KAAK,EAAE,OAAO,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,cAAc,SAAS;AAC5E,UAAQ,KAAK,EAAE,WAAW,IAAI,KAAK,IAAI,EAAE,CAAC;AAC1C,QAAM,OAAO;AACf;AAGO,SAAS,sBAAgC;AAC9C,QAAM,MAAM,KAAK;AACjB,QAAM,QAAQ,IAAI,OAAO,OAAO;AAChC,MAAI,MAAM,WAAW,IAAI,OAAQ,OAAM,KAAK;AAC5C,SAAO,MAAM,IAAI,CAAC,MAAM,EAAE,SAAS;AACrC;AAGO,SAAS,sBAAsB,WAAmB;AACvD,QAAM,KAAK,EAAE,OAAO,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,cAAc,SAAS,CAAC;AACrE;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shware/purchase",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,16 +37,19 @@
|
|
|
37
37
|
"require": "./dist/stripe/index.cjs"
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
40
43
|
"dependencies": {
|
|
41
44
|
"ms": "^2.1.3",
|
|
42
45
|
"zod": "^4.4.3",
|
|
43
|
-
"@shware/utils": "^1.4.
|
|
46
|
+
"@shware/utils": "^1.4.3"
|
|
44
47
|
},
|
|
45
48
|
"devDependencies": {
|
|
46
49
|
"@types/ms": "^2.1.0",
|
|
47
|
-
"@types/node": "^25",
|
|
50
|
+
"@types/node": "^25.9.3",
|
|
48
51
|
"typescript": "^6.0.3",
|
|
49
|
-
"vitest": "^4.1.
|
|
52
|
+
"vitest": "^4.1.9",
|
|
50
53
|
"@repo/typescript-config": "0.0.0"
|
|
51
54
|
},
|
|
52
55
|
"peerDependencies": {
|