@shware/purchase 7.5.0 → 8.0.0
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/README.md +25 -14
- package/dist/app-store/config.cjs +24 -3
- package/dist/app-store/config.cjs.map +1 -1
- package/dist/app-store/config.d.cts +14 -2
- package/dist/app-store/config.d.cts.map +1 -1
- package/dist/app-store/config.d.mts +14 -2
- package/dist/app-store/config.d.mts.map +1 -1
- package/dist/app-store/config.mjs +24 -4
- package/dist/app-store/config.mjs.map +1 -1
- package/dist/app-store/index.cjs +2 -0
- package/dist/app-store/index.d.cts +3 -3
- package/dist/app-store/index.d.mts +3 -3
- package/dist/app-store/index.mjs +3 -3
- package/dist/app-store/mapper.cjs +22 -0
- package/dist/app-store/mapper.cjs.map +1 -1
- package/dist/app-store/mapper.d.cts +17 -1
- package/dist/app-store/mapper.d.cts.map +1 -1
- package/dist/app-store/mapper.d.mts +17 -1
- package/dist/app-store/mapper.d.mts.map +1 -1
- package/dist/app-store/mapper.mjs +22 -1
- package/dist/app-store/mapper.mjs.map +1 -1
- package/dist/catalog.cjs +22 -0
- package/dist/catalog.cjs.map +1 -0
- package/dist/catalog.d.cts +21 -0
- package/dist/catalog.d.cts.map +1 -0
- package/dist/catalog.d.mts +21 -0
- package/dist/catalog.d.mts.map +1 -0
- package/dist/catalog.mjs +19 -0
- package/dist/catalog.mjs.map +1 -0
- package/dist/google-play/config.cjs +23 -3
- package/dist/google-play/config.cjs.map +1 -1
- package/dist/google-play/config.d.cts +14 -2
- package/dist/google-play/config.d.cts.map +1 -1
- package/dist/google-play/config.d.mts +14 -2
- package/dist/google-play/config.d.mts.map +1 -1
- package/dist/google-play/config.mjs +23 -4
- package/dist/google-play/config.mjs.map +1 -1
- package/dist/google-play/index.cjs +2 -0
- package/dist/google-play/index.d.cts +3 -3
- package/dist/google-play/index.d.mts +3 -3
- package/dist/google-play/index.mjs +3 -3
- package/dist/google-play/mapper.cjs +12 -0
- package/dist/google-play/mapper.cjs.map +1 -1
- package/dist/google-play/mapper.d.cts +10 -1
- package/dist/google-play/mapper.d.cts.map +1 -1
- package/dist/google-play/mapper.d.mts +10 -1
- package/dist/google-play/mapper.d.mts.map +1 -1
- package/dist/google-play/mapper.mjs +12 -1
- package/dist/google-play/mapper.mjs.map +1 -1
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +4 -4
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +4 -4
- package/dist/stripe/config.cjs +50 -4
- package/dist/stripe/config.cjs.map +1 -1
- package/dist/stripe/config.d.cts +36 -2
- package/dist/stripe/config.d.cts.map +1 -1
- package/dist/stripe/config.d.mts +36 -2
- package/dist/stripe/config.d.mts.map +1 -1
- package/dist/stripe/config.mjs +50 -5
- package/dist/stripe/config.mjs.map +1 -1
- package/dist/stripe/index.cjs +5 -0
- package/dist/stripe/index.d.cts +3 -2
- package/dist/stripe/index.d.mts +3 -2
- package/dist/stripe/index.mjs +3 -2
- package/dist/stripe/plan-change.cjs +400 -0
- package/dist/stripe/plan-change.cjs.map +1 -0
- package/dist/stripe/plan-change.d.cts +142 -0
- package/dist/stripe/plan-change.d.cts.map +1 -0
- package/dist/stripe/plan-change.d.mts +142 -0
- package/dist/stripe/plan-change.d.mts.map +1 -0
- package/dist/stripe/plan-change.mjs +397 -0
- package/dist/stripe/plan-change.mjs.map +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CancellationDetails } from "../subscription/index.mjs";
|
|
1
|
+
import { CancellationDetails, PaymentType } from "../subscription/index.mjs";
|
|
2
2
|
//#region src/app-store/mapper.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* The App Store Server Notification fields that say why a subscription stops renewing: the
|
|
@@ -20,5 +20,21 @@ export interface AppStoreCancellationSource {
|
|
|
20
20
|
* ref: [expirationIntent](https://developer.apple.com/documentation/appstoreserverapi/expirationintent)
|
|
21
21
|
*/
|
|
22
22
|
export declare function mapCancellationDetails(source: AppStoreCancellationSource): CancellationDetails | null;
|
|
23
|
+
/** The App Store Server Notification fields that say whether a notification is a payment. */
|
|
24
|
+
export interface AppStorePaymentSource {
|
|
25
|
+
notificationType?: string | null;
|
|
26
|
+
subtype?: string | null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The payment a subscription notification's transaction is, if it is one: the first purchase
|
|
30
|
+
* (`SUBSCRIBED`, or a redeemed offer code that starts or resumes the subscription), a renewal
|
|
31
|
+
* (`DID_RENEW`), or an upgrade. An upgrade counts as a renewal: Apple charges the higher plan in
|
|
32
|
+
* full and starts a new period at once, refunding what is left of the old one separately, so it is
|
|
33
|
+
* a regular period charge rather than an off-cycle proration. A downgrade, an auto-renew change
|
|
34
|
+
* and the like carry the current transaction, already paid, and are null.
|
|
35
|
+
*
|
|
36
|
+
* ref: [notificationType](https://developer.apple.com/documentation/appstoreservernotifications/notificationtype)
|
|
37
|
+
*/
|
|
38
|
+
export declare function mapPaymentType({ notificationType, subtype }: AppStorePaymentSource): Exclude<PaymentType, 'ONE_TIME'> | null;
|
|
23
39
|
//#endregion
|
|
24
40
|
//# sourceMappingURL=mapper.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mapper.d.mts","names":[],"sources":["../../src/app-store/mapper.ts"],"mappings":";;;;;;;;iBAQiB;EACf;EACA;;EAEA;;;;;;;;;wBAUc,uBACd,QAAQ,6BACP"}
|
|
1
|
+
{"version":3,"file":"mapper.d.mts","names":[],"sources":["../../src/app-store/mapper.ts"],"mappings":";;;;;;;;iBAQiB;EACf;EACA;;EAEA;;;;;;;;;wBAUc,uBACd,QAAQ,6BACP;;iBAoCc;EACf;EACA;;;;;;;;;;;;wBAac,iBACd,kBACA,WACC,wBAAwB,QAAQ"}
|
|
@@ -30,6 +30,27 @@ function mapCancellationDetails(source) {
|
|
|
30
30
|
default: return null;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The payment a subscription notification's transaction is, if it is one: the first purchase
|
|
35
|
+
* (`SUBSCRIBED`, or a redeemed offer code that starts or resumes the subscription), a renewal
|
|
36
|
+
* (`DID_RENEW`), or an upgrade. An upgrade counts as a renewal: Apple charges the higher plan in
|
|
37
|
+
* full and starts a new period at once, refunding what is left of the old one separately, so it is
|
|
38
|
+
* a regular period charge rather than an off-cycle proration. A downgrade, an auto-renew change
|
|
39
|
+
* and the like carry the current transaction, already paid, and are null.
|
|
40
|
+
*
|
|
41
|
+
* ref: [notificationType](https://developer.apple.com/documentation/appstoreservernotifications/notificationtype)
|
|
42
|
+
*/
|
|
43
|
+
function mapPaymentType({ notificationType, subtype }) {
|
|
44
|
+
switch (notificationType) {
|
|
45
|
+
case "SUBSCRIBED": return "SUBSCRIPTION_START";
|
|
46
|
+
case "DID_RENEW": return "SUBSCRIPTION_RENEWAL";
|
|
47
|
+
case "DID_CHANGE_RENEWAL_PREF": return subtype === "UPGRADE" ? "SUBSCRIPTION_RENEWAL" : null;
|
|
48
|
+
case "OFFER_REDEEMED":
|
|
49
|
+
if (subtype === "INITIAL_BUY" || subtype === "RESUBSCRIBE") return "SUBSCRIPTION_START";
|
|
50
|
+
return subtype === "UPGRADE" ? "SUBSCRIPTION_RENEWAL" : null;
|
|
51
|
+
default: return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
33
54
|
/** The EXPIRED subtype Apple would have sent for an `expirationIntent`. */
|
|
34
55
|
function expirationIntentSubtype(intent) {
|
|
35
56
|
switch (intent) {
|
|
@@ -41,6 +62,6 @@ function expirationIntentSubtype(intent) {
|
|
|
41
62
|
}
|
|
42
63
|
}
|
|
43
64
|
//#endregion
|
|
44
|
-
export { mapCancellationDetails };
|
|
65
|
+
export { mapCancellationDetails, mapPaymentType };
|
|
45
66
|
|
|
46
67
|
//# sourceMappingURL=mapper.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mapper.mjs","names":[],"sources":["../../src/app-store/mapper.ts"],"sourcesContent":["import type { CancellationDetails } from '../subscription/index';\n\n/**\n * The App Store Server Notification fields that say why a subscription stops renewing: the\n * notification type and subtype, plus `expirationIntent` from the renewal info once it has\n * expired. Strings and numbers as Apple sends them, so this needs no runtime import of the\n * App Store Server Library.\n */\nexport interface AppStoreCancellationSource {\n notificationType: string;\n subtype?: string | null;\n /** `JWSRenewalInfoDecodedPayload.expirationIntent`: 1 customer, 2 billing, 3 price increase, 4 unavailable, 5 other. */\n expirationIntent?: number | null;\n}\n\n/**\n * Who ended an App Store subscription and why, from the notification that reported it. Null for\n * anything that is not a cancellation, including the customer turning auto-renew back on.\n *\n * ref: [notificationType](https://developer.apple.com/documentation/appstoreservernotifications/notificationtype)\n * ref: [expirationIntent](https://developer.apple.com/documentation/appstoreserverapi/expirationintent)\n */\nexport function mapCancellationDetails(\n source: AppStoreCancellationSource\n): CancellationDetails | null {\n const { notificationType, subtype, expirationIntent } = source;\n const details = (initiator: CancellationDetails['initiator'], reason: string) => ({\n initiator,\n reason,\n feedback: null,\n comment: null,\n });\n\n switch (notificationType) {\n case 'DID_CHANGE_RENEWAL_STATUS':\n return subtype === 'AUTO_RENEW_DISABLED' ? details('user', subtype) : null;\n case 'EXPIRED':\n switch (subtype ?? expirationIntentSubtype(expirationIntent)) {\n case 'VOLUNTARY':\n return details('user', 'VOLUNTARY');\n case 'BILLING_RETRY':\n return details('system', 'BILLING_RETRY');\n case 'PRICE_INCREASE':\n return details('user', 'PRICE_INCREASE');\n case 'PRODUCT_NOT_FOR_SALE':\n return details('developer', 'PRODUCT_NOT_FOR_SALE');\n default:\n return details('system', 'EXPIRED');\n }\n case 'DID_FAIL_TO_RENEW':\n case 'GRACE_PERIOD_EXPIRED':\n case 'REFUND':\n case 'REVOKE':\n return details('system', notificationType);\n default:\n return null;\n }\n}\n\n/** The EXPIRED subtype Apple would have sent for an `expirationIntent`. */\nfunction expirationIntentSubtype(intent: number | null | undefined) {\n switch (intent) {\n case 1:\n return 'VOLUNTARY';\n case 2:\n return 'BILLING_RETRY';\n case 3:\n return 'PRICE_INCREASE';\n case 4:\n return 'PRODUCT_NOT_FOR_SALE';\n default:\n return undefined;\n }\n}\n"],"mappings":";;;;;;;;AAsBA,SAAgB,uBACd,QAC4B;CAC5B,MAAM,EAAE,kBAAkB,SAAS,qBAAqB;CACxD,MAAM,WAAW,WAA6C,YAAoB;EAChF;EACA;EACA,UAAU;EACV,SAAS;CACX;CAEA,QAAQ,kBAAR;EACE,KAAK,6BACH,OAAO,YAAY,wBAAwB,QAAQ,QAAQ,OAAO,IAAI;EACxE,KAAK,WACH,QAAQ,WAAW,wBAAwB,gBAAgB,GAA3D;GACE,KAAK,aACH,OAAO,QAAQ,QAAQ,WAAW;GACpC,KAAK,iBACH,OAAO,QAAQ,UAAU,eAAe;GAC1C,KAAK,kBACH,OAAO,QAAQ,QAAQ,gBAAgB;GACzC,KAAK,wBACH,OAAO,QAAQ,aAAa,sBAAsB;GACpD,SACE,OAAO,QAAQ,UAAU,SAAS;EACtC;EACF,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,UACH,OAAO,QAAQ,UAAU,gBAAgB;EAC3C,SACE,OAAO;CACX;AACF;;AAGA,SAAS,wBAAwB,QAAmC;CAClE,QAAQ,QAAR;EACE,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,SACE;CACJ;AACF"}
|
|
1
|
+
{"version":3,"file":"mapper.mjs","names":[],"sources":["../../src/app-store/mapper.ts"],"sourcesContent":["import type { CancellationDetails, PaymentType } from '../subscription/index';\n\n/**\n * The App Store Server Notification fields that say why a subscription stops renewing: the\n * notification type and subtype, plus `expirationIntent` from the renewal info once it has\n * expired. Strings and numbers as Apple sends them, so this needs no runtime import of the\n * App Store Server Library.\n */\nexport interface AppStoreCancellationSource {\n notificationType: string;\n subtype?: string | null;\n /** `JWSRenewalInfoDecodedPayload.expirationIntent`: 1 customer, 2 billing, 3 price increase, 4 unavailable, 5 other. */\n expirationIntent?: number | null;\n}\n\n/**\n * Who ended an App Store subscription and why, from the notification that reported it. Null for\n * anything that is not a cancellation, including the customer turning auto-renew back on.\n *\n * ref: [notificationType](https://developer.apple.com/documentation/appstoreservernotifications/notificationtype)\n * ref: [expirationIntent](https://developer.apple.com/documentation/appstoreserverapi/expirationintent)\n */\nexport function mapCancellationDetails(\n source: AppStoreCancellationSource\n): CancellationDetails | null {\n const { notificationType, subtype, expirationIntent } = source;\n const details = (initiator: CancellationDetails['initiator'], reason: string) => ({\n initiator,\n reason,\n feedback: null,\n comment: null,\n });\n\n switch (notificationType) {\n case 'DID_CHANGE_RENEWAL_STATUS':\n return subtype === 'AUTO_RENEW_DISABLED' ? details('user', subtype) : null;\n case 'EXPIRED':\n switch (subtype ?? expirationIntentSubtype(expirationIntent)) {\n case 'VOLUNTARY':\n return details('user', 'VOLUNTARY');\n case 'BILLING_RETRY':\n return details('system', 'BILLING_RETRY');\n case 'PRICE_INCREASE':\n return details('user', 'PRICE_INCREASE');\n case 'PRODUCT_NOT_FOR_SALE':\n return details('developer', 'PRODUCT_NOT_FOR_SALE');\n default:\n return details('system', 'EXPIRED');\n }\n case 'DID_FAIL_TO_RENEW':\n case 'GRACE_PERIOD_EXPIRED':\n case 'REFUND':\n case 'REVOKE':\n return details('system', notificationType);\n default:\n return null;\n }\n}\n\n/** The App Store Server Notification fields that say whether a notification is a payment. */\nexport interface AppStorePaymentSource {\n notificationType?: string | null;\n subtype?: string | null;\n}\n\n/**\n * The payment a subscription notification's transaction is, if it is one: the first purchase\n * (`SUBSCRIBED`, or a redeemed offer code that starts or resumes the subscription), a renewal\n * (`DID_RENEW`), or an upgrade. An upgrade counts as a renewal: Apple charges the higher plan in\n * full and starts a new period at once, refunding what is left of the old one separately, so it is\n * a regular period charge rather than an off-cycle proration. A downgrade, an auto-renew change\n * and the like carry the current transaction, already paid, and are null.\n *\n * ref: [notificationType](https://developer.apple.com/documentation/appstoreservernotifications/notificationtype)\n */\nexport function mapPaymentType({\n notificationType,\n subtype,\n}: AppStorePaymentSource): Exclude<PaymentType, 'ONE_TIME'> | null {\n switch (notificationType) {\n case 'SUBSCRIBED':\n return 'SUBSCRIPTION_START';\n case 'DID_RENEW':\n return 'SUBSCRIPTION_RENEWAL';\n case 'DID_CHANGE_RENEWAL_PREF':\n return subtype === 'UPGRADE' ? 'SUBSCRIPTION_RENEWAL' : null;\n case 'OFFER_REDEEMED':\n if (subtype === 'INITIAL_BUY' || subtype === 'RESUBSCRIBE') return 'SUBSCRIPTION_START';\n return subtype === 'UPGRADE' ? 'SUBSCRIPTION_RENEWAL' : null;\n default:\n return null;\n }\n}\n\n/** The EXPIRED subtype Apple would have sent for an `expirationIntent`. */\nfunction expirationIntentSubtype(intent: number | null | undefined) {\n switch (intent) {\n case 1:\n return 'VOLUNTARY';\n case 2:\n return 'BILLING_RETRY';\n case 3:\n return 'PRICE_INCREASE';\n case 4:\n return 'PRODUCT_NOT_FOR_SALE';\n default:\n return undefined;\n }\n}\n"],"mappings":";;;;;;;;AAsBA,SAAgB,uBACd,QAC4B;CAC5B,MAAM,EAAE,kBAAkB,SAAS,qBAAqB;CACxD,MAAM,WAAW,WAA6C,YAAoB;EAChF;EACA;EACA,UAAU;EACV,SAAS;CACX;CAEA,QAAQ,kBAAR;EACE,KAAK,6BACH,OAAO,YAAY,wBAAwB,QAAQ,QAAQ,OAAO,IAAI;EACxE,KAAK,WACH,QAAQ,WAAW,wBAAwB,gBAAgB,GAA3D;GACE,KAAK,aACH,OAAO,QAAQ,QAAQ,WAAW;GACpC,KAAK,iBACH,OAAO,QAAQ,UAAU,eAAe;GAC1C,KAAK,kBACH,OAAO,QAAQ,QAAQ,gBAAgB;GACzC,KAAK,wBACH,OAAO,QAAQ,aAAa,sBAAsB;GACpD,SACE,OAAO,QAAQ,UAAU,SAAS;EACtC;EACF,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,UACH,OAAO,QAAQ,UAAU,gBAAgB;EAC3C,SACE,OAAO;CACX;AACF;;;;;;;;;;;AAkBA,SAAgB,eAAe,EAC7B,kBACA,WACiE;CACjE,QAAQ,kBAAR;EACE,KAAK,cACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,KAAK,2BACH,OAAO,YAAY,YAAY,yBAAyB;EAC1D,KAAK;GACH,IAAI,YAAY,iBAAiB,YAAY,eAAe,OAAO;GACnE,OAAO,YAAY,YAAY,yBAAyB;EAC1D,SACE,OAAO;CACX;AACF;;AAGA,SAAS,wBAAwB,QAAmC;CAClE,QAAQ,QAAR;EACE,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,SACE;CACJ;AACF"}
|
package/dist/catalog.cjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _shware_utils = require("@shware/utils");
|
|
3
|
+
//#region src/catalog.ts
|
|
4
|
+
function assertCatalog({ plans, billingPeriods }) {
|
|
5
|
+
(0, _shware_utils.invariant)(new Set(plans).size === plans.length, "Duplicate plan in plans");
|
|
6
|
+
(0, _shware_utils.invariant)(new Set(billingPeriods).size === billingPeriods.length, "Duplicate billing period in billingPeriods");
|
|
7
|
+
}
|
|
8
|
+
/** Runtime mirror of the template-literal type, for ids built at runtime or cast. */
|
|
9
|
+
function assertInNamespace(namespace, productId) {
|
|
10
|
+
(0, _shware_utils.invariant)(productId.startsWith(`${namespace}.`), `Product ${productId} is not in namespace ${namespace}`);
|
|
11
|
+
}
|
|
12
|
+
/** Runtime mirror of the plan and period unions. */
|
|
13
|
+
function assertDeclared({ plans, billingPeriods }, plan, billingPeriod) {
|
|
14
|
+
(0, _shware_utils.invariant)(plan === null || plans.includes(plan), `Plan ${plan} is not in plans`);
|
|
15
|
+
(0, _shware_utils.invariant)(billingPeriod === null || billingPeriods.includes(billingPeriod), `Billing period ${billingPeriod} is not in billingPeriods`);
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
exports.assertCatalog = assertCatalog;
|
|
19
|
+
exports.assertDeclared = assertDeclared;
|
|
20
|
+
exports.assertInNamespace = assertInNamespace;
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=catalog.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.cjs","names":[],"sources":["../src/catalog.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\n\n/**\n * What every store config is declared over, and what its type parameters are inferred from: the\n * prefix of every product id, the plans lowest tier first, the billing periods shortest first. A\n * config with no subscriptions passes empty arrays.\n */\nexport interface Catalog<NS extends Lowercase<string>, PE extends string, BP extends string> {\n /** The prefix every product id starts with (`io.example` for `io.example.pro.monthly`). */\n namespace: NS;\n /** Every plan, lowest tier first: its level, as Apple orders a subscription group. */\n plans: readonly PE[];\n /** Every billing period, shortest first. */\n billingPeriods: readonly BP[];\n}\n\nexport function assertCatalog({\n plans,\n billingPeriods,\n}: Catalog<Lowercase<string>, string, string>) {\n invariant(new Set(plans).size === plans.length, 'Duplicate plan in plans');\n invariant(\n new Set(billingPeriods).size === billingPeriods.length,\n 'Duplicate billing period in billingPeriods'\n );\n}\n\n/** Runtime mirror of the template-literal type, for ids built at runtime or cast. */\nexport function assertInNamespace(namespace: string, productId: string) {\n invariant(\n productId.startsWith(`${namespace}.`),\n `Product ${productId} is not in namespace ${namespace}`\n );\n}\n\n/** Runtime mirror of the plan and period unions. */\nexport function assertDeclared(\n { plans, billingPeriods }: Catalog<Lowercase<string>, string, string>,\n plan: string | null,\n billingPeriod: string | null\n) {\n invariant(plan === null || plans.includes(plan), `Plan ${plan} is not in plans`);\n invariant(\n billingPeriod === null || billingPeriods.includes(billingPeriod),\n `Billing period ${billingPeriod} is not in billingPeriods`\n );\n}\n"],"mappings":";;;AAgBA,SAAgB,cAAc,EAC5B,OACA,kBAC6C;CAC7C,CAAA,GAAA,cAAA,UAAA,CAAU,IAAI,IAAI,KAAK,CAAC,CAAC,SAAS,MAAM,QAAQ,yBAAyB;CACzE,CAAA,GAAA,cAAA,UAAA,CACE,IAAI,IAAI,cAAc,CAAC,CAAC,SAAS,eAAe,QAChD,4CACF;AACF;;AAGA,SAAgB,kBAAkB,WAAmB,WAAmB;CACtE,CAAA,GAAA,cAAA,UAAA,CACE,UAAU,WAAW,GAAG,UAAU,EAAE,GACpC,WAAW,UAAU,uBAAuB,WAC9C;AACF;;AAGA,SAAgB,eACd,EAAE,OAAO,kBACT,MACA,eACA;CACA,CAAA,GAAA,cAAA,UAAA,CAAU,SAAS,QAAQ,MAAM,SAAS,IAAI,GAAG,QAAQ,KAAK,iBAAiB;CAC/E,CAAA,GAAA,cAAA,UAAA,CACE,kBAAkB,QAAQ,eAAe,SAAS,aAAa,GAC/D,kBAAkB,cAAc,0BAClC;AACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/catalog.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* What every store config is declared over, and what its type parameters are inferred from: the
|
|
4
|
+
* prefix of every product id, the plans lowest tier first, the billing periods shortest first. A
|
|
5
|
+
* config with no subscriptions passes empty arrays.
|
|
6
|
+
*/
|
|
7
|
+
export interface Catalog<NS extends Lowercase<string>, PE extends string, BP extends string> {
|
|
8
|
+
/** The prefix every product id starts with (`io.example` for `io.example.pro.monthly`). */
|
|
9
|
+
namespace: NS;
|
|
10
|
+
/** Every plan, lowest tier first: its level, as Apple orders a subscription group. */
|
|
11
|
+
plans: readonly PE[];
|
|
12
|
+
/** Every billing period, shortest first. */
|
|
13
|
+
billingPeriods: readonly BP[];
|
|
14
|
+
}
|
|
15
|
+
export declare function assertCatalog({ plans, billingPeriods }: Catalog<Lowercase<string>, string, string>): void;
|
|
16
|
+
/** Runtime mirror of the template-literal type, for ids built at runtime or cast. */
|
|
17
|
+
export declare function assertInNamespace(namespace: string, productId: string): void;
|
|
18
|
+
/** Runtime mirror of the plan and period unions. */
|
|
19
|
+
export declare function assertDeclared({ plans, billingPeriods }: Catalog<Lowercase<string>, string, string>, plan: string | null, billingPeriod: string | null): void;
|
|
20
|
+
//#endregion
|
|
21
|
+
//# sourceMappingURL=catalog.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.cts","names":[],"sources":["../src/catalog.ts"],"mappings":";;;;;;iBAOiB,QAAQ,WAAW,mBAAmB,mBAAmB;;EAExE,WAAW;;EAEX,gBAAgB;;EAEhB,yBAAyB;;wBAGX,gBACd,OACA,kBACC,QAAQ;;wBASK,kBAAkB,mBAAmB;;wBAQrC,iBACZ,OAAO,kBAAkB,QAAQ,oCACnC,qBACA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/catalog.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* What every store config is declared over, and what its type parameters are inferred from: the
|
|
4
|
+
* prefix of every product id, the plans lowest tier first, the billing periods shortest first. A
|
|
5
|
+
* config with no subscriptions passes empty arrays.
|
|
6
|
+
*/
|
|
7
|
+
export interface Catalog<NS extends Lowercase<string>, PE extends string, BP extends string> {
|
|
8
|
+
/** The prefix every product id starts with (`io.example` for `io.example.pro.monthly`). */
|
|
9
|
+
namespace: NS;
|
|
10
|
+
/** Every plan, lowest tier first: its level, as Apple orders a subscription group. */
|
|
11
|
+
plans: readonly PE[];
|
|
12
|
+
/** Every billing period, shortest first. */
|
|
13
|
+
billingPeriods: readonly BP[];
|
|
14
|
+
}
|
|
15
|
+
export declare function assertCatalog({ plans, billingPeriods }: Catalog<Lowercase<string>, string, string>): void;
|
|
16
|
+
/** Runtime mirror of the template-literal type, for ids built at runtime or cast. */
|
|
17
|
+
export declare function assertInNamespace(namespace: string, productId: string): void;
|
|
18
|
+
/** Runtime mirror of the plan and period unions. */
|
|
19
|
+
export declare function assertDeclared({ plans, billingPeriods }: Catalog<Lowercase<string>, string, string>, plan: string | null, billingPeriod: string | null): void;
|
|
20
|
+
//#endregion
|
|
21
|
+
//# sourceMappingURL=catalog.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.mts","names":[],"sources":["../src/catalog.ts"],"mappings":";;;;;;iBAOiB,QAAQ,WAAW,mBAAmB,mBAAmB;;EAExE,WAAW;;EAEX,gBAAgB;;EAEhB,yBAAyB;;wBAGX,gBACd,OACA,kBACC,QAAQ;;wBASK,kBAAkB,mBAAmB;;wBAQrC,iBACZ,OAAO,kBAAkB,QAAQ,oCACnC,qBACA"}
|
package/dist/catalog.mjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { invariant } from "@shware/utils";
|
|
2
|
+
//#region src/catalog.ts
|
|
3
|
+
function assertCatalog({ plans, billingPeriods }) {
|
|
4
|
+
invariant(new Set(plans).size === plans.length, "Duplicate plan in plans");
|
|
5
|
+
invariant(new Set(billingPeriods).size === billingPeriods.length, "Duplicate billing period in billingPeriods");
|
|
6
|
+
}
|
|
7
|
+
/** Runtime mirror of the template-literal type, for ids built at runtime or cast. */
|
|
8
|
+
function assertInNamespace(namespace, productId) {
|
|
9
|
+
invariant(productId.startsWith(`${namespace}.`), `Product ${productId} is not in namespace ${namespace}`);
|
|
10
|
+
}
|
|
11
|
+
/** Runtime mirror of the plan and period unions. */
|
|
12
|
+
function assertDeclared({ plans, billingPeriods }, plan, billingPeriod) {
|
|
13
|
+
invariant(plan === null || plans.includes(plan), `Plan ${plan} is not in plans`);
|
|
14
|
+
invariant(billingPeriod === null || billingPeriods.includes(billingPeriod), `Billing period ${billingPeriod} is not in billingPeriods`);
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { assertCatalog, assertDeclared, assertInNamespace };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=catalog.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.mjs","names":[],"sources":["../src/catalog.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\n\n/**\n * What every store config is declared over, and what its type parameters are inferred from: the\n * prefix of every product id, the plans lowest tier first, the billing periods shortest first. A\n * config with no subscriptions passes empty arrays.\n */\nexport interface Catalog<NS extends Lowercase<string>, PE extends string, BP extends string> {\n /** The prefix every product id starts with (`io.example` for `io.example.pro.monthly`). */\n namespace: NS;\n /** Every plan, lowest tier first: its level, as Apple orders a subscription group. */\n plans: readonly PE[];\n /** Every billing period, shortest first. */\n billingPeriods: readonly BP[];\n}\n\nexport function assertCatalog({\n plans,\n billingPeriods,\n}: Catalog<Lowercase<string>, string, string>) {\n invariant(new Set(plans).size === plans.length, 'Duplicate plan in plans');\n invariant(\n new Set(billingPeriods).size === billingPeriods.length,\n 'Duplicate billing period in billingPeriods'\n );\n}\n\n/** Runtime mirror of the template-literal type, for ids built at runtime or cast. */\nexport function assertInNamespace(namespace: string, productId: string) {\n invariant(\n productId.startsWith(`${namespace}.`),\n `Product ${productId} is not in namespace ${namespace}`\n );\n}\n\n/** Runtime mirror of the plan and period unions. */\nexport function assertDeclared(\n { plans, billingPeriods }: Catalog<Lowercase<string>, string, string>,\n plan: string | null,\n billingPeriod: string | null\n) {\n invariant(plan === null || plans.includes(plan), `Plan ${plan} is not in plans`);\n invariant(\n billingPeriod === null || billingPeriods.includes(billingPeriod),\n `Billing period ${billingPeriod} is not in billingPeriods`\n );\n}\n"],"mappings":";;AAgBA,SAAgB,cAAc,EAC5B,OACA,kBAC6C;CAC7C,UAAU,IAAI,IAAI,KAAK,CAAC,CAAC,SAAS,MAAM,QAAQ,yBAAyB;CACzE,UACE,IAAI,IAAI,cAAc,CAAC,CAAC,SAAS,eAAe,QAChD,4CACF;AACF;;AAGA,SAAgB,kBAAkB,WAAmB,WAAmB;CACtE,UACE,UAAU,WAAW,GAAG,UAAU,EAAE,GACpC,WAAW,UAAU,uBAAuB,WAC9C;AACF;;AAGA,SAAgB,eACd,EAAE,OAAO,kBACT,MACA,eACA;CACA,UAAU,SAAS,QAAQ,MAAM,SAAS,IAAI,GAAG,QAAQ,KAAK,iBAAiB;CAC/E,UACE,kBAAkB,QAAQ,eAAe,SAAS,aAAa,GAC/D,kBAAkB,cAAc,0BAClC;AACF"}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
3
|
+
const require_catalog = require("../catalog.cjs");
|
|
3
4
|
let _shware_utils = require("@shware/utils");
|
|
4
5
|
let ms = require("ms");
|
|
5
6
|
ms = require_runtime.__toESM(ms, 1);
|
|
6
7
|
//#region src/google-play/config.ts
|
|
7
8
|
const addMethod = Symbol("addMethod");
|
|
9
|
+
/** The private constructor's one door, for this module's `define` function only. */
|
|
10
|
+
const construct = Symbol("construct");
|
|
8
11
|
var Subscription = class {
|
|
9
12
|
productId;
|
|
10
13
|
config;
|
|
@@ -25,6 +28,7 @@ var Subscription = class {
|
|
|
25
28
|
credits: 0,
|
|
26
29
|
expiresIn: "0"
|
|
27
30
|
}) => {
|
|
31
|
+
require_catalog.assertDeclared(this.config, plan, billingPeriod);
|
|
28
32
|
(0, _shware_utils.invariant)(!this.plans.has(planId), `Duplicate base plan ${planId}`);
|
|
29
33
|
this.plans.set(planId, {
|
|
30
34
|
...metadata,
|
|
@@ -45,6 +49,9 @@ var Subscription = class {
|
|
|
45
49
|
var GooglePlayConfig = class GooglePlayConfig {
|
|
46
50
|
package;
|
|
47
51
|
audience;
|
|
52
|
+
namespace;
|
|
53
|
+
plans;
|
|
54
|
+
billingPeriods;
|
|
48
55
|
onetimes;
|
|
49
56
|
subscriptions;
|
|
50
57
|
products;
|
|
@@ -62,22 +69,26 @@ var GooglePlayConfig = class GooglePlayConfig {
|
|
|
62
69
|
return planId ? `${productId}:${planId}` : productId;
|
|
63
70
|
};
|
|
64
71
|
constructor(options) {
|
|
72
|
+
require_catalog.assertCatalog(options);
|
|
73
|
+
this.namespace = options.namespace;
|
|
74
|
+
this.plans = options.plans;
|
|
75
|
+
this.billingPeriods = options.billingPeriods;
|
|
65
76
|
this.package = options.package;
|
|
66
77
|
this.audience = options.audience;
|
|
67
78
|
this.products = /* @__PURE__ */ new Map();
|
|
68
79
|
this.onetimes = /* @__PURE__ */ new Set();
|
|
69
80
|
this.subscriptions = /* @__PURE__ */ new Map();
|
|
70
81
|
}
|
|
71
|
-
static
|
|
72
|
-
return new GooglePlayConfig(options);
|
|
73
|
-
};
|
|
82
|
+
static [construct] = (options) => new GooglePlayConfig(options);
|
|
74
83
|
subscription = (productId) => {
|
|
84
|
+
require_catalog.assertInNamespace(this.namespace, productId);
|
|
75
85
|
return new Subscription(this, productId);
|
|
76
86
|
};
|
|
77
87
|
onetime = (productId, metadata = {
|
|
78
88
|
credits: 0,
|
|
79
89
|
expiresIn: "0"
|
|
80
90
|
}) => {
|
|
91
|
+
require_catalog.assertInNamespace(this.namespace, productId);
|
|
81
92
|
(0, _shware_utils.invariant)(!this.products.has(productId), `Duplicate product ${productId}`);
|
|
82
93
|
this.onetimes.add(productId);
|
|
83
94
|
this.products.set(productId, metadata);
|
|
@@ -125,7 +136,16 @@ var GooglePlayConfig = class GooglePlayConfig {
|
|
|
125
136
|
return new Date(Date.now() + expiresIn).toISOString();
|
|
126
137
|
};
|
|
127
138
|
};
|
|
139
|
+
/**
|
|
140
|
+
* Declare the GooglePlay product catalog. Every type parameter is inferred from the options:
|
|
141
|
+
* the namespace from `namespace`, the plans and billing periods from `plans` and `billingPeriods`
|
|
142
|
+
* (`const`, so a literal array needs no `as const`). Products are then added with the fluent chain.
|
|
143
|
+
*/
|
|
144
|
+
function defineGooglePlayConfig(options) {
|
|
145
|
+
return GooglePlayConfig[construct](options);
|
|
146
|
+
}
|
|
128
147
|
//#endregion
|
|
129
148
|
exports.GooglePlayConfig = GooglePlayConfig;
|
|
149
|
+
exports.defineGooglePlayConfig = defineGooglePlayConfig;
|
|
130
150
|
|
|
131
151
|
//# sourceMappingURL=config.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.cjs","names":[],"sources":["../../src/google-play/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { IsUnique, Metadata } from '../types';\n\nconst addMethod = Symbol('addMethod');\n\nclass Subscription<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PI extends string = never,\n I extends Lowercase<string> = never,\n> {\n readonly productId: string;\n readonly config: GooglePlayConfig<NS, PE, BP, PI>;\n readonly plans: Map<string, Metadata & { plan?: PE; billingPeriod?: BP }>;\n readonly defaultPlans: Set<string>;\n\n constructor(config: GooglePlayConfig<NS, PE, BP, PI>, productId: string) {\n this.config = config;\n this.productId = productId;\n this.plans = new Map();\n this.defaultPlans = new Set();\n }\n\n /**\n * Non-default base plans stay declared on purpose: they keep resolving for\n * grandfathered subscribers and serve as price-experiment arms. Never sell\n * a legacy base plan to new users unless that is intentional.\n */\n basePlan = <K extends Lowercase<string>>(\n planId: K extends I ? never : K,\n plan: PE,\n billingPeriod: BP,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Subscription<NS, PE, BP, PI, I | K> => {\n invariant(!this.plans.has(planId), `Duplicate base plan ${planId}`);\n this.plans.set(planId, { ...metadata, plan, billingPeriod });\n return this;\n };\n\n default = <const T extends readonly [I, ...I[]]>(\n planIds: T & (IsUnique<T> extends true ? unknown : never)\n ) => {\n planIds.forEach((planId) => {\n invariant(this.plans.has(planId), `Default base plan ${planId} is not declared`);\n this.defaultPlans.add(planId);\n });\n this.config[addMethod](this);\n return this.config;\n };\n}\n\ntype Options = { package: string; audience: string };\n\nexport class GooglePlayConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PI extends string = never,\n> {\n readonly package: string;\n readonly audience: string;\n private readonly onetimes: Set<string>;\n private readonly subscriptions: Map<string, Subscription<NS, PE, BP, PI>>;\n private readonly products: Map<string, (Metadata & { plan?: PE; billingPeriod?: BP }) | null>;\n\n // runtime mirror of the type-level guards: generic accumulation only\n // protects a single fluent chain, not statement-style re-entry or casts\n [addMethod] = (subscription: Subscription<NS, PE, BP, PI>) => {\n invariant(\n !this.subscriptions.has(subscription.productId),\n `Duplicate subscription ${subscription.productId}`\n );\n subscription.plans.forEach((metadata, planId) => {\n const id = this.getId(subscription.productId, planId);\n invariant(!this.products.has(id), `Duplicate product ${id}`);\n this.products.set(id, metadata);\n });\n this.subscriptions.set(subscription.productId, subscription);\n return this;\n };\n\n private readonly getId = (productId: string, planId?: string) => {\n return planId ? `${productId}:${planId}` : productId;\n };\n\n private constructor(options: Options) {\n this.package = options.package;\n this.audience = options.audience;\n this.products = new Map();\n this.onetimes = new Set();\n this.subscriptions = new Map();\n }\n\n static create = <\n NS extends Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n >(\n options: Options\n ) => {\n return new GooglePlayConfig<NS, PE, BP>(options);\n };\n\n subscription = <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => {\n return new Subscription<NS, PE, BP, PI | K>(this, productId);\n };\n\n onetime = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ) => {\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.onetimes.add(productId);\n this.products.set(productId, metadata);\n return this as GooglePlayConfig<NS, PE, BP, PI | K>;\n };\n\n /** Base plan ids that new subscribers should purchase for the product. */\n getDefaultPlanIds = (productId: string): string[] => {\n const subscription = this.subscriptions.get(productId);\n invariant(subscription, `Subscription not found for ${productId}`);\n return Array.from(subscription.defaultPlans);\n };\n\n getPlan = (productId: string, planId: string): PE => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.plan, `Plan not found for ${id}`);\n return metadata.plan;\n };\n\n getBillingPeriod = (productId: string, planId: string): BP => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.billingPeriod, `Billing period not found for ${id}`);\n return metadata.billingPeriod;\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n if (this.onetimes.has(productId)) return 'payment';\n if (this.subscriptions.has(productId)) return 'subscription';\n throw new Error(`Mode not found for product ${productId}`);\n };\n\n getCreditAmount = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return metadata.credits;\n };\n\n private readonly getCreditExpiresIn = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return ms(metadata.expiresIn);\n };\n\n getCreditExpiresAt = (productId: string, planId?: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId, planId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.cjs","names":[],"sources":["../../src/google-play/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport { type Catalog, assertCatalog, assertDeclared, assertInNamespace } from '../catalog';\nimport type { IsUnique, Metadata } from '../types';\n\nconst addMethod = Symbol('addMethod');\n/** The private constructor's one door, for this module's `define` function only. */\nconst construct = Symbol('construct');\n\nclass Subscription<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PI extends string = never,\n I extends Lowercase<string> = never,\n> {\n readonly productId: string;\n readonly config: GooglePlayConfig<NS, PE, BP, PI>;\n readonly plans: Map<string, Metadata & { plan?: PE; billingPeriod?: BP }>;\n readonly defaultPlans: Set<string>;\n\n constructor(config: GooglePlayConfig<NS, PE, BP, PI>, productId: string) {\n this.config = config;\n this.productId = productId;\n this.plans = new Map();\n this.defaultPlans = new Set();\n }\n\n /**\n * Non-default base plans stay declared on purpose: they keep resolving for\n * grandfathered subscribers and serve as price-experiment arms. Never sell\n * a legacy base plan to new users unless that is intentional.\n */\n basePlan = <K extends Lowercase<string>>(\n planId: K extends I ? never : K,\n plan: PE,\n billingPeriod: BP,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Subscription<NS, PE, BP, PI, I | K> => {\n assertDeclared(this.config, plan, billingPeriod);\n invariant(!this.plans.has(planId), `Duplicate base plan ${planId}`);\n this.plans.set(planId, { ...metadata, plan, billingPeriod });\n return this;\n };\n\n default = <const T extends readonly [I, ...I[]]>(\n planIds: T & (IsUnique<T> extends true ? unknown : never)\n ) => {\n planIds.forEach((planId) => {\n invariant(this.plans.has(planId), `Default base plan ${planId} is not declared`);\n this.defaultPlans.add(planId);\n });\n this.config[addMethod](this);\n return this.config;\n };\n}\n\ntype Options<NS extends Lowercase<string>, PE extends string, BP extends string> = Catalog<\n NS,\n PE,\n BP\n> & { package: string; audience: string };\n\nexport class GooglePlayConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PI extends string = never,\n> {\n readonly package: string;\n readonly audience: string;\n readonly namespace: NS;\n readonly plans: readonly PE[];\n readonly billingPeriods: readonly BP[];\n private readonly onetimes: Set<string>;\n private readonly subscriptions: Map<string, Subscription<NS, PE, BP, PI>>;\n private readonly products: Map<string, (Metadata & { plan?: PE; billingPeriod?: BP }) | null>;\n\n // runtime mirror of the type-level guards: generic accumulation only\n // protects a single fluent chain, not statement-style re-entry or casts\n [addMethod] = (subscription: Subscription<NS, PE, BP, PI>) => {\n invariant(\n !this.subscriptions.has(subscription.productId),\n `Duplicate subscription ${subscription.productId}`\n );\n subscription.plans.forEach((metadata, planId) => {\n const id = this.getId(subscription.productId, planId);\n invariant(!this.products.has(id), `Duplicate product ${id}`);\n this.products.set(id, metadata);\n });\n this.subscriptions.set(subscription.productId, subscription);\n return this;\n };\n\n private readonly getId = (productId: string, planId?: string) => {\n return planId ? `${productId}:${planId}` : productId;\n };\n\n private constructor(options: Options<NS, PE, BP>) {\n assertCatalog(options);\n this.namespace = options.namespace;\n this.plans = options.plans;\n this.billingPeriods = options.billingPeriods;\n this.package = options.package;\n this.audience = options.audience;\n this.products = new Map();\n this.onetimes = new Set();\n this.subscriptions = new Map();\n }\n\n static [construct] = <NS extends Lowercase<string>, PE extends string, BP extends string>(\n options: Options<NS, PE, BP>\n ) => new GooglePlayConfig<NS, PE, BP>(options);\n\n subscription = <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => {\n assertInNamespace(this.namespace, productId);\n return new Subscription<NS, PE, BP, PI | K>(this, productId);\n };\n\n onetime = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ) => {\n assertInNamespace(this.namespace, productId);\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.onetimes.add(productId);\n this.products.set(productId, metadata);\n return this as GooglePlayConfig<NS, PE, BP, PI | K>;\n };\n\n /** Base plan ids that new subscribers should purchase for the product. */\n getDefaultPlanIds = (productId: string): string[] => {\n const subscription = this.subscriptions.get(productId);\n invariant(subscription, `Subscription not found for ${productId}`);\n return Array.from(subscription.defaultPlans);\n };\n\n getPlan = (productId: string, planId: string): PE => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.plan, `Plan not found for ${id}`);\n return metadata.plan;\n };\n\n getBillingPeriod = (productId: string, planId: string): BP => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.billingPeriod, `Billing period not found for ${id}`);\n return metadata.billingPeriod;\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n if (this.onetimes.has(productId)) return 'payment';\n if (this.subscriptions.has(productId)) return 'subscription';\n throw new Error(`Mode not found for product ${productId}`);\n };\n\n getCreditAmount = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return metadata.credits;\n };\n\n private readonly getCreditExpiresIn = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return ms(metadata.expiresIn);\n };\n\n getCreditExpiresAt = (productId: string, planId?: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId, planId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n\n/**\n * Declare the GooglePlay product catalog. Every type parameter is inferred from the options:\n * the namespace from `namespace`, the plans and billing periods from `plans` and `billingPeriods`\n * (`const`, so a literal array needs no `as const`). Products are then added with the fluent chain.\n */\nexport function defineGooglePlayConfig<\n const NS extends Lowercase<string>,\n const PE extends string,\n const BP extends string,\n>(options: Options<NS, PE, BP>): GooglePlayConfig<NS, PE, BP> {\n return GooglePlayConfig[construct](options);\n}\n"],"mappings":";;;;;;;AAKA,MAAM,YAAY,OAAO,WAAW;;AAEpC,MAAM,YAAY,OAAO,WAAW;AAEpC,IAAM,eAAN,MAME;CACA;CACA;CACA;CACA;CAEA,YAAY,QAA0C,WAAmB;EACvE,KAAK,SAAS;EACd,KAAK,YAAY;EACjB,KAAK,wBAAQ,IAAI,IAAI;EACrB,KAAK,+BAAe,IAAI,IAAI;CAC9B;;;;;;CAOA,YACE,QACA,MACA,eACA,WAAqB;EAAE,SAAS;EAAG,WAAW;CAAI,MACV;EACxC,gBAAA,eAAe,KAAK,QAAQ,MAAM,aAAa;EAC/C,CAAA,GAAA,cAAA,UAAA,CAAU,CAAC,KAAK,MAAM,IAAI,MAAM,GAAG,uBAAuB,QAAQ;EAClE,KAAK,MAAM,IAAI,QAAQ;GAAE,GAAG;GAAU;GAAM;EAAc,CAAC;EAC3D,OAAO;CACT;CAEA,WACE,YACG;EACH,QAAQ,SAAS,WAAW;GAC1B,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,MAAM,IAAI,MAAM,GAAG,qBAAqB,OAAO,iBAAiB;GAC/E,KAAK,aAAa,IAAI,MAAM;EAC9B,CAAC;EACD,KAAK,OAAO,UAAU,CAAC,IAAI;EAC3B,OAAO,KAAK;CACd;AACF;AAQA,IAAa,mBAAb,MAAa,iBAKX;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAIA,CAAC,cAAc,iBAA+C;EAC5D,CAAA,GAAA,cAAA,UAAA,CACE,CAAC,KAAK,cAAc,IAAI,aAAa,SAAS,GAC9C,0BAA0B,aAAa,WACzC;EACA,aAAa,MAAM,SAAS,UAAU,WAAW;GAC/C,MAAM,KAAK,KAAK,MAAM,aAAa,WAAW,MAAM;GACpD,CAAA,GAAA,cAAA,UAAA,CAAU,CAAC,KAAK,SAAS,IAAI,EAAE,GAAG,qBAAqB,IAAI;GAC3D,KAAK,SAAS,IAAI,IAAI,QAAQ;EAChC,CAAC;EACD,KAAK,cAAc,IAAI,aAAa,WAAW,YAAY;EAC3D,OAAO;CACT;CAEA,SAA0B,WAAmB,WAAoB;EAC/D,OAAO,SAAS,GAAG,UAAU,GAAG,WAAW;CAC7C;CAEA,YAAoB,SAA8B;EAChD,gBAAA,cAAc,OAAO;EACrB,KAAK,YAAY,QAAQ;EACzB,KAAK,QAAQ,QAAQ;EACrB,KAAK,iBAAiB,QAAQ;EAC9B,KAAK,UAAU,QAAQ;EACvB,KAAK,WAAW,QAAQ;EACxB,KAAK,2BAAW,IAAI,IAAI;EACxB,KAAK,2BAAW,IAAI,IAAI;EACxB,KAAK,gCAAgB,IAAI,IAAI;CAC/B;CAEA,QAAQ,cACN,YACG,IAAI,iBAA6B,OAAO;CAE7C,gBAAwD,cAAwC;EAC9F,gBAAA,kBAAkB,KAAK,WAAW,SAAS;EAC3C,OAAO,IAAI,aAAiC,MAAM,SAAS;CAC7D;CAEA,WACE,WACA,WAAqB;EAAE,SAAS;EAAG,WAAW;CAAI,MAC/C;EACH,gBAAA,kBAAkB,KAAK,WAAW,SAAS;EAC3C,CAAA,GAAA,cAAA,UAAA,CAAU,CAAC,KAAK,SAAS,IAAI,SAAS,GAAG,qBAAqB,WAAW;EACzE,KAAK,SAAS,IAAI,SAAS;EAC3B,KAAK,SAAS,IAAI,WAAW,QAAQ;EACrC,OAAO;CACT;;CAGA,qBAAqB,cAAgC;EACnD,MAAM,eAAe,KAAK,cAAc,IAAI,SAAS;EACrD,CAAA,GAAA,cAAA,UAAA,CAAU,cAAc,8BAA8B,WAAW;EACjE,OAAO,MAAM,KAAK,aAAa,YAAY;CAC7C;CAEA,WAAW,WAAmB,WAAuB;EACnD,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,yBAAyB,IAAI;EACjD,CAAA,GAAA,cAAA,UAAA,CAAU,SAAS,MAAM,sBAAsB,IAAI;EACnD,OAAO,SAAS;CAClB;CAEA,oBAAoB,WAAmB,WAAuB;EAC5D,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,yBAAyB,IAAI;EACjD,CAAA,GAAA,cAAA,UAAA,CAAU,SAAS,eAAe,gCAAgC,IAAI;EACtE,OAAO,SAAS;CAClB;CAEA,WAAW,cAAkD;EAC3D,IAAI,KAAK,SAAS,IAAI,SAAS,GAAG,OAAO;EACzC,IAAI,KAAK,cAAc,IAAI,SAAS,GAAG,OAAO;EAC9C,MAAM,IAAI,MAAM,8BAA8B,WAAW;CAC3D;CAEA,mBAAmB,WAAmB,WAA4B;EAChE,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,yBAAyB,IAAI;EACjD,OAAO,SAAS;CAClB;CAEA,sBAAuC,WAAmB,WAA4B;EACpF,MAAM,KAAK,KAAK,MAAM,WAAW,MAAM;EACvC,MAAM,WAAW,KAAK,SAAS,IAAI,EAAE;EACrC,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,yBAAyB,IAAI;EACjD,QAAA,GAAO,GAAA,QAAA,CAAG,SAAS,SAAS;CAC9B;CAEA,sBAAsB,WAAmB,WAA4B;EACnE,MAAM,YAAY,KAAK,mBAAmB,WAAW,MAAM;EAC3D,OAAO,IAAI,KAAK,KAAK,IAAI,IAAI,SAAS,CAAC,CAAC,YAAY;CACtD;AACF;;;;;;AAOA,SAAgB,uBAId,SAA4D;CAC5D,OAAO,iBAAiB,UAAU,CAAC,OAAO;AAC5C"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { Catalog } from "../catalog.cjs";
|
|
1
2
|
import { IsUnique, Metadata } from "../types.cjs";
|
|
2
3
|
//#region src/google-play/config.d.ts
|
|
3
4
|
declare const addMethod: unique symbol;
|
|
5
|
+
/** The private constructor's one door, for this module's `define` function only. */
|
|
6
|
+
declare const construct: unique symbol;
|
|
4
7
|
declare class Subscription<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PI extends string = never, I extends Lowercase<string> = never> {
|
|
5
8
|
readonly productId: string;
|
|
6
9
|
readonly config: GooglePlayConfig<NS, PE, BP, PI>;
|
|
@@ -18,20 +21,23 @@ declare class Subscription<NS extends Lowercase<string> = Lowercase<string>, PE
|
|
|
18
21
|
basePlan: <K extends Lowercase<string>>(planId: K extends I ? never : K, plan: PE, billingPeriod: BP, metadata?: Metadata) => Subscription<NS, PE, BP, PI, I | K>;
|
|
19
22
|
default: <const T extends readonly [I, ...I[]]>(planIds: T & (IsUnique<T> extends true ? unknown : never)) => GooglePlayConfig<NS, PE, BP, PI>;
|
|
20
23
|
}
|
|
21
|
-
type Options = {
|
|
24
|
+
type Options<NS extends Lowercase<string>, PE extends string, BP extends string> = Catalog<NS, PE, BP> & {
|
|
22
25
|
package: string;
|
|
23
26
|
audience: string;
|
|
24
27
|
};
|
|
25
28
|
export declare class GooglePlayConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PI extends string = never> {
|
|
26
29
|
readonly package: string;
|
|
27
30
|
readonly audience: string;
|
|
31
|
+
readonly namespace: NS;
|
|
32
|
+
readonly plans: readonly PE[];
|
|
33
|
+
readonly billingPeriods: readonly BP[];
|
|
28
34
|
private readonly onetimes;
|
|
29
35
|
private readonly subscriptions;
|
|
30
36
|
private readonly products;
|
|
31
37
|
[addMethod]: (subscription: Subscription<NS, PE, BP, PI>) => this;
|
|
32
38
|
private readonly getId;
|
|
33
39
|
private constructor();
|
|
34
|
-
static
|
|
40
|
+
static [construct]: <NS_1 extends Lowercase<string>, PE_1 extends string, BP_1 extends string>(options: Options<NS_1, PE_1, BP_1>) => GooglePlayConfig<NS_1, PE_1, BP_1, never>;
|
|
35
41
|
subscription: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => Subscription<NS, PE, BP, K | PI, never>;
|
|
36
42
|
onetime: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => GooglePlayConfig<NS, PE, BP, PI | K>;
|
|
37
43
|
/** Base plan ids that new subscribers should purchase for the product. */
|
|
@@ -43,6 +49,12 @@ export declare class GooglePlayConfig<NS extends Lowercase<string> = Lowercase<s
|
|
|
43
49
|
private readonly getCreditExpiresIn;
|
|
44
50
|
getCreditExpiresAt: (productId: string, planId?: string) => string;
|
|
45
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Declare the GooglePlay product catalog. Every type parameter is inferred from the options:
|
|
54
|
+
* the namespace from `namespace`, the plans and billing periods from `plans` and `billingPeriods`
|
|
55
|
+
* (`const`, so a literal array needs no `as const`). Products are then added with the fluent chain.
|
|
56
|
+
*/
|
|
57
|
+
export declare function defineGooglePlayConfig<const NS extends Lowercase<string>, const PE extends string, const BP extends string>(options: Options<NS, PE, BP>): GooglePlayConfig<NS, PE, BP>;
|
|
46
58
|
//#endregion
|
|
47
59
|
export {};
|
|
48
60
|
//# sourceMappingURL=config.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.cts","names":[],"sources":["../../src/google-play/config.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"config.d.cts","names":[],"sources":["../../src/google-play/config.ts"],"mappings":";;;cAKM;;cAEA;cAEA,aACJ,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA,UAAU;WAED;WACA,QAAQ,iBAAiB,IAAI,IAAI,IAAI;WACrC,OAAO,YAAY;IAAa,OAAO;IAAI,gBAAgB;;WAC3D,cAAc;EAEvB,YAAY,QAAQ,iBAAiB,IAAI,IAAI,IAAI,KAAK;;;;;;EAYtD,WAAY,UAAU,mBAAiB,QAC7B,UAAU,YAAY,GAAC,MACzB,IAAE,eACO,IAAE,WACP,aACT,aAAa,IAAI,IAAI,IAAI,IAAI,IAAI;EAOpC,gBAAiB,oBAAoB,MAAM,MAAI,SACpC,KAAK,SAAS,uCAAkC,iBAAA,IAAA,IAAA,IAAA;;KAWxD,QAAQ,WAAW,mBAAmB,mBAAmB,qBAAqB,QACjF,IACA,IACA;EACI;EAAiB;;qBAEV,iBACX,WAAW,oBAAoB,mBAC/B,4BACA,4BACA;WAES;WACA;WACA,WAAW;WACX,gBAAgB;WAChB,yBAAyB;mBACjB;mBACA;mBACA;GAIhB,aAAU,cAAkB,aAAa,IAAI,IAAI,IAAI;mBAcrC;UAIV;UAYC,aAAc,aAAW,mBAAmB,qBAAmB,qBAAiB,SAC7E,QAAQ,MAAI,MAAI,UAAG,iBAAA,MAAA,MAAA;EAG9B,eAAgB,aAAa,MAAM,qBAAmB,WAAa,UAAU,aAAa,MAAC,aAAA,IAAA,IAAA,IAAA,IAAA;EAK3F,UAAW,aAAa,MAAM,qBAAmB,WACpC,UAAU,aAAa,GAAC,WACzB,aAMK,iBAAiB,IAAI,IAAI,IAAI,KAAK;;EAInD,oBAAiB;EAMjB,UAAO,mBAAqB,mBAAmB;EAQ/C,mBAAgB,mBAAqB,mBAAmB;EAQxD,UAAO;EAMP,kBAAe,mBAAqB;mBAOnB;EAOjB,qBAAkB,mBAAqB;;;;;;;wBAWzB,6BACR,WAAW,yBACX,yBACA,mBACN,SAAS,QAAQ,IAAI,IAAI,MAAM,iBAAiB,IAAI,IAAI"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { Catalog } from "../catalog.mjs";
|
|
1
2
|
import { IsUnique, Metadata } from "../types.mjs";
|
|
2
3
|
//#region src/google-play/config.d.ts
|
|
3
4
|
declare const addMethod: unique symbol;
|
|
5
|
+
/** The private constructor's one door, for this module's `define` function only. */
|
|
6
|
+
declare const construct: unique symbol;
|
|
4
7
|
declare class Subscription<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PI extends string = never, I extends Lowercase<string> = never> {
|
|
5
8
|
readonly productId: string;
|
|
6
9
|
readonly config: GooglePlayConfig<NS, PE, BP, PI>;
|
|
@@ -18,20 +21,23 @@ declare class Subscription<NS extends Lowercase<string> = Lowercase<string>, PE
|
|
|
18
21
|
basePlan: <K extends Lowercase<string>>(planId: K extends I ? never : K, plan: PE, billingPeriod: BP, metadata?: Metadata) => Subscription<NS, PE, BP, PI, I | K>;
|
|
19
22
|
default: <const T extends readonly [I, ...I[]]>(planIds: T & (IsUnique<T> extends true ? unknown : never)) => GooglePlayConfig<NS, PE, BP, PI>;
|
|
20
23
|
}
|
|
21
|
-
type Options = {
|
|
24
|
+
type Options<NS extends Lowercase<string>, PE extends string, BP extends string> = Catalog<NS, PE, BP> & {
|
|
22
25
|
package: string;
|
|
23
26
|
audience: string;
|
|
24
27
|
};
|
|
25
28
|
export declare class GooglePlayConfig<NS extends Lowercase<string> = Lowercase<string>, PE extends string = string, BP extends string = string, PI extends string = never> {
|
|
26
29
|
readonly package: string;
|
|
27
30
|
readonly audience: string;
|
|
31
|
+
readonly namespace: NS;
|
|
32
|
+
readonly plans: readonly PE[];
|
|
33
|
+
readonly billingPeriods: readonly BP[];
|
|
28
34
|
private readonly onetimes;
|
|
29
35
|
private readonly subscriptions;
|
|
30
36
|
private readonly products;
|
|
31
37
|
[addMethod]: (subscription: Subscription<NS, PE, BP, PI>) => this;
|
|
32
38
|
private readonly getId;
|
|
33
39
|
private constructor();
|
|
34
|
-
static
|
|
40
|
+
static [construct]: <NS_1 extends Lowercase<string>, PE_1 extends string, BP_1 extends string>(options: Options<NS_1, PE_1, BP_1>) => GooglePlayConfig<NS_1, PE_1, BP_1, never>;
|
|
35
41
|
subscription: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => Subscription<NS, PE, BP, K | PI, never>;
|
|
36
42
|
onetime: <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K, metadata?: Metadata) => GooglePlayConfig<NS, PE, BP, PI | K>;
|
|
37
43
|
/** Base plan ids that new subscribers should purchase for the product. */
|
|
@@ -43,6 +49,12 @@ export declare class GooglePlayConfig<NS extends Lowercase<string> = Lowercase<s
|
|
|
43
49
|
private readonly getCreditExpiresIn;
|
|
44
50
|
getCreditExpiresAt: (productId: string, planId?: string) => string;
|
|
45
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Declare the GooglePlay product catalog. Every type parameter is inferred from the options:
|
|
54
|
+
* the namespace from `namespace`, the plans and billing periods from `plans` and `billingPeriods`
|
|
55
|
+
* (`const`, so a literal array needs no `as const`). Products are then added with the fluent chain.
|
|
56
|
+
*/
|
|
57
|
+
export declare function defineGooglePlayConfig<const NS extends Lowercase<string>, const PE extends string, const BP extends string>(options: Options<NS, PE, BP>): GooglePlayConfig<NS, PE, BP>;
|
|
46
58
|
//#endregion
|
|
47
59
|
export {};
|
|
48
60
|
//# sourceMappingURL=config.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.mts","names":[],"sources":["../../src/google-play/config.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"config.d.mts","names":[],"sources":["../../src/google-play/config.ts"],"mappings":";;;cAKM;;cAEA;cAEA,aACJ,WAAW,oBAAoB,mBAC/B,4BACA,4BACA,2BACA,UAAU;WAED;WACA,QAAQ,iBAAiB,IAAI,IAAI,IAAI;WACrC,OAAO,YAAY;IAAa,OAAO;IAAI,gBAAgB;;WAC3D,cAAc;EAEvB,YAAY,QAAQ,iBAAiB,IAAI,IAAI,IAAI,KAAK;;;;;;EAYtD,WAAY,UAAU,mBAAiB,QAC7B,UAAU,YAAY,GAAC,MACzB,IAAE,eACO,IAAE,WACP,aACT,aAAa,IAAI,IAAI,IAAI,IAAI,IAAI;EAOpC,gBAAiB,oBAAoB,MAAM,MAAI,SACpC,KAAK,SAAS,uCAAkC,iBAAA,IAAA,IAAA,IAAA;;KAWxD,QAAQ,WAAW,mBAAmB,mBAAmB,qBAAqB,QACjF,IACA,IACA;EACI;EAAiB;;qBAEV,iBACX,WAAW,oBAAoB,mBAC/B,4BACA,4BACA;WAES;WACA;WACA,WAAW;WACX,gBAAgB;WAChB,yBAAyB;mBACjB;mBACA;mBACA;GAIhB,aAAU,cAAkB,aAAa,IAAI,IAAI,IAAI;mBAcrC;UAIV;UAYC,aAAc,aAAW,mBAAmB,qBAAmB,qBAAiB,SAC7E,QAAQ,MAAI,MAAI,UAAG,iBAAA,MAAA,MAAA;EAG9B,eAAgB,aAAa,MAAM,qBAAmB,WAAa,UAAU,aAAa,MAAC,aAAA,IAAA,IAAA,IAAA,IAAA;EAK3F,UAAW,aAAa,MAAM,qBAAmB,WACpC,UAAU,aAAa,GAAC,WACzB,aAMK,iBAAiB,IAAI,IAAI,IAAI,KAAK;;EAInD,oBAAiB;EAMjB,UAAO,mBAAqB,mBAAmB;EAQ/C,mBAAgB,mBAAqB,mBAAmB;EAQxD,UAAO;EAMP,kBAAe,mBAAqB;mBAOnB;EAOjB,qBAAkB,mBAAqB;;;;;;;wBAWzB,6BACR,WAAW,yBACX,yBACA,mBACN,SAAS,QAAQ,IAAI,IAAI,MAAM,iBAAiB,IAAI,IAAI"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { assertCatalog, assertDeclared, assertInNamespace } from "../catalog.mjs";
|
|
1
2
|
import { invariant } from "@shware/utils";
|
|
2
3
|
import ms from "ms";
|
|
3
4
|
//#region src/google-play/config.ts
|
|
4
5
|
const addMethod = Symbol("addMethod");
|
|
6
|
+
/** The private constructor's one door, for this module's `define` function only. */
|
|
7
|
+
const construct = Symbol("construct");
|
|
5
8
|
var Subscription = class {
|
|
6
9
|
productId;
|
|
7
10
|
config;
|
|
@@ -22,6 +25,7 @@ var Subscription = class {
|
|
|
22
25
|
credits: 0,
|
|
23
26
|
expiresIn: "0"
|
|
24
27
|
}) => {
|
|
28
|
+
assertDeclared(this.config, plan, billingPeriod);
|
|
25
29
|
invariant(!this.plans.has(planId), `Duplicate base plan ${planId}`);
|
|
26
30
|
this.plans.set(planId, {
|
|
27
31
|
...metadata,
|
|
@@ -42,6 +46,9 @@ var Subscription = class {
|
|
|
42
46
|
var GooglePlayConfig = class GooglePlayConfig {
|
|
43
47
|
package;
|
|
44
48
|
audience;
|
|
49
|
+
namespace;
|
|
50
|
+
plans;
|
|
51
|
+
billingPeriods;
|
|
45
52
|
onetimes;
|
|
46
53
|
subscriptions;
|
|
47
54
|
products;
|
|
@@ -59,22 +66,26 @@ var GooglePlayConfig = class GooglePlayConfig {
|
|
|
59
66
|
return planId ? `${productId}:${planId}` : productId;
|
|
60
67
|
};
|
|
61
68
|
constructor(options) {
|
|
69
|
+
assertCatalog(options);
|
|
70
|
+
this.namespace = options.namespace;
|
|
71
|
+
this.plans = options.plans;
|
|
72
|
+
this.billingPeriods = options.billingPeriods;
|
|
62
73
|
this.package = options.package;
|
|
63
74
|
this.audience = options.audience;
|
|
64
75
|
this.products = /* @__PURE__ */ new Map();
|
|
65
76
|
this.onetimes = /* @__PURE__ */ new Set();
|
|
66
77
|
this.subscriptions = /* @__PURE__ */ new Map();
|
|
67
78
|
}
|
|
68
|
-
static
|
|
69
|
-
return new GooglePlayConfig(options);
|
|
70
|
-
};
|
|
79
|
+
static [construct] = (options) => new GooglePlayConfig(options);
|
|
71
80
|
subscription = (productId) => {
|
|
81
|
+
assertInNamespace(this.namespace, productId);
|
|
72
82
|
return new Subscription(this, productId);
|
|
73
83
|
};
|
|
74
84
|
onetime = (productId, metadata = {
|
|
75
85
|
credits: 0,
|
|
76
86
|
expiresIn: "0"
|
|
77
87
|
}) => {
|
|
88
|
+
assertInNamespace(this.namespace, productId);
|
|
78
89
|
invariant(!this.products.has(productId), `Duplicate product ${productId}`);
|
|
79
90
|
this.onetimes.add(productId);
|
|
80
91
|
this.products.set(productId, metadata);
|
|
@@ -122,7 +133,15 @@ var GooglePlayConfig = class GooglePlayConfig {
|
|
|
122
133
|
return new Date(Date.now() + expiresIn).toISOString();
|
|
123
134
|
};
|
|
124
135
|
};
|
|
136
|
+
/**
|
|
137
|
+
* Declare the GooglePlay product catalog. Every type parameter is inferred from the options:
|
|
138
|
+
* the namespace from `namespace`, the plans and billing periods from `plans` and `billingPeriods`
|
|
139
|
+
* (`const`, so a literal array needs no `as const`). Products are then added with the fluent chain.
|
|
140
|
+
*/
|
|
141
|
+
function defineGooglePlayConfig(options) {
|
|
142
|
+
return GooglePlayConfig[construct](options);
|
|
143
|
+
}
|
|
125
144
|
//#endregion
|
|
126
|
-
export { GooglePlayConfig };
|
|
145
|
+
export { GooglePlayConfig, defineGooglePlayConfig };
|
|
127
146
|
|
|
128
147
|
//# sourceMappingURL=config.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/google-play/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport type { IsUnique, Metadata } from '../types';\n\nconst addMethod = Symbol('addMethod');\n\nclass Subscription<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PI extends string = never,\n I extends Lowercase<string> = never,\n> {\n readonly productId: string;\n readonly config: GooglePlayConfig<NS, PE, BP, PI>;\n readonly plans: Map<string, Metadata & { plan?: PE; billingPeriod?: BP }>;\n readonly defaultPlans: Set<string>;\n\n constructor(config: GooglePlayConfig<NS, PE, BP, PI>, productId: string) {\n this.config = config;\n this.productId = productId;\n this.plans = new Map();\n this.defaultPlans = new Set();\n }\n\n /**\n * Non-default base plans stay declared on purpose: they keep resolving for\n * grandfathered subscribers and serve as price-experiment arms. Never sell\n * a legacy base plan to new users unless that is intentional.\n */\n basePlan = <K extends Lowercase<string>>(\n planId: K extends I ? never : K,\n plan: PE,\n billingPeriod: BP,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Subscription<NS, PE, BP, PI, I | K> => {\n invariant(!this.plans.has(planId), `Duplicate base plan ${planId}`);\n this.plans.set(planId, { ...metadata, plan, billingPeriod });\n return this;\n };\n\n default = <const T extends readonly [I, ...I[]]>(\n planIds: T & (IsUnique<T> extends true ? unknown : never)\n ) => {\n planIds.forEach((planId) => {\n invariant(this.plans.has(planId), `Default base plan ${planId} is not declared`);\n this.defaultPlans.add(planId);\n });\n this.config[addMethod](this);\n return this.config;\n };\n}\n\ntype Options = { package: string; audience: string };\n\nexport class GooglePlayConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PI extends string = never,\n> {\n readonly package: string;\n readonly audience: string;\n private readonly onetimes: Set<string>;\n private readonly subscriptions: Map<string, Subscription<NS, PE, BP, PI>>;\n private readonly products: Map<string, (Metadata & { plan?: PE; billingPeriod?: BP }) | null>;\n\n // runtime mirror of the type-level guards: generic accumulation only\n // protects a single fluent chain, not statement-style re-entry or casts\n [addMethod] = (subscription: Subscription<NS, PE, BP, PI>) => {\n invariant(\n !this.subscriptions.has(subscription.productId),\n `Duplicate subscription ${subscription.productId}`\n );\n subscription.plans.forEach((metadata, planId) => {\n const id = this.getId(subscription.productId, planId);\n invariant(!this.products.has(id), `Duplicate product ${id}`);\n this.products.set(id, metadata);\n });\n this.subscriptions.set(subscription.productId, subscription);\n return this;\n };\n\n private readonly getId = (productId: string, planId?: string) => {\n return planId ? `${productId}:${planId}` : productId;\n };\n\n private constructor(options: Options) {\n this.package = options.package;\n this.audience = options.audience;\n this.products = new Map();\n this.onetimes = new Set();\n this.subscriptions = new Map();\n }\n\n static create = <\n NS extends Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n >(\n options: Options\n ) => {\n return new GooglePlayConfig<NS, PE, BP>(options);\n };\n\n subscription = <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => {\n return new Subscription<NS, PE, BP, PI | K>(this, productId);\n };\n\n onetime = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ) => {\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.onetimes.add(productId);\n this.products.set(productId, metadata);\n return this as GooglePlayConfig<NS, PE, BP, PI | K>;\n };\n\n /** Base plan ids that new subscribers should purchase for the product. */\n getDefaultPlanIds = (productId: string): string[] => {\n const subscription = this.subscriptions.get(productId);\n invariant(subscription, `Subscription not found for ${productId}`);\n return Array.from(subscription.defaultPlans);\n };\n\n getPlan = (productId: string, planId: string): PE => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.plan, `Plan not found for ${id}`);\n return metadata.plan;\n };\n\n getBillingPeriod = (productId: string, planId: string): BP => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.billingPeriod, `Billing period not found for ${id}`);\n return metadata.billingPeriod;\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n if (this.onetimes.has(productId)) return 'payment';\n if (this.subscriptions.has(productId)) return 'subscription';\n throw new Error(`Mode not found for product ${productId}`);\n };\n\n getCreditAmount = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return metadata.credits;\n };\n\n private readonly getCreditExpiresIn = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return ms(metadata.expiresIn);\n };\n\n getCreditExpiresAt = (productId: string, planId?: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId, planId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/google-play/config.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport ms from 'ms';\nimport { type Catalog, assertCatalog, assertDeclared, assertInNamespace } from '../catalog';\nimport type { IsUnique, Metadata } from '../types';\n\nconst addMethod = Symbol('addMethod');\n/** The private constructor's one door, for this module's `define` function only. */\nconst construct = Symbol('construct');\n\nclass Subscription<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PI extends string = never,\n I extends Lowercase<string> = never,\n> {\n readonly productId: string;\n readonly config: GooglePlayConfig<NS, PE, BP, PI>;\n readonly plans: Map<string, Metadata & { plan?: PE; billingPeriod?: BP }>;\n readonly defaultPlans: Set<string>;\n\n constructor(config: GooglePlayConfig<NS, PE, BP, PI>, productId: string) {\n this.config = config;\n this.productId = productId;\n this.plans = new Map();\n this.defaultPlans = new Set();\n }\n\n /**\n * Non-default base plans stay declared on purpose: they keep resolving for\n * grandfathered subscribers and serve as price-experiment arms. Never sell\n * a legacy base plan to new users unless that is intentional.\n */\n basePlan = <K extends Lowercase<string>>(\n planId: K extends I ? never : K,\n plan: PE,\n billingPeriod: BP,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ): Subscription<NS, PE, BP, PI, I | K> => {\n assertDeclared(this.config, plan, billingPeriod);\n invariant(!this.plans.has(planId), `Duplicate base plan ${planId}`);\n this.plans.set(planId, { ...metadata, plan, billingPeriod });\n return this;\n };\n\n default = <const T extends readonly [I, ...I[]]>(\n planIds: T & (IsUnique<T> extends true ? unknown : never)\n ) => {\n planIds.forEach((planId) => {\n invariant(this.plans.has(planId), `Default base plan ${planId} is not declared`);\n this.defaultPlans.add(planId);\n });\n this.config[addMethod](this);\n return this.config;\n };\n}\n\ntype Options<NS extends Lowercase<string>, PE extends string, BP extends string> = Catalog<\n NS,\n PE,\n BP\n> & { package: string; audience: string };\n\nexport class GooglePlayConfig<\n NS extends Lowercase<string> = Lowercase<string>,\n PE extends string = string,\n BP extends string = string,\n PI extends string = never,\n> {\n readonly package: string;\n readonly audience: string;\n readonly namespace: NS;\n readonly plans: readonly PE[];\n readonly billingPeriods: readonly BP[];\n private readonly onetimes: Set<string>;\n private readonly subscriptions: Map<string, Subscription<NS, PE, BP, PI>>;\n private readonly products: Map<string, (Metadata & { plan?: PE; billingPeriod?: BP }) | null>;\n\n // runtime mirror of the type-level guards: generic accumulation only\n // protects a single fluent chain, not statement-style re-entry or casts\n [addMethod] = (subscription: Subscription<NS, PE, BP, PI>) => {\n invariant(\n !this.subscriptions.has(subscription.productId),\n `Duplicate subscription ${subscription.productId}`\n );\n subscription.plans.forEach((metadata, planId) => {\n const id = this.getId(subscription.productId, planId);\n invariant(!this.products.has(id), `Duplicate product ${id}`);\n this.products.set(id, metadata);\n });\n this.subscriptions.set(subscription.productId, subscription);\n return this;\n };\n\n private readonly getId = (productId: string, planId?: string) => {\n return planId ? `${productId}:${planId}` : productId;\n };\n\n private constructor(options: Options<NS, PE, BP>) {\n assertCatalog(options);\n this.namespace = options.namespace;\n this.plans = options.plans;\n this.billingPeriods = options.billingPeriods;\n this.package = options.package;\n this.audience = options.audience;\n this.products = new Map();\n this.onetimes = new Set();\n this.subscriptions = new Map();\n }\n\n static [construct] = <NS extends Lowercase<string>, PE extends string, BP extends string>(\n options: Options<NS, PE, BP>\n ) => new GooglePlayConfig<NS, PE, BP>(options);\n\n subscription = <K extends `${NS}.${Lowercase<string>}`>(productId: K extends PI ? never : K) => {\n assertInNamespace(this.namespace, productId);\n return new Subscription<NS, PE, BP, PI | K>(this, productId);\n };\n\n onetime = <K extends `${NS}.${Lowercase<string>}`>(\n productId: K extends PI ? never : K,\n metadata: Metadata = { credits: 0, expiresIn: '0' }\n ) => {\n assertInNamespace(this.namespace, productId);\n invariant(!this.products.has(productId), `Duplicate product ${productId}`);\n this.onetimes.add(productId);\n this.products.set(productId, metadata);\n return this as GooglePlayConfig<NS, PE, BP, PI | K>;\n };\n\n /** Base plan ids that new subscribers should purchase for the product. */\n getDefaultPlanIds = (productId: string): string[] => {\n const subscription = this.subscriptions.get(productId);\n invariant(subscription, `Subscription not found for ${productId}`);\n return Array.from(subscription.defaultPlans);\n };\n\n getPlan = (productId: string, planId: string): PE => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.plan, `Plan not found for ${id}`);\n return metadata.plan;\n };\n\n getBillingPeriod = (productId: string, planId: string): BP => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n invariant(metadata.billingPeriod, `Billing period not found for ${id}`);\n return metadata.billingPeriod;\n };\n\n getMode = (productId: string): 'payment' | 'subscription' => {\n if (this.onetimes.has(productId)) return 'payment';\n if (this.subscriptions.has(productId)) return 'subscription';\n throw new Error(`Mode not found for product ${productId}`);\n };\n\n getCreditAmount = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return metadata.credits;\n };\n\n private readonly getCreditExpiresIn = (productId: string, planId?: string): number => {\n const id = this.getId(productId, planId);\n const metadata = this.products.get(id);\n invariant(metadata, `Product not found for ${id}`);\n return ms(metadata.expiresIn);\n };\n\n getCreditExpiresAt = (productId: string, planId?: string): string => {\n const expiresIn = this.getCreditExpiresIn(productId, planId);\n return new Date(Date.now() + expiresIn).toISOString();\n };\n}\n\n/**\n * Declare the GooglePlay product catalog. Every type parameter is inferred from the options:\n * the namespace from `namespace`, the plans and billing periods from `plans` and `billingPeriods`\n * (`const`, so a literal array needs no `as const`). Products are then added with the fluent chain.\n */\nexport function defineGooglePlayConfig<\n const NS extends Lowercase<string>,\n const PE extends string,\n const BP extends string,\n>(options: Options<NS, PE, BP>): GooglePlayConfig<NS, PE, BP> {\n return GooglePlayConfig[construct](options);\n}\n"],"mappings":"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"}
|
|
@@ -8,6 +8,8 @@ exports.OneTimeProductNotificationType = require_google_play_real_time_developer
|
|
|
8
8
|
exports.SubscriptionNotificationType = require_google_play_real_time_developer_notification.SubscriptionNotificationType;
|
|
9
9
|
exports.VoidedPurchaseProductType = require_google_play_real_time_developer_notification.VoidedPurchaseProductType;
|
|
10
10
|
exports.VoidedPurchaseRefundType = require_google_play_real_time_developer_notification.VoidedPurchaseRefundType;
|
|
11
|
+
exports.defineGooglePlayConfig = require_google_play_config.defineGooglePlayConfig;
|
|
11
12
|
exports.mapCanceledAt = require_google_play_mapper.mapCanceledAt;
|
|
12
13
|
exports.mapCancellationDetails = require_google_play_mapper.mapCancellationDetails;
|
|
14
|
+
exports.mapPaymentType = require_google_play_mapper.mapPaymentType;
|
|
13
15
|
exports.mapSubscriptionStatus = require_google_play_mapper.mapSubscriptionStatus;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GooglePlayConfig } from "./config.cjs";
|
|
1
|
+
import { GooglePlayConfig, defineGooglePlayConfig } from "./config.cjs";
|
|
2
2
|
import { DeveloperNotification, GOOGLE_PLAY_SUBSCRIPTION_STATES, GooglePlaySubscriptionState, OneTimeProductNotification, OneTimeProductNotificationType, PubsubMessage, SubscriptionNotification, SubscriptionNotificationType, TestNotification, VoidedPurchaseNotification, VoidedPurchaseProductType, VoidedPurchaseRefundType } from "./real-time-developer-notification.cjs";
|
|
3
|
-
import { CanceledStateContext, mapCanceledAt, mapCancellationDetails, mapSubscriptionStatus } from "./mapper.cjs";
|
|
4
|
-
export { type CanceledStateContext, type DeveloperNotification, GOOGLE_PLAY_SUBSCRIPTION_STATES, GooglePlayConfig, type GooglePlaySubscriptionState, type OneTimeProductNotification, OneTimeProductNotificationType, type PubsubMessage, type SubscriptionNotification, SubscriptionNotificationType, type TestNotification, type VoidedPurchaseNotification, VoidedPurchaseProductType, VoidedPurchaseRefundType, mapCanceledAt, mapCancellationDetails, mapSubscriptionStatus };
|
|
3
|
+
import { CanceledStateContext, mapCanceledAt, mapCancellationDetails, mapPaymentType, mapSubscriptionStatus } from "./mapper.cjs";
|
|
4
|
+
export { type CanceledStateContext, type DeveloperNotification, GOOGLE_PLAY_SUBSCRIPTION_STATES, GooglePlayConfig, type GooglePlaySubscriptionState, type OneTimeProductNotification, OneTimeProductNotificationType, type PubsubMessage, type SubscriptionNotification, SubscriptionNotificationType, type TestNotification, type VoidedPurchaseNotification, VoidedPurchaseProductType, VoidedPurchaseRefundType, defineGooglePlayConfig, mapCanceledAt, mapCancellationDetails, mapPaymentType, mapSubscriptionStatus };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GooglePlayConfig } from "./config.mjs";
|
|
1
|
+
import { GooglePlayConfig, defineGooglePlayConfig } from "./config.mjs";
|
|
2
2
|
import { DeveloperNotification, GOOGLE_PLAY_SUBSCRIPTION_STATES, GooglePlaySubscriptionState, OneTimeProductNotification, OneTimeProductNotificationType, PubsubMessage, SubscriptionNotification, SubscriptionNotificationType, TestNotification, VoidedPurchaseNotification, VoidedPurchaseProductType, VoidedPurchaseRefundType } from "./real-time-developer-notification.mjs";
|
|
3
|
-
import { CanceledStateContext, mapCanceledAt, mapCancellationDetails, mapSubscriptionStatus } from "./mapper.mjs";
|
|
4
|
-
export { type CanceledStateContext, type DeveloperNotification, GOOGLE_PLAY_SUBSCRIPTION_STATES, GooglePlayConfig, type GooglePlaySubscriptionState, type OneTimeProductNotification, OneTimeProductNotificationType, type PubsubMessage, type SubscriptionNotification, SubscriptionNotificationType, type TestNotification, type VoidedPurchaseNotification, VoidedPurchaseProductType, VoidedPurchaseRefundType, mapCanceledAt, mapCancellationDetails, mapSubscriptionStatus };
|
|
3
|
+
import { CanceledStateContext, mapCanceledAt, mapCancellationDetails, mapPaymentType, mapSubscriptionStatus } from "./mapper.mjs";
|
|
4
|
+
export { type CanceledStateContext, type DeveloperNotification, GOOGLE_PLAY_SUBSCRIPTION_STATES, GooglePlayConfig, type GooglePlaySubscriptionState, type OneTimeProductNotification, OneTimeProductNotificationType, type PubsubMessage, type SubscriptionNotification, SubscriptionNotificationType, type TestNotification, type VoidedPurchaseNotification, VoidedPurchaseProductType, VoidedPurchaseRefundType, defineGooglePlayConfig, mapCanceledAt, mapCancellationDetails, mapPaymentType, mapSubscriptionStatus };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GooglePlayConfig } from "./config.mjs";
|
|
1
|
+
import { GooglePlayConfig, defineGooglePlayConfig } from "./config.mjs";
|
|
2
2
|
import { GOOGLE_PLAY_SUBSCRIPTION_STATES, OneTimeProductNotificationType, SubscriptionNotificationType, VoidedPurchaseProductType, VoidedPurchaseRefundType } from "./real-time-developer-notification.mjs";
|
|
3
|
-
import { mapCanceledAt, mapCancellationDetails, mapSubscriptionStatus } from "./mapper.mjs";
|
|
4
|
-
export { GOOGLE_PLAY_SUBSCRIPTION_STATES, GooglePlayConfig, OneTimeProductNotificationType, SubscriptionNotificationType, VoidedPurchaseProductType, VoidedPurchaseRefundType, mapCanceledAt, mapCancellationDetails, mapSubscriptionStatus };
|
|
3
|
+
import { mapCanceledAt, mapCancellationDetails, mapPaymentType, mapSubscriptionStatus } from "./mapper.mjs";
|
|
4
|
+
export { GOOGLE_PLAY_SUBSCRIPTION_STATES, GooglePlayConfig, OneTimeProductNotificationType, SubscriptionNotificationType, VoidedPurchaseProductType, VoidedPurchaseRefundType, defineGooglePlayConfig, mapCanceledAt, mapCancellationDetails, mapPaymentType, mapSubscriptionStatus };
|