additional stripe settings
This commit is contained in:
+24
-1
@@ -262,6 +262,25 @@ const stripePriceByBillingPlanAndInterval: Partial<Record<Exclude<BillingPlan, '
|
|||||||
yearly: process.env.STRIPE_PRICE_HOUSEHOLD_MACAW_YEARLY?.trim() ?? '',
|
yearly: process.env.STRIPE_PRICE_HOUSEHOLD_MACAW_YEARLY?.trim() ?? '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const stripePriceEnvNamesByBillingPlanAndInterval: Record<Exclude<BillingPlan, 'rescue_free'>, Record<BillingInterval, string[]>> = {
|
||||||
|
household_basic: {
|
||||||
|
monthly: ['STRIPE_PRICE_HOUSEHOLD_CONURE_MONTHLY', 'STRIPE_PRICE_HOUSEHOLD_CONURE'],
|
||||||
|
yearly: ['STRIPE_PRICE_HOUSEHOLD_CONURE_YEARLY'],
|
||||||
|
},
|
||||||
|
household_plus: {
|
||||||
|
monthly: ['STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_MONTHLY', 'STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK'],
|
||||||
|
yearly: ['STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_YEARLY'],
|
||||||
|
},
|
||||||
|
household_macaw: {
|
||||||
|
monthly: ['STRIPE_PRICE_HOUSEHOLD_MACAW_MONTHLY', 'STRIPE_PRICE_HOUSEHOLD_MACAW'],
|
||||||
|
yearly: ['STRIPE_PRICE_HOUSEHOLD_MACAW_YEARLY'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const stripePricePlanLabels: Record<Exclude<BillingPlan, 'rescue_free'>, string> = {
|
||||||
|
household_basic: 'Conure',
|
||||||
|
household_plus: 'Indian Ringneck',
|
||||||
|
household_macaw: 'Macaw',
|
||||||
|
};
|
||||||
const stripe = stripeSecretKey ? new Stripe(stripeSecretKey) : null;
|
const stripe = stripeSecretKey ? new Stripe(stripeSecretKey) : null;
|
||||||
const adminEmails = new Set(
|
const adminEmails = new Set(
|
||||||
(process.env.ADMIN_EMAILS ?? '')
|
(process.env.ADMIN_EMAILS ?? '')
|
||||||
@@ -573,7 +592,11 @@ const getStripePriceIdForBillingPlan = (billingPlan: BillingPlan, billingInterva
|
|||||||
const priceId = stripePriceByBillingPlanAndInterval[billingPlan]?.[billingInterval]?.trim() ?? '';
|
const priceId = stripePriceByBillingPlanAndInterval[billingPlan]?.[billingInterval]?.trim() ?? '';
|
||||||
|
|
||||||
if (!priceId) {
|
if (!priceId) {
|
||||||
throw new Error(`Stripe price is not configured for ${billingPlan} (${billingInterval}).`);
|
const planLabel = stripePricePlanLabels[billingPlan] ?? billingPlan;
|
||||||
|
const envNames = stripePriceEnvNamesByBillingPlanAndInterval[billingPlan]?.[billingInterval] ?? [];
|
||||||
|
const envHint = envNames.length > 0 ? ` Set ${envNames.join(' or ')} in the backend environment.` : '';
|
||||||
|
|
||||||
|
throw new Error(`Stripe price is not configured for ${planLabel} ${billingInterval}.${envHint}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return priceId;
|
return priceId;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const ensureSchema = async (database: DatabaseClient = db) => {
|
|||||||
billing_email VARCHAR(255),
|
billing_email VARCHAR(255),
|
||||||
billing_plan VARCHAR(32) NOT NULL DEFAULT 'household_basic',
|
billing_plan VARCHAR(32) NOT NULL DEFAULT 'household_basic',
|
||||||
billing_interval VARCHAR(16) NOT NULL DEFAULT 'monthly',
|
billing_interval VARCHAR(16) NOT NULL DEFAULT 'monthly',
|
||||||
subscription_status VARCHAR(32) NOT NULL DEFAULT 'active',
|
subscription_status VARCHAR(32) NOT NULL DEFAULT 'none',
|
||||||
stripe_customer_id VARCHAR(255),
|
stripe_customer_id VARCHAR(255),
|
||||||
stripe_subscription_id VARCHAR(255),
|
stripe_subscription_id VARCHAR(255),
|
||||||
rescue_verification_status VARCHAR(32) NOT NULL DEFAULT 'not_required',
|
rescue_verification_status VARCHAR(32) NOT NULL DEFAULT 'not_required',
|
||||||
@@ -34,7 +34,7 @@ export const ensureSchema = async (database: DatabaseClient = db) => {
|
|||||||
ADD COLUMN IF NOT EXISTS billing_email VARCHAR(255),
|
ADD COLUMN IF NOT EXISTS billing_email VARCHAR(255),
|
||||||
ADD COLUMN IF NOT EXISTS billing_plan VARCHAR(32) NOT NULL DEFAULT 'household_basic',
|
ADD COLUMN IF NOT EXISTS billing_plan VARCHAR(32) NOT NULL DEFAULT 'household_basic',
|
||||||
ADD COLUMN IF NOT EXISTS billing_interval VARCHAR(16) NOT NULL DEFAULT 'monthly',
|
ADD COLUMN IF NOT EXISTS billing_interval VARCHAR(16) NOT NULL DEFAULT 'monthly',
|
||||||
ADD COLUMN IF NOT EXISTS subscription_status VARCHAR(32) NOT NULL DEFAULT 'active',
|
ADD COLUMN IF NOT EXISTS subscription_status VARCHAR(32) NOT NULL DEFAULT 'none',
|
||||||
ADD COLUMN IF NOT EXISTS stripe_customer_id VARCHAR(255),
|
ADD COLUMN IF NOT EXISTS stripe_customer_id VARCHAR(255),
|
||||||
ADD COLUMN IF NOT EXISTS stripe_subscription_id VARCHAR(255),
|
ADD COLUMN IF NOT EXISTS stripe_subscription_id VARCHAR(255),
|
||||||
ADD COLUMN IF NOT EXISTS rescue_verification_status VARCHAR(32) NOT NULL DEFAULT 'not_required';
|
ADD COLUMN IF NOT EXISTS rescue_verification_status VARCHAR(32) NOT NULL DEFAULT 'not_required';
|
||||||
@@ -47,6 +47,12 @@ export const ensureSchema = async (database: DatabaseClient = db) => {
|
|||||||
ON workspaces (stripe_customer_id)
|
ON workspaces (stripe_customer_id)
|
||||||
WHERE stripe_customer_id IS NOT NULL;
|
WHERE stripe_customer_id IS NOT NULL;
|
||||||
|
|
||||||
|
UPDATE workspaces
|
||||||
|
SET subscription_status = 'none'
|
||||||
|
WHERE workspace_type = 'standard'
|
||||||
|
AND stripe_subscription_id IS NULL
|
||||||
|
AND subscription_status = 'active';
|
||||||
|
|
||||||
UPDATE workspaces
|
UPDATE workspaces
|
||||||
SET rescue_verification_status = 'pending'
|
SET rescue_verification_status = 'pending'
|
||||||
WHERE workspace_type = 'rescue'
|
WHERE workspace_type = 'rescue'
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export const ensurePersonalWorkspaceForUser = async (user: UserRow) => {
|
|||||||
if (!unclaimed.rowCount) {
|
if (!unclaimed.rowCount) {
|
||||||
await db.query(
|
await db.query(
|
||||||
`INSERT INTO workspaces (id, name, workspace_type, billing_plan, billing_interval, billing_email, subscription_status, rescue_verification_status)
|
`INSERT INTO workspaces (id, name, workspace_type, billing_plan, billing_interval, billing_email, subscription_status, rescue_verification_status)
|
||||||
VALUES ($1, $2, 'standard', 'household_basic', 'monthly', $3, 'active', 'not_required')`,
|
VALUES ($1, $2, 'standard', 'household_basic', 'monthly', $3, 'none', 'not_required')`,
|
||||||
[workspaceId, `${user.name}'s Flock`, user.email],
|
[workspaceId, `${user.name}'s Flock`, user.email],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -117,7 +117,7 @@ export const ensurePersonalWorkspaceForUser = async (user: UserRow) => {
|
|||||||
billing_plan = 'household_basic',
|
billing_plan = 'household_basic',
|
||||||
billing_interval = 'monthly',
|
billing_interval = 'monthly',
|
||||||
billing_email = $3,
|
billing_email = $3,
|
||||||
subscription_status = 'active',
|
subscription_status = 'none',
|
||||||
rescue_verification_status = 'not_required',
|
rescue_verification_status = 'not_required',
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $1`,
|
WHERE id = $1`,
|
||||||
@@ -178,7 +178,7 @@ export const createWorkspace = async ({
|
|||||||
billingEmail,
|
billingEmail,
|
||||||
billingPlan,
|
billingPlan,
|
||||||
billingInterval,
|
billingInterval,
|
||||||
workspaceType === 'rescue' ? 'active' : 'active',
|
workspaceType === 'rescue' ? 'active' : 'none',
|
||||||
workspaceType === 'rescue' ? 'pending' : 'not_required',
|
workspaceType === 'rescue' ? 'pending' : 'not_required',
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -412,6 +412,10 @@ export const updateRescueVerificationStatus = async (workspaceId: number, status
|
|||||||
WHEN $2 = 'rejected' THEN 'monthly'
|
WHEN $2 = 'rejected' THEN 'monthly'
|
||||||
ELSE billing_interval
|
ELSE billing_interval
|
||||||
END,
|
END,
|
||||||
|
subscription_status = CASE
|
||||||
|
WHEN $2 = 'rejected' THEN 'none'
|
||||||
|
ELSE subscription_status
|
||||||
|
END,
|
||||||
rescue_verification_status = CASE
|
rescue_verification_status = CASE
|
||||||
WHEN $2 = 'rejected' THEN 'not_required'
|
WHEN $2 = 'rejected' THEN 'not_required'
|
||||||
ELSE $2
|
ELSE $2
|
||||||
@@ -432,6 +436,7 @@ export const cancelRescueVerificationRequest = async (workspaceId: number) => {
|
|||||||
SET workspace_type = 'standard',
|
SET workspace_type = 'standard',
|
||||||
billing_plan = 'household_basic',
|
billing_plan = 'household_basic',
|
||||||
billing_interval = 'monthly',
|
billing_interval = 'monthly',
|
||||||
|
subscription_status = 'none',
|
||||||
rescue_verification_status = 'not_required',
|
rescue_verification_status = 'not_required',
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
|
|||||||
+40
-10
@@ -521,6 +521,36 @@ const formatBillingPlanCapacity = (billingPlan: BillingPlan) => {
|
|||||||
return 'Permits 11 or more birds in the flock.';
|
return 'Permits 11 or more birds in the flock.';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatBillingPlanDropdownLabel = (billingPlan: HouseholdBillingPlan) => {
|
||||||
|
if (billingPlan === 'household_basic') {
|
||||||
|
return 'Conure (4 birds)';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (billingPlan === 'household_plus') {
|
||||||
|
return 'Indian Ringneck (10 birds)';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Macaw (11+)';
|
||||||
|
};
|
||||||
|
|
||||||
|
const householdPlanPrices: Record<HouseholdBillingPlan, Record<BillingInterval, string>> = {
|
||||||
|
household_basic: {
|
||||||
|
monthly: '$4.99/month',
|
||||||
|
yearly: '$50/year',
|
||||||
|
},
|
||||||
|
household_plus: {
|
||||||
|
monthly: '$8.99/month',
|
||||||
|
yearly: '$90/year',
|
||||||
|
},
|
||||||
|
household_macaw: {
|
||||||
|
monthly: '$15.99/month',
|
||||||
|
yearly: '$160/year',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatBillingIntervalDropdownLabel = (billingPlan: HouseholdBillingPlan, billingInterval: BillingInterval) =>
|
||||||
|
`${formatBillingIntervalName(billingInterval)} (${householdPlanPrices[billingPlan][billingInterval]})`;
|
||||||
|
|
||||||
const formatBillingPlanBirdLimit = (billingPlan: BillingPlan) => {
|
const formatBillingPlanBirdLimit = (billingPlan: BillingPlan) => {
|
||||||
if (billingPlan === 'household_basic') {
|
if (billingPlan === 'household_basic') {
|
||||||
return '4';
|
return '4';
|
||||||
@@ -3392,9 +3422,9 @@ function App() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<option value="household_basic">Conure - up to 4 birds</option>
|
<option value="household_basic">Conure (4 birds)</option>
|
||||||
<option value="household_plus">Indian Ringneck - up to 10 birds</option>
|
<option value="household_plus">Indian Ringneck (10 birds)</option>
|
||||||
<option value="household_macaw">Macaw - 11+ birds</option>
|
<option value="household_macaw">Macaw (11+)</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
@@ -3408,8 +3438,8 @@ function App() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<option value="monthly">Monthly</option>
|
<option value="monthly">{formatBillingIntervalDropdownLabel(workspaceForm.billingPlan, 'monthly')}</option>
|
||||||
<option value="yearly">Annual</option>
|
<option value="yearly">{formatBillingIntervalDropdownLabel(workspaceForm.billingPlan, 'yearly')}</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
@@ -3763,9 +3793,9 @@ function App() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<option value="household_basic">Conure - up to 4 birds</option>
|
<option value="household_basic">Conure (4 birds)</option>
|
||||||
<option value="household_plus">Indian Ringneck - up to 10 birds</option>
|
<option value="household_plus">Indian Ringneck (10 birds)</option>
|
||||||
<option value="household_macaw">Macaw - 11+ birds</option>
|
<option value="household_macaw">Macaw (11+)</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
@@ -3779,8 +3809,8 @@ function App() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<option value="monthly">Monthly</option>
|
<option value="monthly">{formatBillingIntervalDropdownLabel(workspaceCreateForm.billingPlan, 'monthly')}</option>
|
||||||
<option value="yearly">Annual</option>
|
<option value="yearly">{formatBillingIntervalDropdownLabel(workspaceCreateForm.billingPlan, 'yearly')}</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<article className="summary-card">
|
<article className="summary-card">
|
||||||
|
|||||||
Reference in New Issue
Block a user