@crediball/react 0.5.0 → 0.6.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.
@@ -41,13 +41,18 @@ export interface CrediballProviderProps {
41
41
  */
42
42
  onSelectPackage?: (pkg: PackageOption) => void | Promise<void>;
43
43
  /**
44
- * Called when the user picks a subscription plan. Unlike one-time top-ups,
45
- * subscriptions have NO built-in checkout (they need recurring billing your
46
- * backend sets up), so the paywall only shows subscription plans when this is
47
- * provided — wire it to `meter.subscribe({ userId, planId })` server-side.
44
+ * Optional override for subscription plans. If omitted, the paywall starts a
45
+ * recurring Stripe Checkout itself (connect payouts in the dashboard) and Stripe
46
+ * grants the plan's credits each period. Provide this only to run your own flow
47
+ * (e.g. `meter.subscribe({ userId, planId })` server-side).
48
48
  */
49
49
  onSelectPlan?: (plan: SubscriptionPlanOption) => void | Promise<void>;
50
- /** Called when the user cancels their subscription. Call `meter.cancelSubscription({ userId, subscriptionId })` server-side. */
50
+ /**
51
+ * Called when the user cancels their subscription. Cancellation is server-side
52
+ * only (it needs your secret key), so the paywall shows a Cancel button on the
53
+ * active-subscription banner ONLY when this is provided — wire it to
54
+ * `meter.cancelSubscription({ userId, subscriptionId })` on your backend.
55
+ */
51
56
  onCancelSubscription?: (subscriptionId: string) => void | Promise<void>;
52
57
  /**
53
58
  * Auto-detect insufficient-credit responses from any fetch() call and open the
@@ -158,14 +158,35 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
158
158
  setOpen(false);
159
159
  }
160
160
  async function handleSelectPlan(plan) {
161
- try {
162
- await onSelectPlan?.(plan);
163
- notifyBalanceChanged();
164
- await client?.refresh();
161
+ // Host-supplied subscription flow wins.
162
+ if (onSelectPlan) {
163
+ try {
164
+ await onSelectPlan(plan);
165
+ notifyBalanceChanged();
166
+ await client?.refresh();
167
+ }
168
+ finally {
169
+ setOpen(false);
170
+ }
171
+ return;
165
172
  }
166
- finally {
167
- setOpen(false);
173
+ // Otherwise start the recurring Stripe Checkout ourselves. Leave the modal
174
+ // open on failure so the error is visible.
175
+ if (client) {
176
+ try {
177
+ const { url } = await client.createSubscriptionCheckout({
178
+ planId: plan.id,
179
+ returnPath: currentReturnPath(),
180
+ });
181
+ if (typeof window !== "undefined")
182
+ window.location.assign(url);
183
+ }
184
+ catch (err) {
185
+ console.error("[crediball] subscription checkout failed:", err);
186
+ }
187
+ return;
168
188
  }
189
+ setOpen(false);
169
190
  }
170
191
  async function handleCancelSubscription(subscriptionId) {
171
192
  await onCancelSubscription?.(subscriptionId);
@@ -179,11 +200,7 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
179
200
  const effectiveAllowCustom = allowCustom ?? customCfg?.enabled ?? false;
180
201
  const effectiveCustomMin = customMin ?? customCfg?.minEur;
181
202
  const effectiveCustomMax = customMax ?? customCfg?.maxEur ?? undefined;
182
- return (_jsxs(CrediballContext.Provider, { value: ctx, children: [children, _jsx(PaywallModal, { open: open, packages: pkgs?.packages, onSelectPackage: handleSelectPackage,
183
- // Subscriptions can't be fulfilled by the built-in checkout (that path is
184
- // one-time top-ups only), so only OFFER them when the host wired an
185
- // onSelectPlan to fulfill them — otherwise they'd be dead buttons.
186
- subscriptionPlans: onSelectPlan ? pkgs?.subscriptionPlans : undefined, onSelectPlan: handleSelectPlan, activeSubscription: pkgs?.activeSubscription, onCancelSubscription: handleCancelSubscription, amounts: amounts, amountPrefix: `Add ${currencySymbol}`, onAdd: handleAdd, onClose: hidePaywall, allowCustom: effectiveAllowCustom, customMin: effectiveCustomMin, customMax: effectiveCustomMax, currencySymbol: currencySymbol, balance: snapshot.balance ?? undefined, balanceRate: pkgs?.customTopup.enabled ? pkgs.customTopup.rate : undefined, currency: pkgs?.currency, usage: snapshot.usage?.usedCredits ?? undefined, title: title, description: description, accentColor: accentColor })] }));
203
+ return (_jsxs(CrediballContext.Provider, { value: ctx, children: [children, _jsx(PaywallModal, { open: open, packages: pkgs?.packages, onSelectPackage: handleSelectPackage, subscriptionPlans: pkgs?.subscriptionPlans, onSelectPlan: handleSelectPlan, activeSubscription: pkgs?.activeSubscription, onCancelSubscription: handleCancelSubscription, amounts: amounts, amountPrefix: `Add ${currencySymbol}`, onAdd: handleAdd, onClose: hidePaywall, allowCustom: effectiveAllowCustom, customMin: effectiveCustomMin, customMax: effectiveCustomMax, currencySymbol: currencySymbol, balance: snapshot.balance ?? undefined, balanceRate: pkgs?.customTopup.enabled ? pkgs.customTopup.rate : undefined, currency: pkgs?.currency, usage: snapshot.usage?.usedCredits ?? undefined, title: title, description: description, accentColor: accentColor })] }));
187
204
  }
188
205
  /**
189
206
  * Access the paywall + live data from anywhere inside <CrediballProvider>.
@@ -130,7 +130,7 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
130
130
  borderRadius: theme.radiusCard,
131
131
  background: "#f5f5f7",
132
132
  border: `1px solid ${theme.hairline}`,
133
- }, children: [_jsx("div", { style: { fontSize: 13, fontWeight: 600, color: theme.ink }, children: cancelDone ? "Subscription canceled" : `${activeSubscription.planName} · ${activeSubscription.planPeriod}` }), !cancelDone && (_jsx("button", { onClick: handleCancelSubscription, disabled: cancelPending, style: {
133
+ }, children: [_jsx("div", { style: { fontSize: 13, fontWeight: 600, color: theme.ink }, children: cancelDone ? "Subscription canceled" : `${activeSubscription.planName} · ${activeSubscription.planPeriod}` }), onCancelSubscription && !cancelDone && (_jsx("button", { onClick: handleCancelSubscription, disabled: cancelPending, style: {
134
134
  marginTop: 8,
135
135
  appearance: "none",
136
136
  border: "none",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crediball/react",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Drop-in React components for showing Crediball credits inside your AI app.",
5
5
  "license": "MIT",
6
6
  "author": "Filippo Rezzadore <filipporezzadore@gmail.com>",
@@ -51,7 +51,7 @@
51
51
  "react": ">=18"
52
52
  },
53
53
  "dependencies": {
54
- "@crediball/core": "^0.1.1"
54
+ "@crediball/core": "^0.2.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/react": "^18.3.11",