Updated subscriptions
Deploy / deploy-dev (push) Failing after 4s
Deploy / deploy-prod (push) Has been skipped

This commit is contained in:
blaisadmin
2026-05-30 15:19:47 -04:00
parent b4f6193395
commit 841d0a9669
8 changed files with 86 additions and 34 deletions
+6 -3
View File
@@ -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
+5 -3
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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';
+6 -3
View File
@@ -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
View File
@@ -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}
+1 -1
View File
@@ -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
View File
@@ -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>