@donotdev/billing 0.0.2 → 0.0.4

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.
@@ -1,4 +1,4 @@
1
- import type { AuthUser, BillingAPI } from '@donotdev/core';
1
+ import type { AuthUser, BillingAPI, FeatureStatus } from '@donotdev/core';
2
2
  /**
3
3
  * Auth state passed from parent component.
4
4
  * Required for billing to work - removes race condition from dynamic import.
@@ -9,8 +9,7 @@ import type { AuthUser, BillingAPI } from '@donotdev/core';
9
9
  */
10
10
  export interface BillingAuthState {
11
11
  user: AuthUser | null;
12
- initialized: boolean;
13
- authStateChecked: boolean;
12
+ status: FeatureStatus;
14
13
  }
15
14
  /**
16
15
  * React hook for Stripe billing with property-based access
@@ -19,18 +18,17 @@ export interface BillingAuthState {
19
18
  * Uses local state for loading/error (no store needed). Lazy loads Firebase SDK only when methods are called.
20
19
  *
21
20
  * **IMPORTANT:** Auth state must be passed from parent component to avoid race conditions.
22
- * The hook returns `isAvailable: false` until auth is fully initialized (`authStateChecked: true`).
21
+ * The hook returns `isAvailable: false` until auth status is `ready`.
23
22
  *
24
23
  * **Property-based access (matches useAuth pattern):**
25
24
  * ```typescript
26
25
  * // ✅ First, get auth state from useAuth
27
26
  * const user = useAuth('user');
28
- * const initialized = useAuth('initialized');
29
- * const authStateChecked = useAuth('authStateChecked');
30
- * const authState = { user, initialized, authStateChecked };
27
+ * const status = useAuth('status'); // 'initializing' | 'ready' | 'degraded' | 'error'
28
+ * const authState = { user, status };
31
29
  *
32
30
  * // ✅ State (reactive, re-renders on change)
33
- * const loading = useStripeBilling('loading', authState);
31
+ * const billingStatus = useStripeBilling('status', authState);
34
32
  * const error = useStripeBilling('error', authState);
35
33
  * const isAvailable = useStripeBilling('isAvailable', authState);
36
34
  *
@@ -53,24 +51,23 @@ export interface BillingAuthState {
53
51
  *
54
52
  * @template K - The property key from BillingAPI
55
53
  * @param key - Property name to access (loading, error, checkout, cancelSubscription, changePlan, refreshStatus, openCustomerPortal, clearError, isAvailable)
56
- * @param authState - Auth state from useAuth hook (user, initialized, authStateChecked)
54
+ * @param authState - Auth state from useAuth hook (user, status)
57
55
  * @returns The value of the specified property
58
56
  *
59
57
  * @example
60
58
  * ```typescript
61
59
  * // Get auth state first (required)
62
60
  * const user = useAuth('user');
63
- * const initialized = useAuth('initialized');
64
- * const authStateChecked = useAuth('authStateChecked');
65
- * const authState = { user, initialized, authStateChecked };
61
+ * const status = useAuth('status');
62
+ * const authState = { user, status };
66
63
  *
67
64
  * // Check if billing is available before showing UI
68
65
  * const isAvailable = useStripeBilling('isAvailable', authState);
69
66
  * if (!isAvailable) return null; // Wait for auth
70
67
  *
71
- * // Get loading state (re-renders when loading changes)
72
- * const loading = useStripeBilling('loading', authState);
73
- * if (loading) return <Spinner />;
68
+ * // Get billing status (re-renders when status changes)
69
+ * const billingStatus = useStripeBilling('status', authState);
70
+ * if (billingStatus === 'initializing') return <Spinner />;
74
71
  *
75
72
  * // Get checkout method (stable, never re-renders)
76
73
  * const checkout = useStripeBilling('checkout', authState);
@@ -1 +1 @@
1
- {"version":3,"file":"useStripeBilling.d.ts","sourceRoot":"","sources":["../src/useStripeBilling.ts"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EACV,QAAQ,EACR,UAAU,EAIX,MAAM,gBAAgB,CAAC;AAkBxB;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,UAAU,EACzD,GAAG,EAAE,CAAC,EACN,SAAS,CAAC,EAAE,gBAAgB,GAC3B,UAAU,CAAC,CAAC,CAAC,CA0Vf"}
1
+ {"version":3,"file":"useStripeBilling.d.ts","sourceRoot":"","sources":["../src/useStripeBilling.ts"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EACV,QAAQ,EACR,UAAU,EAIV,aAAa,EACd,MAAM,gBAAgB,CAAC;AAqBxB;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,UAAU,EACzD,GAAG,EAAE,CAAC,EACN,SAAS,CAAC,EAAE,gBAAgB,GAC3B,UAAU,CAAC,CAAC,CAAC,CA+Vf"}
@@ -1 +1 @@
1
- "use client";import{useState as y,useCallback as c}from"react";import{DEGRADED_BILLING_API as F,isClient as S,handleError as l,getPlatformEnvVar as w,FRAMEWORK_FEATURES as T,redirectToExternalUrlWithErrorHandling as x,useFeatureConsent as L}from"@donotdev/core";let p=!1;function D(n,h){const t=h?.user??null,U=h?.initialized&&h?.authStateChecked,m=L(T.BILLING),[C,i]=y(!1),[N,s]=y(null),P=c(async r=>{if(!S()||!m)throw new Error("Billing not available in SSR or consent required");if(!t){const e=l(new Error("User must be authenticated"),{userMessage:"Please sign in to continue with checkout.",severity:"warning"});throw s(e.message),e}i(!0),s(null);try{const{getFirebaseFunctions:e,httpsCallable:a}=await import("@donotdev/firebase"),o=w("FIREBASE_FUNCTIONS_REGION")||"europe-west1",u=await e(o),d=a(u,"createCheckoutSession"),I=t.providerData?.find(M=>M.providerId==="github.com"),E=I?.displayName,f=I?.uid,B={priceId:r.priceId,userId:t.id,userEmail:t.email||"",customerEmail:t.email||"",metadata:{firebaseUid:t.id,...E&&{githubUsername:E},...f&&{githubUid:f},...r.metadata},successUrl:r.successUrl||`${typeof window<"u"?window.location.origin:""}/billing/success`,cancelUrl:r.cancelUrl||(typeof window<"u"?window.location.href:""),allowPromotionCodes:r.allowPromotionCodes??!0,mode:r.mode},k=await d(B),{sessionId:G,sessionUrl:b}=k.data;if(!b)throw new Error("No checkout URL received from server");return p=!1,{sessionId:G,sessionUrl:b,success:!0,error:void 0}}catch(e){if(p){const a=e instanceof Error?e:new Error("Checkout failed");throw s(a.message),a}else{const a=l(e,{userMessage:"Unable to start checkout. Please try again or contact support if the problem persists.",context:{priceId:r.priceId,mode:r.mode},severity:"error"});throw s(a.message),p=!0,a}}finally{i(!1)}},[t,m]),R=c(async()=>{if(!t)throw new Error("User must be authenticated");i(!0),s(null);try{const{getFirebaseFunctions:r,httpsCallable:e}=await import("@donotdev/firebase"),a=w("FIREBASE_FUNCTIONS_REGION")||"europe-west1",o=await r(a);return(await e(o,"cancelSubscription")({userId:t.id})).data}catch(r){const e=l(r,{userMessage:"Failed to cancel subscription. Please try again or contact support.",context:{operation:"cancelSubscription",userId:t.id},severity:"error"});throw s(e.message),e}finally{i(!1)}},[t]),v=c(async(r,e)=>{if(!t)throw new Error("User must be authenticated");i(!0),s(null);try{const{getFirebaseFunctions:a,httpsCallable:o}=await import("@donotdev/firebase"),u=w("FIREBASE_FUNCTIONS_REGION")||"europe-west1",d=await a(u);return(await o(d,"changePlan")({userId:t.id,newPriceId:r,billingConfigKey:e})).data}catch(a){const o=l(a,{userMessage:"Failed to change plan. Please try again or contact support.",context:{operation:"changePlan",userId:t.id,newPriceId:r,billingConfigKey:e},severity:"error"});throw s(o.message),o}finally{i(!1)}},[t]),_=c(async()=>{if(!t)throw new Error("User must be authenticated");i(!0),s(null);try{const{getFirebaseFunctions:r,httpsCallable:e}=await import("@donotdev/firebase"),a=w("FIREBASE_FUNCTIONS_REGION")||"europe-west1",o=await r(a);await e(o,"refreshSubscriptionStatus")({userId:t.id})}catch(r){const e=l(r,{userMessage:"Failed to refresh subscription status. Please try again.",context:{operation:"refreshStatus",userId:t.id},severity:"error"});throw s(e.message),e}finally{i(!1)}},[t]),O=c(async r=>{if(!t)throw new Error("User must be authenticated");i(!0),s(null);try{const{getFirebaseFunctions:e,httpsCallable:a}=await import("@donotdev/firebase"),o=w("FIREBASE_FUNCTIONS_REGION")||"europe-west1",u=await e(o),d=await a(u,"createCustomerPortal")({userId:t.id,returnUrl:r||(typeof window<"u"?window.location.href:"")});await x(d.data.url,{},"Failed to redirect to customer portal")}catch(e){const a=l(e,{userMessage:"Failed to open customer portal. Please try again or contact support.",context:{operation:"openCustomerPortal",userId:t.id},severity:"error"});throw s(a.message),a}finally{i(!1)}},[t]),A=c(()=>{s(null),p=!1},[]),g={checkout:P,cancelSubscription:R,changePlan:v,refreshStatus:_,openCustomerPortal:O,clearError:A};return!S()||!m?F[n]:n==="isAvailable"?!!U:n==="loading"?C:n==="error"?N:n in g?g[n]:F[n]}export{D as useStripeBilling};
1
+ "use client";import{useState as U,useCallback as c}from"react";import{DEGRADED_BILLING_API as y,FEATURE_STATUS as u,isClient as C,handleError as l,getPlatformEnvVar as p,FRAMEWORK_FEATURES as k,redirectToExternalUrlWithErrorHandling as x,useFeatureConsent as M}from"@donotdev/core";import{getFirebaseFunctions as f,httpsCallable as h}from"@donotdev/firebase";let g=!1;function Z(a,E){const t=E?.user??null,w=(E?.status??u.DEGRADED)===u.READY,m=M(k.BILLING),[q,n]=U(!1),[F,s]=U(null),R=c(async r=>{if(!C()||!m)throw new Error("Billing not available in SSR or consent required");if(!t){const e=l(new Error("User must be authenticated"),{userMessage:"Please sign in to continue with checkout.",severity:"warning"});throw s(e.message),e}n(!0),s(null);try{const e=p("FIREBASE_FUNCTIONS_REGION")||"europe-west1",o=await f(e),i=h(o,"createCheckoutSession"),d=t.providerData?.find(L=>L.providerId==="github.com"),I=d?.displayName,N=d?.uid,T={priceId:r.priceId,userId:t.id,userEmail:t.email||"",customerEmail:t.email||"",metadata:{firebaseUid:t.id,...I&&{githubUsername:I},...N&&{githubUid:N},...r.metadata},successUrl:r.successUrl||`${typeof window<"u"?window.location.origin:""}/billing/success`,cancelUrl:r.cancelUrl||(typeof window<"u"?window.location.href:""),allowPromotionCodes:r.allowPromotionCodes??!0,mode:r.mode},G=await i(T),{sessionId:D,sessionUrl:b}=G.data;if(!b)throw new Error("No checkout URL received from server");return g=!1,{sessionId:D,sessionUrl:b,success:!0,error:void 0}}catch(e){if(g){const o=e instanceof Error?e:new Error("Checkout failed");throw s(o.message),o}else{const o=l(e,{userMessage:"Unable to start checkout. Please try again or contact support if the problem persists.",context:{priceId:r.priceId,mode:r.mode},severity:"error"});throw s(o.message),g=!0,o}}finally{n(!1)}},[t,m]),A=c(async()=>{if(!t)throw new Error("User must be authenticated");n(!0),s(null);try{const r=p("FIREBASE_FUNCTIONS_REGION")||"europe-west1",e=await f(r);return(await h(e,"cancelSubscription")({userId:t.id})).data}catch(r){const e=l(r,{userMessage:"Failed to cancel subscription. Please try again or contact support.",context:{operation:"cancelSubscription",userId:t.id},severity:"error"});throw s(e.message),e}finally{n(!1)}},[t]),P=c(async(r,e)=>{if(!t)throw new Error("User must be authenticated");n(!0),s(null);try{const o=p("FIREBASE_FUNCTIONS_REGION")||"europe-west1",i=await f(o);return(await h(i,"changePlan")({userId:t.id,newPriceId:r,billingConfigKey:e})).data}catch(o){const i=l(o,{userMessage:"Failed to change plan. Please try again or contact support.",context:{operation:"changePlan",userId:t.id,newPriceId:r,billingConfigKey:e},severity:"error"});throw s(i.message),i}finally{n(!1)}},[t]),v=c(async()=>{if(!t)throw new Error("User must be authenticated");n(!0),s(null);try{const r=p("FIREBASE_FUNCTIONS_REGION")||"europe-west1",e=await f(r);await h(e,"refreshSubscriptionStatus")({userId:t.id})}catch(r){const e=l(r,{userMessage:"Failed to refresh subscription status. Please try again.",context:{operation:"refreshStatus",userId:t.id},severity:"error"});throw s(e.message),e}finally{n(!1)}},[t]),_=c(async r=>{if(!t)throw new Error("User must be authenticated");n(!0),s(null);try{const e=p("FIREBASE_FUNCTIONS_REGION")||"europe-west1",o=await f(e),d=await h(o,"createCustomerPortal")({userId:t.id,returnUrl:r||(typeof window<"u"?window.location.href:"")});await x(d.data.url,{},"Failed to redirect to customer portal")}catch(e){const o=l(e,{userMessage:"Failed to open customer portal. Please try again or contact support.",context:{operation:"openCustomerPortal",userId:t.id},severity:"error"});throw s(o.message),o}finally{n(!1)}},[t]),B=c(()=>{s(null),g=!1},[]),S={checkout:R,cancelSubscription:A,changePlan:P,refreshStatus:v,openCustomerPortal:_,clearError:B};if(!C()||!m)return y[a];const O=w?u.READY:u.INITIALIZING;return a==="status"?O:a==="isAvailable"?!!w:a==="status"?w?u.READY:u.INITIALIZING:a==="error"?F:a in S?S[a]:y[a]}export{Z as useStripeBilling};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donotdev/billing",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -33,10 +33,10 @@
33
33
  },
34
34
  "dependencies": {},
35
35
  "peerDependencies": {
36
- "@donotdev/auth": "0.0.2",
37
- "@donotdev/components": "0.0.2",
38
- "@donotdev/core": "0.0.2",
39
- "@donotdev/firebase": "0.0.2",
36
+ "@donotdev/auth": "0.0.4",
37
+ "@donotdev/components": "0.0.4",
38
+ "@donotdev/core": "0.0.4",
39
+ "@donotdev/firebase": "0.0.4",
40
40
  "lucide-react": "^0.562.0",
41
41
  "react": "^19.2.3",
42
42
  "react-dom": "^19.2.3",