Updated subscriptions
This commit is contained in:
+6
-3
@@ -29,9 +29,12 @@ STRIPE_PRICE_HOUSEHOLD_CONURE_YEARLY=
|
||||
STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK=
|
||||
STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_MONTHLY=
|
||||
STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_YEARLY=
|
||||
STRIPE_PRICE_HOUSEHOLD_MACAW=
|
||||
STRIPE_PRICE_HOUSEHOLD_MACAW_MONTHLY=
|
||||
STRIPE_PRICE_HOUSEHOLD_MACAW_YEARLY=
|
||||
STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY=
|
||||
STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_MONTHLY=
|
||||
STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_YEARLY=
|
||||
STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW=
|
||||
STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_MONTHLY=
|
||||
STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_YEARLY=
|
||||
STRIPE_CHECKOUT_SUCCESS_URL=http://localhost:3000/?billing=success
|
||||
STRIPE_CHECKOUT_CANCEL_URL=http://localhost:3000/?billing=cancelled
|
||||
STRIPE_PORTAL_RETURN_URL=http://localhost:3000/?billing=portal
|
||||
|
||||
@@ -203,8 +203,10 @@ Set these when the Stripe Checkout, Customer Portal, and webhook flow is enabled
|
||||
- `STRIPE_PRICE_HOUSEHOLD_CONURE_YEARLY`
|
||||
- `STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_MONTHLY` or `STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK`
|
||||
- `STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_YEARLY`
|
||||
- `STRIPE_PRICE_HOUSEHOLD_MACAW_MONTHLY` or `STRIPE_PRICE_HOUSEHOLD_MACAW`
|
||||
- `STRIPE_PRICE_HOUSEHOLD_MACAW_YEARLY`
|
||||
- `STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_MONTHLY` or `STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY`
|
||||
- `STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_YEARLY`
|
||||
- `STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_MONTHLY` or `STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW`
|
||||
- `STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_YEARLY`
|
||||
- `STRIPE_CHECKOUT_SUCCESS_URL`
|
||||
- `STRIPE_CHECKOUT_CANCEL_URL`
|
||||
- `STRIPE_PORTAL_RETURN_URL`
|
||||
@@ -221,7 +223,7 @@ Recommended defaults:
|
||||
- Enable the proration behavior you want in the Customer Portal configuration. FlockPal treats Stripe as the source of truth for upgrade timing and proration outcomes.
|
||||
- Point the Stripe webhook endpoint at `https://your-backend-host/api/billing/stripe/webhook`.
|
||||
- Subscribe the webhook endpoint to at least `checkout.session.completed`, `customer.subscription.created`, `customer.subscription.updated`, and `customer.subscription.deleted`.
|
||||
- Use one Stripe Price per plan and billing interval. FlockPal maps Stripe price IDs back to `household_basic`, `household_plus`, and `household_macaw`.
|
||||
- Use one Stripe Price per plan and billing interval. FlockPal maps Stripe price IDs back to `household_basic`, `household_plus`, `household_macaw`, and `household_hyacinth_macaw`.
|
||||
- After Stripe redirects back to the app, FlockPal now performs a direct billing sync against Stripe and then refreshes the active session. Webhooks are still required so asynchronous subscription changes stay in sync later.
|
||||
|
||||
For local development with the Stripe CLI:
|
||||
|
||||
+26
-7
@@ -199,7 +199,7 @@ const switchWorkspaceSchema = z.object({
|
||||
|
||||
const workspaceTypeSchema = z.enum(['standard', 'rescue']);
|
||||
const workspaceRoleSchema = z.enum(['owner', 'assistant', 'caregiver', 'viewer']);
|
||||
const billingPlanSchema = z.enum(['household_basic', 'household_plus', 'household_macaw']);
|
||||
const billingPlanSchema = z.enum(['household_basic', 'household_plus', 'household_macaw', 'household_hyacinth_macaw']);
|
||||
const billingIntervalSchema = z.enum(['monthly', 'yearly']);
|
||||
const integrationTokenScopeSchema = z.enum(['read_only', 'read_write']);
|
||||
const birdGenderSchema = z.enum(['unknown', 'male', 'female']);
|
||||
@@ -383,12 +383,16 @@ const createCodeChallenge = (verifier: string) => crypto.createHash('sha256').up
|
||||
|
||||
const resolveBillingPlan = (
|
||||
workspaceType: WorkspaceType,
|
||||
requestedPlan?: BillingPlan | 'household_basic' | 'household_plus' | 'household_macaw',
|
||||
requestedPlan?: BillingPlan | 'household_basic' | 'household_plus' | 'household_macaw' | 'household_hyacinth_macaw',
|
||||
) => {
|
||||
if (workspaceType === 'rescue') {
|
||||
return 'rescue_free' as const;
|
||||
}
|
||||
|
||||
if (requestedPlan === 'household_hyacinth_macaw') {
|
||||
return 'household_hyacinth_macaw';
|
||||
}
|
||||
|
||||
if (requestedPlan === 'household_macaw') {
|
||||
return 'household_macaw';
|
||||
}
|
||||
@@ -440,8 +444,18 @@ const stripePriceByBillingPlanAndInterval: Partial<Record<Exclude<BillingPlan, '
|
||||
yearly: process.env.STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_YEARLY?.trim() ?? '',
|
||||
},
|
||||
household_macaw: {
|
||||
monthly: process.env.STRIPE_PRICE_HOUSEHOLD_MACAW_MONTHLY?.trim() || process.env.STRIPE_PRICE_HOUSEHOLD_MACAW?.trim() || '',
|
||||
yearly: process.env.STRIPE_PRICE_HOUSEHOLD_MACAW_YEARLY?.trim() ?? '',
|
||||
monthly:
|
||||
process.env.STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_MONTHLY?.trim() ||
|
||||
process.env.STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY?.trim() ||
|
||||
'',
|
||||
yearly: process.env.STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_YEARLY?.trim() ?? '',
|
||||
},
|
||||
household_hyacinth_macaw: {
|
||||
monthly:
|
||||
process.env.STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_MONTHLY?.trim() ||
|
||||
process.env.STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW?.trim() ||
|
||||
'',
|
||||
yearly: process.env.STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_YEARLY?.trim() ?? '',
|
||||
},
|
||||
};
|
||||
const stripePriceEnvNamesByBillingPlanAndInterval: Record<Exclude<BillingPlan, 'rescue_free'>, Record<BillingInterval, string[]>> = {
|
||||
@@ -454,14 +468,19 @@ const stripePriceEnvNamesByBillingPlanAndInterval: Record<Exclude<BillingPlan, '
|
||||
yearly: ['STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_YEARLY'],
|
||||
},
|
||||
household_macaw: {
|
||||
monthly: ['STRIPE_PRICE_HOUSEHOLD_MACAW_MONTHLY', 'STRIPE_PRICE_HOUSEHOLD_MACAW'],
|
||||
yearly: ['STRIPE_PRICE_HOUSEHOLD_MACAW_YEARLY'],
|
||||
monthly: ['STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_MONTHLY', 'STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY'],
|
||||
yearly: ['STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_YEARLY'],
|
||||
},
|
||||
household_hyacinth_macaw: {
|
||||
monthly: ['STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_MONTHLY', 'STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW'],
|
||||
yearly: ['STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_YEARLY'],
|
||||
},
|
||||
};
|
||||
const stripePricePlanLabels: Record<Exclude<BillingPlan, 'rescue_free'>, string> = {
|
||||
household_basic: 'Conure',
|
||||
household_plus: 'Indian Ringneck',
|
||||
household_macaw: 'Macaw',
|
||||
household_macaw: 'African Grey',
|
||||
household_hyacinth_macaw: 'Hyacinth Macaw',
|
||||
};
|
||||
const stripe = stripeSecretKey ? new Stripe(stripeSecretKey) : null;
|
||||
const adminEmails = new Set(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export type WorkspaceType = 'standard' | 'rescue';
|
||||
export type WorkspaceRole = 'owner' | 'assistant' | 'caregiver' | 'viewer';
|
||||
export type BillingPlan = 'rescue_free' | 'household_basic' | 'household_plus' | 'household_macaw';
|
||||
export type BillingPlan = 'rescue_free' | 'household_basic' | 'household_plus' | 'household_macaw' | 'household_hyacinth_macaw';
|
||||
export type BillingInterval = 'monthly' | 'yearly';
|
||||
export type SubscriptionStatus = 'active' | 'trialing' | 'past_due' | 'canceled' | 'unpaid' | 'none';
|
||||
export type RescueVerificationStatus = 'not_required' | 'pending' | 'approved' | 'rejected';
|
||||
|
||||
@@ -73,9 +73,12 @@ services:
|
||||
STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK: ${STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_MONTHLY: ${STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_MONTHLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_YEARLY: ${STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_YEARLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_MACAW: ${STRIPE_PRICE_HOUSEHOLD_MACAW:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_MACAW_MONTHLY: ${STRIPE_PRICE_HOUSEHOLD_MACAW_MONTHLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_MACAW_YEARLY: ${STRIPE_PRICE_HOUSEHOLD_MACAW_YEARLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY: ${STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_MONTHLY: ${STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_MONTHLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_YEARLY: ${STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_YEARLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW: ${STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_MONTHLY: ${STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_MONTHLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_YEARLY: ${STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_YEARLY:-}
|
||||
STRIPE_CHECKOUT_SUCCESS_URL: ${STRIPE_CHECKOUT_SUCCESS_URL:-${FRONTEND_URL}/?billing=success}
|
||||
STRIPE_CHECKOUT_CANCEL_URL: ${STRIPE_CHECKOUT_CANCEL_URL:-${FRONTEND_URL}/?billing=cancelled}
|
||||
STRIPE_PORTAL_RETURN_URL: ${STRIPE_PORTAL_RETURN_URL:-${FRONTEND_URL}/?billing=portal}
|
||||
|
||||
+6
-3
@@ -71,9 +71,12 @@ services:
|
||||
STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK: ${STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_MONTHLY: ${STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_MONTHLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_YEARLY: ${STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_YEARLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_MACAW: ${STRIPE_PRICE_HOUSEHOLD_MACAW:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_MACAW_MONTHLY: ${STRIPE_PRICE_HOUSEHOLD_MACAW_MONTHLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_MACAW_YEARLY: ${STRIPE_PRICE_HOUSEHOLD_MACAW_YEARLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY: ${STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_MONTHLY: ${STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_MONTHLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_YEARLY: ${STRIPE_PRICE_HOUSEHOLD_AFRICAN_GREY_YEARLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW: ${STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_MONTHLY: ${STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_MONTHLY:-}
|
||||
STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_YEARLY: ${STRIPE_PRICE_HOUSEHOLD_HYACINTH_MACAW_YEARLY:-}
|
||||
STRIPE_CHECKOUT_SUCCESS_URL: ${STRIPE_CHECKOUT_SUCCESS_URL:-http://localhost:3000/?billing=success}
|
||||
STRIPE_CHECKOUT_CANCEL_URL: ${STRIPE_CHECKOUT_CANCEL_URL:-http://localhost:3000/?billing=cancelled}
|
||||
STRIPE_PORTAL_RETURN_URL: ${STRIPE_PORTAL_RETURN_URL:-http://localhost:3000/?billing=portal}
|
||||
|
||||
@@ -653,7 +653,7 @@ Request body:
|
||||
Notes:
|
||||
|
||||
- `workspaceType` must be `standard` or `rescue`
|
||||
- `billingPlan` may be `household_basic`, `household_plus`, or `household_macaw`
|
||||
- `billingPlan` may be `household_basic`, `household_plus`, `household_macaw`, or `household_hyacinth_macaw`
|
||||
- rescue workspaces are forced to `rescue_free`
|
||||
|
||||
Response `201`:
|
||||
|
||||
+35
-13
@@ -5,7 +5,7 @@ import defaultBirdPhoto from './assets/yoda-default.png';
|
||||
import { findParrotWeightReference, parrotSpeciesOptions, type ParrotWeightReference } from './parrotWeightReference';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
type BillingPlan = 'rescue_free' | 'household_basic' | 'household_plus' | 'household_macaw';
|
||||
type BillingPlan = 'rescue_free' | 'household_basic' | 'household_plus' | 'household_macaw' | 'household_hyacinth_macaw';
|
||||
type HouseholdBillingPlan = Exclude<BillingPlan, 'rescue_free'>;
|
||||
type BillingInterval = 'monthly' | 'yearly';
|
||||
type WorkspaceType = 'standard' | 'rescue';
|
||||
@@ -978,7 +978,7 @@ const oauthStartUrl = (providerKey: AuthProvider['providerKey']) => {
|
||||
};
|
||||
|
||||
const isHouseholdPlan = (billingPlan: BillingPlan): billingPlan is HouseholdBillingPlan =>
|
||||
billingPlan === 'household_basic' || billingPlan === 'household_plus' || billingPlan === 'household_macaw';
|
||||
billingPlan === 'household_basic' || billingPlan === 'household_plus' || billingPlan === 'household_macaw' || billingPlan === 'household_hyacinth_macaw';
|
||||
|
||||
const formatBillingIntervalName = (billingInterval: BillingInterval) => (billingInterval === 'yearly' ? 'Annual' : 'Monthly');
|
||||
|
||||
@@ -995,7 +995,11 @@ const formatBillingPlanName = (billingPlan: BillingPlan) => {
|
||||
return 'Indian Ringneck';
|
||||
}
|
||||
|
||||
return 'Macaw';
|
||||
if (billingPlan === 'household_macaw') {
|
||||
return 'African Grey';
|
||||
}
|
||||
|
||||
return 'Hyacinth Macaw';
|
||||
};
|
||||
|
||||
const formatBillingPlanCapacity = (billingPlan: BillingPlan) => {
|
||||
@@ -1008,10 +1012,14 @@ const formatBillingPlanCapacity = (billingPlan: BillingPlan) => {
|
||||
}
|
||||
|
||||
if (billingPlan === 'household_plus') {
|
||||
return 'Permits 5 to 10 birds in the flock.';
|
||||
return 'Permits 5 to 9 birds in the flock.';
|
||||
}
|
||||
|
||||
return 'Permits 11 or more birds in the flock.';
|
||||
if (billingPlan === 'household_macaw') {
|
||||
return 'Permits 11 to 16 birds in the flock.';
|
||||
}
|
||||
|
||||
return 'Permits 17 or more birds in the flock.';
|
||||
};
|
||||
|
||||
const formatBillingPlanDropdownLabel = (billingPlan: HouseholdBillingPlan) => {
|
||||
@@ -1020,10 +1028,14 @@ const formatBillingPlanDropdownLabel = (billingPlan: HouseholdBillingPlan) => {
|
||||
}
|
||||
|
||||
if (billingPlan === 'household_plus') {
|
||||
return 'Indian Ringneck (10 birds)';
|
||||
return 'Indian Ringneck (5-9 birds)';
|
||||
}
|
||||
|
||||
return 'Macaw (11+)';
|
||||
if (billingPlan === 'household_macaw') {
|
||||
return 'African Grey (11-16 birds)';
|
||||
}
|
||||
|
||||
return 'Hyacinth Macaw (17+)';
|
||||
};
|
||||
|
||||
const householdPlanPrices: Record<HouseholdBillingPlan, Record<BillingInterval, string>> = {
|
||||
@@ -1039,6 +1051,10 @@ const householdPlanPrices: Record<HouseholdBillingPlan, Record<BillingInterval,
|
||||
monthly: '$15.99/month',
|
||||
yearly: '$160/year',
|
||||
},
|
||||
household_hyacinth_macaw: {
|
||||
monthly: '$49.99/month',
|
||||
yearly: '$500/year',
|
||||
},
|
||||
};
|
||||
|
||||
const formatBillingIntervalDropdownLabel = (billingPlan: HouseholdBillingPlan, billingInterval: BillingInterval) =>
|
||||
@@ -1050,11 +1066,15 @@ const formatBillingPlanBirdLimit = (billingPlan: BillingPlan) => {
|
||||
}
|
||||
|
||||
if (billingPlan === 'household_plus') {
|
||||
return '10';
|
||||
return '9';
|
||||
}
|
||||
|
||||
if (billingPlan === 'household_macaw') {
|
||||
return '11+';
|
||||
return '16';
|
||||
}
|
||||
|
||||
if (billingPlan === 'household_hyacinth_macaw') {
|
||||
return '17+';
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -6432,8 +6452,9 @@ function App() {
|
||||
}
|
||||
>
|
||||
<option value="household_basic">Conure (4 birds)</option>
|
||||
<option value="household_plus">Indian Ringneck (10 birds)</option>
|
||||
<option value="household_macaw">Macaw (11+)</option>
|
||||
<option value="household_plus">Indian Ringneck (5-9 birds)</option>
|
||||
<option value="household_macaw">African Grey (11-16 birds)</option>
|
||||
<option value="household_hyacinth_macaw">Hyacinth Macaw (17+)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
@@ -6570,8 +6591,9 @@ function App() {
|
||||
}
|
||||
>
|
||||
<option value="household_basic">Conure (4 birds)</option>
|
||||
<option value="household_plus">Indian Ringneck (10 birds)</option>
|
||||
<option value="household_macaw">Macaw (11+)</option>
|
||||
<option value="household_plus">Indian Ringneck (5-9 birds)</option>
|
||||
<option value="household_macaw">African Grey (11-16 birds)</option>
|
||||
<option value="household_hyacinth_macaw">Hyacinth Macaw (17+)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
|
||||
Reference in New Issue
Block a user