@burdenoff/microfe-billing 2026.609.3 → 2026.612.1
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/billing/modules/checkout/components/NativeCheckoutView.js +119 -115
- package/dist/billing/modules/checkout/components/NativeCheckoutView.js.map +1 -1
- package/dist/billing/modules/checkout/hooks/useNativeCheckout.js +57 -53
- package/dist/billing/modules/checkout/hooks/useNativeCheckout.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +472 -411
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,88 +1,92 @@
|
|
|
1
1
|
import { useBilling as e } from "../../../providers/BillingProvider.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { normalizeGlobalGatewayBaseUrl as t } from "../../../shared/utils/gatewayGraphql.js";
|
|
3
|
+
import { Capacitor as n } from "../../../../node_modules/@capacitor/core/dist/index.js";
|
|
4
|
+
import { acknowledgeNativePurchase as r, getNativeProducts as i, isNativeIAPAvailable as a, purchaseNativeProduct as o, restoreNativePurchases as s } from "../../../lib/native-purchases.js";
|
|
5
|
+
import { useCallback as c, useState as l } from "react";
|
|
5
6
|
//#region src/billing/modules/checkout/hooks/useNativeCheckout.ts
|
|
6
|
-
function
|
|
7
|
-
let { apiGatewayUrl:
|
|
8
|
-
if (!
|
|
9
|
-
let t = e ?? (
|
|
7
|
+
function u({ billingAccountId: u, purchaseType: d, productId: f } = {}) {
|
|
8
|
+
let { apiGatewayUrl: p, authToken: m } = e(), [h, g] = l([]), [_, v] = l("idle"), [y, b] = l(null), [x, S] = l(!1), C = c(async (e) => {
|
|
9
|
+
if (!a()) return;
|
|
10
|
+
let t = e ?? (f ? [f] : []);
|
|
10
11
|
if (t.length !== 0) {
|
|
11
|
-
|
|
12
|
+
S(!0), b(null);
|
|
12
13
|
try {
|
|
13
|
-
|
|
14
|
+
g(await i(t));
|
|
14
15
|
} catch (e) {
|
|
15
|
-
|
|
16
|
+
b(e instanceof Error ? e.message : "Failed to load products");
|
|
16
17
|
} finally {
|
|
17
|
-
|
|
18
|
+
S(!1);
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
|
-
}, [
|
|
21
|
-
if (!
|
|
22
|
-
|
|
23
|
-
let
|
|
21
|
+
}, [f]), w = c(async (e) => {
|
|
22
|
+
if (!u) return b("No billing account selected"), { success: !1 };
|
|
23
|
+
v("processing"), b(null);
|
|
24
|
+
let i;
|
|
24
25
|
try {
|
|
25
|
-
let
|
|
26
|
-
|
|
26
|
+
let a = d === "CREDITS", s = crypto.randomUUID();
|
|
27
|
+
i = await o(e.identifier, {
|
|
27
28
|
autoAcknowledgePurchases: !1,
|
|
28
|
-
isConsumable:
|
|
29
|
-
planIdentifier:
|
|
30
|
-
appAccountToken:
|
|
29
|
+
isConsumable: a,
|
|
30
|
+
planIdentifier: a ? void 0 : e.planIdentifier ?? void 0,
|
|
31
|
+
appAccountToken: s
|
|
31
32
|
});
|
|
32
|
-
let
|
|
33
|
+
let c = `${t(p) ?? ""}/global/rest/billing/iap/validate`, l = n.getPlatform(), f = a ? "INAPP" : "SUBS", h = await fetch(c, {
|
|
33
34
|
method: "POST",
|
|
34
35
|
headers: {
|
|
35
36
|
"Content-Type": "application/json",
|
|
36
|
-
...
|
|
37
|
+
...m ? { Authorization: `Bearer ${m}` } : {}
|
|
37
38
|
},
|
|
38
39
|
body: JSON.stringify({
|
|
39
|
-
platform:
|
|
40
|
-
transactionId:
|
|
41
|
-
productIdentifier:
|
|
42
|
-
receipt:
|
|
43
|
-
jwsRepresentation:
|
|
44
|
-
purchaseToken:
|
|
45
|
-
appAccountToken:
|
|
46
|
-
billingAccountId:
|
|
47
|
-
purchaseType:
|
|
40
|
+
platform: l,
|
|
41
|
+
transactionId: i.transactionId,
|
|
42
|
+
productIdentifier: i.productIdentifier,
|
|
43
|
+
receipt: i.receipt,
|
|
44
|
+
jwsRepresentation: i.jwsRepresentation,
|
|
45
|
+
purchaseToken: i.purchaseToken,
|
|
46
|
+
appAccountToken: s,
|
|
47
|
+
billingAccountId: u,
|
|
48
|
+
purchaseType: d,
|
|
49
|
+
nativePurchaseType: f,
|
|
50
|
+
price: e.price,
|
|
51
|
+
currency: e.currencyCode
|
|
48
52
|
})
|
|
49
53
|
});
|
|
50
|
-
if (!
|
|
51
|
-
let e = await
|
|
52
|
-
throw Error(e.message ?? `Validation failed (${
|
|
54
|
+
if (!h.ok) {
|
|
55
|
+
let e = await h.json().catch(() => ({}));
|
|
56
|
+
throw Error(e.message ?? `Validation failed (${h.status})`);
|
|
53
57
|
}
|
|
54
|
-
return await
|
|
58
|
+
return await r(i.purchaseToken ?? i.transactionId), v("success"), { success: !0 };
|
|
55
59
|
} catch (e) {
|
|
56
60
|
let t = e instanceof Error ? e.message : "Purchase failed";
|
|
57
|
-
return t.toLowerCase().includes("cancel") || t.toLowerCase().includes("dismiss") || t.toLowerCase().includes("abort") ?
|
|
61
|
+
return t.toLowerCase().includes("cancel") || t.toLowerCase().includes("dismiss") || t.toLowerCase().includes("abort") ? v("idle") : (b(t), v("error")), { success: !1 };
|
|
58
62
|
}
|
|
59
63
|
}, [
|
|
60
|
-
|
|
61
|
-
f,
|
|
64
|
+
u,
|
|
62
65
|
p,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
m,
|
|
67
|
+
d
|
|
68
|
+
]), T = c(async () => {
|
|
69
|
+
S(!0), b(null);
|
|
66
70
|
try {
|
|
67
|
-
return await
|
|
71
|
+
return await s(), { success: !0 };
|
|
68
72
|
} catch (e) {
|
|
69
|
-
return
|
|
73
|
+
return b(e instanceof Error ? e.message : "Restore failed"), { success: !1 };
|
|
70
74
|
} finally {
|
|
71
|
-
|
|
75
|
+
S(!1);
|
|
72
76
|
}
|
|
73
77
|
}, []);
|
|
74
78
|
return {
|
|
75
|
-
isAvailable:
|
|
76
|
-
products:
|
|
77
|
-
checkoutState:
|
|
78
|
-
error:
|
|
79
|
-
isLoading:
|
|
80
|
-
loadProducts:
|
|
81
|
-
purchaseProduct:
|
|
82
|
-
restorePurchases:
|
|
79
|
+
isAvailable: a(),
|
|
80
|
+
products: h,
|
|
81
|
+
checkoutState: _,
|
|
82
|
+
error: y,
|
|
83
|
+
isLoading: x,
|
|
84
|
+
loadProducts: C,
|
|
85
|
+
purchaseProduct: w,
|
|
86
|
+
restorePurchases: T
|
|
83
87
|
};
|
|
84
88
|
}
|
|
85
89
|
//#endregion
|
|
86
|
-
export {
|
|
90
|
+
export { u as useNativeCheckout };
|
|
87
91
|
|
|
88
92
|
//# sourceMappingURL=useNativeCheckout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useNativeCheckout.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useNativeCheckout.ts"],"sourcesContent":["/**\n * Native IAP checkout hook — Apple StoreKit / Google Play Billing.\n *\n * Manages the full native purchase lifecycle:\n * 1. Load product info from the App Store / Play Store\n * 2. Trigger the native payment sheet\n * 3. Send the receipt / JWS / purchase token to the backend for validation\n * 4. Acknowledge the transaction after the backend confirms it\n *\n * The backend endpoint POST /rest/billing/iap/validate validates with Apple\n * or Google servers and then activates the subscription or credits — no\n * RevenueCat or third-party account required.\n */\n\nimport { useState, useCallback } from 'react';\nimport { Capacitor } from '@capacitor/core';\nimport {\n getNativeProducts,\n purchaseNativeProduct,\n acknowledgeNativePurchase,\n restoreNativePurchases,\n isNativeIAPAvailable,\n} from '../../../lib/native-purchases';\nimport type { Product, Transaction, PURCHASE_TYPE } from '@capgo/native-purchases';\nimport type { CheckoutState, PurchaseType } from '../types';\nimport { useBilling } from '../../../providers/BillingProvider';\n\nexport type { Product, Transaction };\n\ninterface UseNativeCheckoutOptions {\n billingAccountId?: string;\n purchaseType?: PurchaseType;\n /** Store product identifier — passed to getProducts to load the specific product. */\n productId?: string;\n}\n\ninterface UseNativeCheckoutResult {\n isAvailable: boolean;\n products: Product[];\n checkoutState: CheckoutState;\n error: string | null;\n isLoading: boolean;\n loadProducts: (productIdentifiers?: string[]) => Promise<void>;\n purchaseProduct: (product: Product) => Promise<{ success: boolean }>;\n restorePurchases: () => Promise<{ success: boolean }>;\n}\n\nexport function useNativeCheckout({\n billingAccountId,\n purchaseType,\n productId,\n}: UseNativeCheckoutOptions = {}): UseNativeCheckoutResult {\n const { apiGatewayUrl, authToken } = useBilling();\n const [products, setProducts] = useState<Product[]>([]);\n const [checkoutState, setCheckoutState] = useState<CheckoutState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n const loadProducts = useCallback(\n async (productIdentifiers?: string[]) => {\n if (!isNativeIAPAvailable()) return;\n const ids = productIdentifiers ?? (productId ? [productId] : []);\n if (ids.length === 0) return;\n setIsLoading(true);\n setError(null);\n try {\n const result = await getNativeProducts(ids);\n setProducts(result);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load products');\n } finally {\n setIsLoading(false);\n }\n },\n [productId]\n );\n\n const purchaseProduct = useCallback(\n async (product: Product): Promise<{ success: boolean }> => {\n if (!billingAccountId) {\n setError('No billing account selected');\n return { success: false };\n }\n\n setCheckoutState('processing');\n setError(null);\n\n let transaction: Transaction | undefined;\n try {\n // Trigger native payment sheet — deferred ack so we validate server-side first.\n // planIdentifier is required for Android subscriptions (base plan ID from Play Console).\n // isConsumable allows credits to be re-purchased on Android.\n const isCredits = purchaseType === 'CREDITS';\n // appAccountToken: UUID passed to Apple/Google so they echo it in server notifications,\n // allowing us to link renewals/cancellations to this billing account.\n // iOS requires UUID format; Android (obfuscatedExternalAccountId) accepts any string ≤ 64 chars.\n const appAccountToken = crypto.randomUUID();\n transaction = await purchaseNativeProduct(product.identifier, {\n autoAcknowledgePurchases: false,\n isConsumable: isCredits,\n planIdentifier: !isCredits ? (product.planIdentifier ?? undefined) : undefined,\n appAccountToken,\n });\n\n // Send receipt / token to backend for server-side validation and entitlement activation
|
|
1
|
+
{"version":3,"file":"useNativeCheckout.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useNativeCheckout.ts"],"sourcesContent":["/**\n * Native IAP checkout hook — Apple StoreKit / Google Play Billing.\n *\n * Manages the full native purchase lifecycle:\n * 1. Load product info from the App Store / Play Store\n * 2. Trigger the native payment sheet\n * 3. Send the receipt / JWS / purchase token to the backend for validation\n * 4. Acknowledge the transaction after the backend confirms it\n *\n * The backend endpoint POST /rest/billing/iap/validate validates with Apple\n * or Google servers and then activates the subscription or credits — no\n * RevenueCat or third-party account required.\n */\n\nimport { useState, useCallback } from 'react';\nimport { Capacitor } from '@capacitor/core';\nimport {\n getNativeProducts,\n purchaseNativeProduct,\n acknowledgeNativePurchase,\n restoreNativePurchases,\n isNativeIAPAvailable,\n} from '../../../lib/native-purchases';\nimport type { Product, Transaction, PURCHASE_TYPE } from '@capgo/native-purchases';\nimport type { CheckoutState, PurchaseType } from '../types';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { normalizeGlobalGatewayBaseUrl } from '../../../shared/utils/gatewayGraphql';\n\nexport type { Product, Transaction };\n\ninterface UseNativeCheckoutOptions {\n billingAccountId?: string;\n purchaseType?: PurchaseType;\n /** Store product identifier — passed to getProducts to load the specific product. */\n productId?: string;\n}\n\ninterface UseNativeCheckoutResult {\n isAvailable: boolean;\n products: Product[];\n checkoutState: CheckoutState;\n error: string | null;\n isLoading: boolean;\n loadProducts: (productIdentifiers?: string[]) => Promise<void>;\n purchaseProduct: (product: Product) => Promise<{ success: boolean }>;\n restorePurchases: () => Promise<{ success: boolean }>;\n}\n\nexport function useNativeCheckout({\n billingAccountId,\n purchaseType,\n productId,\n}: UseNativeCheckoutOptions = {}): UseNativeCheckoutResult {\n const { apiGatewayUrl, authToken } = useBilling();\n const [products, setProducts] = useState<Product[]>([]);\n const [checkoutState, setCheckoutState] = useState<CheckoutState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n const loadProducts = useCallback(\n async (productIdentifiers?: string[]) => {\n if (!isNativeIAPAvailable()) return;\n const ids = productIdentifiers ?? (productId ? [productId] : []);\n if (ids.length === 0) return;\n setIsLoading(true);\n setError(null);\n try {\n const result = await getNativeProducts(ids);\n setProducts(result);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load products');\n } finally {\n setIsLoading(false);\n }\n },\n [productId]\n );\n\n const purchaseProduct = useCallback(\n async (product: Product): Promise<{ success: boolean }> => {\n if (!billingAccountId) {\n setError('No billing account selected');\n return { success: false };\n }\n\n setCheckoutState('processing');\n setError(null);\n\n let transaction: Transaction | undefined;\n try {\n // Trigger native payment sheet — deferred ack so we validate server-side first.\n // planIdentifier is required for Android subscriptions (base plan ID from Play Console).\n // isConsumable allows credits to be re-purchased on Android.\n const isCredits = purchaseType === 'CREDITS';\n // appAccountToken: UUID passed to Apple/Google so they echo it in server notifications,\n // allowing us to link renewals/cancellations to this billing account.\n // iOS requires UUID format; Android (obfuscatedExternalAccountId) accepts any string ≤ 64 chars.\n const appAccountToken = crypto.randomUUID();\n transaction = await purchaseNativeProduct(product.identifier, {\n autoAcknowledgePurchases: false,\n isConsumable: isCredits,\n planIdentifier: !isCredits ? (product.planIdentifier ?? undefined) : undefined,\n appAccountToken,\n });\n\n // Send receipt / token to backend for server-side validation and entitlement activation.\n // apiGatewayUrl is the GraphQL endpoint (e.g. https://graphql.burdenoff.com/global/graphql);\n // strip the /global/graphql suffix to get the base URL, then use the REST path.\n const gatewayBase = normalizeGlobalGatewayBaseUrl(apiGatewayUrl) ?? '';\n const iapValidateUrl = `${gatewayBase}/global/rest/billing/iap/validate`;\n const platform = Capacitor.getPlatform() as 'ios' | 'android';\n // nativePurchaseType: credits are consumable (INAPP); plans and addons are subscriptions (SUBS).\n const nativePurchaseType = isCredits ? 'INAPP' : 'SUBS';\n const response = await fetch(iapValidateUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),\n },\n body: JSON.stringify({\n platform,\n transactionId: transaction.transactionId,\n productIdentifier: transaction.productIdentifier,\n receipt: transaction.receipt,\n jwsRepresentation: transaction.jwsRepresentation,\n purchaseToken: transaction.purchaseToken,\n appAccountToken,\n billingAccountId,\n purchaseType,\n nativePurchaseType,\n price: product.price,\n currency: product.currencyCode,\n }),\n });\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as { message?: string };\n throw new Error(body.message ?? `Validation failed (${response.status})`);\n }\n\n // Acknowledge only after backend confirms the purchase is valid\n await acknowledgeNativePurchase(transaction.purchaseToken ?? transaction.transactionId);\n\n setCheckoutState('success');\n return { success: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Purchase failed';\n // User cancellation is not an error worth surfacing as \"error\" state\n if (\n message.toLowerCase().includes('cancel') ||\n message.toLowerCase().includes('dismiss') ||\n message.toLowerCase().includes('abort')\n ) {\n setCheckoutState('idle');\n } else {\n setError(message);\n setCheckoutState('error');\n }\n return { success: false };\n }\n },\n [billingAccountId, apiGatewayUrl, authToken, purchaseType]\n );\n\n const restorePurchases = useCallback(async (): Promise<{ success: boolean }> => {\n setIsLoading(true);\n setError(null);\n try {\n await restoreNativePurchases();\n return { success: true };\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Restore failed');\n return { success: false };\n } finally {\n setIsLoading(false);\n }\n }, []);\n\n return {\n isAvailable: isNativeIAPAvailable(),\n products,\n checkoutState,\n error,\n isLoading,\n loadProducts,\n purchaseProduct,\n restorePurchases,\n };\n}\n"],"mappings":";;;;;;AAgDA,SAAgB,EAAkB,EAChC,qBACA,iBACA,iBAC4B,EAAE,EAA2B;CACzD,IAAM,EAAE,kBAAe,iBAAc,GAAY,EAC3C,CAAC,GAAU,KAAe,EAAoB,EAAE,CAAC,EACjD,CAAC,GAAe,KAAoB,EAAwB,OAAO,EACnE,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAW,KAAgB,EAAS,GAAM,EAE3C,IAAe,EACnB,OAAO,MAAkC;AACvC,MAAI,CAAC,GAAsB,CAAE;EAC7B,IAAM,IAAM,MAAuB,IAAY,CAAC,EAAU,GAAG,EAAE;AAC3D,QAAI,WAAW,GAEnB;GADA,EAAa,GAAK,EAClB,EAAS,KAAK;AACd,OAAI;AAEF,MADe,MAAM,EAAkB,EAAI,CACxB;YACZ,GAAK;AACZ,MAAS,aAAe,QAAQ,EAAI,UAAU,0BAA0B;aAChE;AACR,MAAa,GAAM;;;IAGvB,CAAC,EAAU,CACZ,EAEK,IAAkB,EACtB,OAAO,MAAoD;AACzD,MAAI,CAAC,EAEH,QADA,EAAS,8BAA8B,EAChC,EAAE,SAAS,IAAO;AAI3B,EADA,EAAiB,aAAa,EAC9B,EAAS,KAAK;EAEd,IAAI;AACJ,MAAI;GAIF,IAAM,IAAY,MAAiB,WAI7B,IAAkB,OAAO,YAAY;AAC3C,OAAc,MAAM,EAAsB,EAAQ,YAAY;IAC5D,0BAA0B;IAC1B,cAAc;IACd,gBAAiB,IAAoD,KAAA,IAAvC,EAAQ,kBAAkB,KAAA;IACxD;IACD,CAAC;GAMF,IAAM,IAAiB,GADH,EAA8B,EAAc,IAAI,GAC9B,oCAChC,IAAW,EAAU,aAAa,EAElC,IAAqB,IAAY,UAAU,QAC3C,IAAW,MAAM,MAAM,GAAgB;IAC3C,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,GAAI,IAAY,EAAE,eAAe,UAAU,KAAa,GAAG,EAAE;KAC9D;IACD,MAAM,KAAK,UAAU;KACnB;KACA,eAAe,EAAY;KAC3B,mBAAmB,EAAY;KAC/B,SAAS,EAAY;KACrB,mBAAmB,EAAY;KAC/B,eAAe,EAAY;KAC3B;KACA;KACA;KACA;KACA,OAAO,EAAQ;KACf,UAAU,EAAQ;KACnB,CAAC;IACH,CAAC;AAEF,OAAI,CAAC,EAAS,IAAI;IAChB,IAAM,IAAQ,MAAM,EAAS,MAAM,CAAC,aAAa,EAAE,EAAE;AACrD,UAAU,MAAM,EAAK,WAAW,sBAAsB,EAAS,OAAO,GAAG;;AAO3E,UAHA,MAAM,EAA0B,EAAY,iBAAiB,EAAY,cAAc,EAEvF,EAAiB,UAAU,EACpB,EAAE,SAAS,IAAM;WACjB,GAAK;GACZ,IAAM,IAAU,aAAe,QAAQ,EAAI,UAAU;AAYrD,UATE,EAAQ,aAAa,CAAC,SAAS,SAAS,IACxC,EAAQ,aAAa,CAAC,SAAS,UAAU,IACzC,EAAQ,aAAa,CAAC,SAAS,QAAQ,GAEvC,EAAiB,OAAO,IAExB,EAAS,EAAQ,EACjB,EAAiB,QAAQ,GAEpB,EAAE,SAAS,IAAO;;IAG7B;EAAC;EAAkB;EAAe;EAAW;EAAa,CAC3D,EAEK,IAAmB,EAAY,YAA2C;AAE9E,EADA,EAAa,GAAK,EAClB,EAAS,KAAK;AACd,MAAI;AAEF,UADA,MAAM,GAAwB,EACvB,EAAE,SAAS,IAAM;WACjB,GAAK;AAEZ,UADA,EAAS,aAAe,QAAQ,EAAI,UAAU,iBAAiB,EACxD,EAAE,SAAS,IAAO;YACjB;AACR,KAAa,GAAM;;IAEpB,EAAE,CAAC;AAEN,QAAO;EACL,aAAa,GAAsB;EACnC;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
|