feat: add subscription API endpoints and grace period job - M3 (refs #55)
API Endpoints (all authenticated): - GET /api/subscriptions - current subscription status - POST /api/subscriptions/checkout - create Stripe subscription - POST /api/subscriptions/cancel - schedule cancellation at period end - POST /api/subscriptions/reactivate - cancel pending cancellation - PUT /api/subscriptions/payment-method - update payment method - GET /api/subscriptions/invoices - billing history Grace Period Job: - Daily cron at 2:30 AM to check expired grace periods - Downgrades to free tier when 30-day grace period expires - Syncs tier to user_profiles.subscription_tier Email Templates: - payment_failed_immediate (first failure) - payment_failed_7day (7 days before grace ends) - payment_failed_1day (1 day before grace ends) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* Migration: Add payment failure email templates
|
||||
* @ai-summary Adds email templates for payment failures during grace period
|
||||
* @ai-context Three templates: immediate, 7-day warning, 1-day warning
|
||||
*/
|
||||
|
||||
-- Extend template_key CHECK constraint to include payment failure templates
|
||||
ALTER TABLE email_templates
|
||||
DROP CONSTRAINT IF EXISTS email_templates_template_key_check;
|
||||
|
||||
ALTER TABLE email_templates
|
||||
ADD CONSTRAINT email_templates_template_key_check
|
||||
CHECK (template_key IN (
|
||||
'maintenance_due_soon', 'maintenance_overdue',
|
||||
'document_expiring', 'document_expired',
|
||||
'payment_failed_immediate', 'payment_failed_7day', 'payment_failed_1day'
|
||||
));
|
||||
|
||||
-- Insert payment failure email templates
|
||||
INSERT INTO email_templates (template_key, name, description, subject, body, variables, html_body) VALUES
|
||||
(
|
||||
'payment_failed_immediate',
|
||||
'Payment Failed - Immediate Notice',
|
||||
'Sent immediately when a subscription payment fails',
|
||||
'MotoVaultPro: Payment Failed - Action Required',
|
||||
'Hi {{userName}},
|
||||
|
||||
We were unable to process your payment for your {{tier}} subscription.
|
||||
|
||||
Your subscription will remain active for 30 days while we attempt to collect payment. After 30 days, your subscription will be downgraded to the free tier.
|
||||
|
||||
Please update your payment method to avoid interruption of service.
|
||||
|
||||
Amount Due: ${{amount}}
|
||||
Next Retry: {{retryDate}}
|
||||
|
||||
Best regards,
|
||||
MotoVaultPro Team',
|
||||
'["userName", "tier", "amount", "retryDate"]',
|
||||
'<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Payment Failed</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f4f4f4;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f4f4f4; padding: 20px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||
<tr>
|
||||
<td style="background-color: #d32f2f; padding: 30px; text-align: center;">
|
||||
<h1 style="color: #ffffff; margin: 0; font-size: 24px;">Payment Failed</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 40px 30px;">
|
||||
<p style="color: #333333; font-size: 16px; line-height: 1.6; margin: 0 0 20px 0;">Hi {{userName}},</p>
|
||||
<p style="color: #333333; font-size: 16px; line-height: 1.6; margin: 0 0 20px 0;">We were unable to process your payment for your <strong>{{tier}}</strong> subscription.</p>
|
||||
<p style="color: #333333; font-size: 16px; line-height: 1.6; margin: 0 0 20px 0;">Your subscription will remain active for <strong>30 days</strong> while we attempt to collect payment. After 30 days, your subscription will be downgraded to the free tier.</p>
|
||||
<p style="color: #333333; font-size: 16px; line-height: 1.6; margin: 0 0 20px 0;">Please update your payment method to avoid interruption of service.</p>
|
||||
<table cellpadding="0" cellspacing="0" style="margin: 20px 0; width: 100%;">
|
||||
<tr>
|
||||
<td style="padding: 15px; background-color: #f9f9f9; border-left: 4px solid #d32f2f;">
|
||||
<p style="margin: 0 0 10px 0; color: #666666; font-size: 14px;"><strong>Amount Due:</strong> ${{amount}}</p>
|
||||
<p style="margin: 0; color: #666666; font-size: 14px;"><strong>Next Retry:</strong> {{retryDate}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="https://app.motovaultpro.com/settings/billing" style="display: inline-block; padding: 14px 28px; background-color: #1976d2; color: #ffffff; text-decoration: none; border-radius: 4px; font-weight: bold;">Update Payment Method</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 20px 30px; background-color: #f9f9f9; border-top: 1px solid #e0e0e0;">
|
||||
<p style="color: #666666; font-size: 14px; line-height: 1.6; margin: 0;">Best regards,<br>MotoVaultPro Team</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>'
|
||||
),
|
||||
(
|
||||
'payment_failed_7day',
|
||||
'Payment Failed - 7 Days Left',
|
||||
'Sent 7 days before grace period ends',
|
||||
'MotoVaultPro: Urgent - 7 Days Until Downgrade',
|
||||
'Hi {{userName}},
|
||||
|
||||
This is an urgent reminder that your {{tier}} subscription payment is still outstanding.
|
||||
|
||||
Your subscription will be downgraded to the free tier in 7 days if payment is not received.
|
||||
|
||||
Amount Due: ${{amount}}
|
||||
Grace Period Ends: {{gracePeriodEnd}}
|
||||
|
||||
Please update your payment method immediately to avoid losing access to premium features.
|
||||
|
||||
Best regards,
|
||||
MotoVaultPro Team',
|
||||
'["userName", "tier", "amount", "gracePeriodEnd"]',
|
||||
'<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Payment Reminder - 7 Days Left</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f4f4f4;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f4f4f4; padding: 20px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||
<tr>
|
||||
<td style="background-color: #f57c00; padding: 30px; text-align: center;">
|
||||
<h1 style="color: #ffffff; margin: 0; font-size: 24px;">Urgent: 7 Days Until Downgrade</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 40px 30px;">
|
||||
<p style="color: #333333; font-size: 16px; line-height: 1.6; margin: 0 0 20px 0;">Hi {{userName}},</p>
|
||||
<p style="color: #333333; font-size: 16px; line-height: 1.6; margin: 0 0 20px 0;">This is an urgent reminder that your <strong>{{tier}}</strong> subscription payment is still outstanding.</p>
|
||||
<div style="background-color: #fff3e0; border-left: 4px solid #f57c00; padding: 20px; margin: 20px 0;">
|
||||
<p style="color: #e65100; font-size: 18px; font-weight: bold; margin: 0 0 10px 0;">Your subscription will be downgraded in 7 days</p>
|
||||
<p style="color: #333333; font-size: 14px; margin: 0;">If payment is not received by {{gracePeriodEnd}}, you will lose access to premium features.</p>
|
||||
</div>
|
||||
<table cellpadding="0" cellspacing="0" style="margin: 20px 0; width: 100%;">
|
||||
<tr>
|
||||
<td style="padding: 15px; background-color: #f9f9f9; border-left: 4px solid #f57c00;">
|
||||
<p style="margin: 0 0 10px 0; color: #666666; font-size: 14px;"><strong>Amount Due:</strong> ${{amount}}</p>
|
||||
<p style="margin: 0; color: #666666; font-size: 14px;"><strong>Grace Period Ends:</strong> {{gracePeriodEnd}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="https://app.motovaultpro.com/settings/billing" style="display: inline-block; padding: 14px 28px; background-color: #f57c00; color: #ffffff; text-decoration: none; border-radius: 4px; font-weight: bold;">Update Payment Now</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 20px 30px; background-color: #f9f9f9; border-top: 1px solid #e0e0e0;">
|
||||
<p style="color: #666666; font-size: 14px; line-height: 1.6; margin: 0;">Best regards,<br>MotoVaultPro Team</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>'
|
||||
),
|
||||
(
|
||||
'payment_failed_1day',
|
||||
'Payment Failed - Final Notice',
|
||||
'Sent 1 day before grace period ends',
|
||||
'MotoVaultPro: FINAL NOTICE - Downgrade Tomorrow',
|
||||
'Hi {{userName}},
|
||||
|
||||
FINAL NOTICE: Your {{tier}} subscription will be downgraded to the free tier tomorrow if payment is not received.
|
||||
|
||||
Amount Due: ${{amount}}
|
||||
Grace Period Ends: {{gracePeriodEnd}}
|
||||
|
||||
This is your last chance to update your payment method and keep your premium features.
|
||||
|
||||
After downgrade:
|
||||
- Access to premium features will be lost
|
||||
- Data remains safe but with reduced vehicle limits
|
||||
- You can resubscribe at any time
|
||||
|
||||
Please update your payment method now to avoid interruption.
|
||||
|
||||
Best regards,
|
||||
MotoVaultPro Team',
|
||||
'["userName", "tier", "amount", "gracePeriodEnd"]',
|
||||
'<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Final Notice - Downgrade Tomorrow</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f4f4f4;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f4f4f4; padding: 20px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||
<tr>
|
||||
<td style="background-color: #c62828; padding: 30px; text-align: center;">
|
||||
<h1 style="color: #ffffff; margin: 0; font-size: 24px;">FINAL NOTICE</h1>
|
||||
<p style="color: #ffffff; margin: 10px 0 0 0; font-size: 16px;">Downgrade Tomorrow</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 40px 30px;">
|
||||
<p style="color: #333333; font-size: 16px; line-height: 1.6; margin: 0 0 20px 0;">Hi {{userName}},</p>
|
||||
<div style="background-color: #ffebee; border: 2px solid #c62828; border-radius: 4px; padding: 20px; margin: 20px 0;">
|
||||
<p style="color: #b71c1c; font-size: 18px; font-weight: bold; margin: 0 0 10px 0;">FINAL NOTICE</p>
|
||||
<p style="color: #333333; font-size: 16px; margin: 0;">Your <strong>{{tier}}</strong> subscription will be downgraded to the free tier <strong>tomorrow</strong> if payment is not received.</p>
|
||||
</div>
|
||||
<table cellpadding="0" cellspacing="0" style="margin: 20px 0; width: 100%;">
|
||||
<tr>
|
||||
<td style="padding: 15px; background-color: #f9f9f9; border-left: 4px solid #c62828;">
|
||||
<p style="margin: 0 0 10px 0; color: #666666; font-size: 14px;"><strong>Amount Due:</strong> ${{amount}}</p>
|
||||
<p style="margin: 0; color: #666666; font-size: 14px;"><strong>Grace Period Ends:</strong> {{gracePeriodEnd}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="color: #333333; font-size: 16px; line-height: 1.6; margin: 20px 0;">This is your last chance to update your payment method and keep your premium features.</p>
|
||||
<div style="background-color: #f5f5f5; padding: 20px; border-radius: 4px; margin: 20px 0;">
|
||||
<p style="color: #333333; font-size: 14px; font-weight: bold; margin: 0 0 10px 0;">After downgrade:</p>
|
||||
<ul style="color: #666666; font-size: 14px; line-height: 1.8; margin: 0; padding-left: 20px;">
|
||||
<li>Access to premium features will be lost</li>
|
||||
<li>Data remains safe but with reduced vehicle limits</li>
|
||||
<li>You can resubscribe at any time</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="https://app.motovaultpro.com/settings/billing" style="display: inline-block; padding: 14px 28px; background-color: #c62828; color: #ffffff; text-decoration: none; border-radius: 4px; font-weight: bold; font-size: 16px;">Update Payment Now</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 20px 30px; background-color: #f9f9f9; border-top: 1px solid #e0e0e0;">
|
||||
<p style="color: #666666; font-size: 14px; line-height: 1.6; margin: 0;">Best regards,<br>MotoVaultPro Team</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>'
|
||||
);
|
||||
Reference in New Issue
Block a user