Implement multi-step registration process: Add step indicators, error handling, and payment processing for membership registration. Enhance form validation and user feedback with success and error messages. Refactor state management for improved clarity and maintainability.
This commit is contained in:
parent
a88aa62198
commit
2ca290d6e0
22 changed files with 1994 additions and 213 deletions
282
server/api/helcim/subscription.post.js
Normal file
282
server/api/helcim/subscription.post.js
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
// Create a Helcim subscription
|
||||
import { getHelcimPlanId, requiresPayment } from '../../config/contributions.js'
|
||||
import Member from '../../models/member.js'
|
||||
import { connectDB } from '../../utils/mongoose.js'
|
||||
|
||||
const HELCIM_API_BASE = 'https://api.helcim.com/v2'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
await connectDB()
|
||||
const config = useRuntimeConfig(event)
|
||||
const body = await readBody(event)
|
||||
|
||||
// Validate required fields
|
||||
if (!body.customerId || !body.contributionTier) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Customer ID and contribution tier are required'
|
||||
})
|
||||
}
|
||||
|
||||
if (!body.customerCode) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Customer code is required for subscription creation'
|
||||
})
|
||||
}
|
||||
|
||||
console.log('Subscription request body:', body)
|
||||
|
||||
// Check if payment is required
|
||||
if (!requiresPayment(body.contributionTier)) {
|
||||
console.log('No payment required for tier:', body.contributionTier)
|
||||
// For free tier, just update member status
|
||||
const member = await Member.findOneAndUpdate(
|
||||
{ helcimCustomerId: body.customerId },
|
||||
{
|
||||
status: 'active',
|
||||
contributionTier: body.contributionTier,
|
||||
subscriptionStartDate: new Date()
|
||||
},
|
||||
{ new: true }
|
||||
)
|
||||
|
||||
console.log('Updated member for free tier:', member)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
subscription: null,
|
||||
member
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Payment required for tier:', body.contributionTier)
|
||||
|
||||
// Get the Helcim plan ID
|
||||
const planId = getHelcimPlanId(body.contributionTier)
|
||||
console.log('Plan ID for tier:', planId)
|
||||
|
||||
// Validate card token is provided
|
||||
if (!body.cardToken) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Payment information is required for this contribution tier'
|
||||
})
|
||||
}
|
||||
|
||||
// Check if we have a configured plan for this tier
|
||||
if (!planId) {
|
||||
console.log('No Helcim plan configured for tier:', body.contributionTier)
|
||||
|
||||
const member = await Member.findOneAndUpdate(
|
||||
{ helcimCustomerId: body.customerId },
|
||||
{
|
||||
status: 'active',
|
||||
contributionTier: body.contributionTier,
|
||||
subscriptionStartDate: new Date(),
|
||||
paymentMethod: 'card',
|
||||
cardToken: body.cardToken,
|
||||
notes: `Payment successful but no Helcim plan configured for tier ${body.contributionTier}`
|
||||
},
|
||||
{ new: true }
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
subscription: {
|
||||
subscriptionId: 'manual-' + Date.now(),
|
||||
status: 'needs_plan_setup',
|
||||
nextBillingDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
|
||||
},
|
||||
member,
|
||||
warning: `Payment successful but recurring plan needs to be set up in Helcim for the ${body.contributionTier} tier`
|
||||
}
|
||||
}
|
||||
|
||||
// Try to create subscription in Helcim
|
||||
const helcimToken = config.public.helcimToken || process.env.NUXT_PUBLIC_HELCIM_TOKEN
|
||||
|
||||
console.log('Attempting to create Helcim subscription with plan ID:', planId)
|
||||
|
||||
// Generate a proper alphanumeric idempotency key (exactly 25 characters)
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
let idempotencyKey = ''
|
||||
for (let i = 0; i < 25; i++) {
|
||||
idempotencyKey += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
|
||||
// Get contribution tier details to set recurring amount
|
||||
const { getContributionTierByValue } = await import('../../config/contributions.js')
|
||||
const tierInfo = getContributionTierByValue(body.contributionTier)
|
||||
|
||||
const requestBody = {
|
||||
subscriptions: [{
|
||||
dateActivated: new Date().toISOString().split('T')[0], // Today in YYYY-MM-DD format
|
||||
paymentPlanId: parseInt(planId),
|
||||
customerCode: body.customerCode,
|
||||
recurringAmount: parseFloat(tierInfo.amount),
|
||||
paymentMethod: 'card'
|
||||
}]
|
||||
}
|
||||
const requestHeaders = {
|
||||
'accept': 'application/json',
|
||||
'content-type': 'application/json',
|
||||
'api-token': helcimToken,
|
||||
'idempotency-key': idempotencyKey
|
||||
}
|
||||
|
||||
console.log('Subscription request body:', requestBody)
|
||||
console.log('Request headers:', requestHeaders)
|
||||
console.log('Request URL:', `${HELCIM_API_BASE}/subscriptions`)
|
||||
|
||||
try {
|
||||
const subscriptionResponse = await fetch(`${HELCIM_API_BASE}/subscriptions`, {
|
||||
method: 'POST',
|
||||
headers: requestHeaders,
|
||||
body: JSON.stringify(requestBody)
|
||||
})
|
||||
|
||||
if (!subscriptionResponse.ok) {
|
||||
const errorText = await subscriptionResponse.text()
|
||||
console.error('Subscription creation failed:')
|
||||
console.error('Status:', subscriptionResponse.status)
|
||||
console.error('Status Text:', subscriptionResponse.statusText)
|
||||
console.error('Headers:', Object.fromEntries(subscriptionResponse.headers.entries()))
|
||||
console.error('Response Body:', errorText)
|
||||
console.error('Request was:', {
|
||||
url: `${HELCIM_API_BASE}/subscriptions`,
|
||||
method: 'POST',
|
||||
body: requestBody,
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'content-type': 'application/json',
|
||||
'api-token': helcimToken ? 'present' : 'missing'
|
||||
}
|
||||
})
|
||||
|
||||
// If it's a validation error, let's try to get more info about available plans
|
||||
if (subscriptionResponse.status === 400 || subscriptionResponse.status === 404) {
|
||||
console.log('Plan might not exist. Trying to get list of available payment plans...')
|
||||
|
||||
// Try to fetch available payment plans
|
||||
try {
|
||||
const plansResponse = await fetch(`${HELCIM_API_BASE}/payment-plans`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'api-token': helcimToken
|
||||
}
|
||||
})
|
||||
|
||||
if (plansResponse.ok) {
|
||||
const plansData = await plansResponse.json()
|
||||
console.log('Available payment plans:', JSON.stringify(plansData, null, 2))
|
||||
} else {
|
||||
console.log('Could not fetch payment plans:', plansResponse.status, plansResponse.statusText)
|
||||
}
|
||||
} catch (planError) {
|
||||
console.log('Error fetching payment plans:', planError.message)
|
||||
}
|
||||
|
||||
// For now, just update member status and let user know we need to configure plans
|
||||
const member = await Member.findOneAndUpdate(
|
||||
{ helcimCustomerId: body.customerId },
|
||||
{
|
||||
status: 'active',
|
||||
contributionTier: body.contributionTier,
|
||||
subscriptionStartDate: new Date(),
|
||||
paymentMethod: 'card',
|
||||
cardToken: body.cardToken,
|
||||
notes: `Payment successful but subscription creation failed: ${errorText}`
|
||||
},
|
||||
{ new: true }
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
subscription: {
|
||||
subscriptionId: 'manual-' + Date.now(),
|
||||
status: 'needs_setup',
|
||||
error: errorText,
|
||||
nextBillingDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
|
||||
},
|
||||
member,
|
||||
warning: 'Payment successful but recurring subscription needs manual setup'
|
||||
}
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: subscriptionResponse.status,
|
||||
statusMessage: `Failed to create subscription: ${errorText}`
|
||||
})
|
||||
}
|
||||
|
||||
const subscriptionData = await subscriptionResponse.json()
|
||||
console.log('Subscription created successfully:', subscriptionData)
|
||||
|
||||
// Extract the first subscription from the response array
|
||||
const subscription = subscriptionData.data?.[0]
|
||||
if (!subscription) {
|
||||
throw new Error('No subscription returned in response')
|
||||
}
|
||||
|
||||
// Update member in database
|
||||
const member = await Member.findOneAndUpdate(
|
||||
{ helcimCustomerId: body.customerId },
|
||||
{
|
||||
status: 'active',
|
||||
contributionTier: body.contributionTier,
|
||||
helcimSubscriptionId: subscription.id,
|
||||
subscriptionStartDate: new Date(),
|
||||
paymentMethod: 'card'
|
||||
},
|
||||
{ new: true }
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
subscription: {
|
||||
subscriptionId: subscription.id,
|
||||
status: subscription.status,
|
||||
nextBillingDate: subscription.nextBillingDate
|
||||
},
|
||||
member
|
||||
}
|
||||
} catch (fetchError) {
|
||||
console.error('Error during subscription creation:', fetchError)
|
||||
|
||||
// Still mark member as active since payment was successful
|
||||
const member = await Member.findOneAndUpdate(
|
||||
{ helcimCustomerId: body.customerId },
|
||||
{
|
||||
status: 'active',
|
||||
contributionTier: body.contributionTier,
|
||||
subscriptionStartDate: new Date(),
|
||||
paymentMethod: 'card',
|
||||
cardToken: body.cardToken,
|
||||
notes: `Payment successful but subscription creation failed: ${fetchError.message}`
|
||||
},
|
||||
{ new: true }
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
subscription: {
|
||||
subscriptionId: 'manual-' + Date.now(),
|
||||
status: 'needs_setup',
|
||||
error: fetchError.message,
|
||||
nextBillingDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
|
||||
},
|
||||
member,
|
||||
warning: 'Payment successful but recurring subscription needs manual setup'
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating Helcim subscription:', error)
|
||||
throw createError({
|
||||
statusCode: error.statusCode || 500,
|
||||
statusMessage: error.message || 'Failed to create subscription'
|
||||
})
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue