Files
motovaultpro/backend/src/features/notifications/migrations/006_payment_email_templates.sql
Eric Gullickson e7461a4836 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>
2026-01-18 16:16:58 -06:00

240 lines
12 KiB
SQL

/**
* 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>'
);