// Create a Helcim subscription import { getHelcimPlanId, requiresPayment } from '../../config/contributions.js' import Member from '../../models/member.js' import { connectDB } from '../../utils/mongoose.js' import { getSlackService } from '../../utils/slack.ts' import { requireAuth } from '../../utils/auth.js' const HELCIM_API_BASE = 'https://api.helcim.com/v2' // Function to invite member to Slack async function inviteToSlack(member) { try { const slackService = getSlackService() if (!slackService) { console.warn('Slack service not configured, skipping invitation') return } console.log(`Processing Slack invitation for ${member.email}...`) const inviteResult = await slackService.inviteUserToSlack( member.email, member.name ) if (inviteResult.success) { // Update member record based on the actual result if (inviteResult.status === 'existing_user_added_to_channel' || inviteResult.status === 'user_already_in_channel' || inviteResult.status === 'new_user_invited_to_workspace') { member.slackInviteStatus = 'sent' member.slackUserId = inviteResult.userId member.slackInvited = true } else { // Manual invitation required member.slackInviteStatus = 'pending' member.slackInvited = false } await member.save() // Send notification to vetting channel await slackService.notifyNewMember( member.name, member.email, member.circle, member.contributionTier, inviteResult.status ) console.log(`Successfully processed Slack invitation for ${member.email}: ${inviteResult.status}`) } else { // Update member record to reflect failed invitation member.slackInviteStatus = 'failed' await member.save() console.error(`Failed to process Slack invitation for ${member.email}: ${inviteResult.error}`) // Don't throw error - subscription creation should still succeed } } catch (error) { console.error('Error during Slack invitation process:', error) // Update member record to reflect failed invitation try { member.slackInviteStatus = 'failed' await member.save() } catch (saveError) { console.error('Failed to update member Slack status:', saveError) } // Don't throw error - subscription creation should still succeed } } export default defineEventHandler(async (event) => { try { await requireAuth(event) await connectDB() const config = useRuntimeConfig(event) const body = await validateBody(event, helcimSubscriptionSchema) // Check if payment is required if (!requiresPayment(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 } ) // Send Slack invitation for free tier members await inviteToSlack(member) return { success: true, subscription: null, member: { id: member._id, email: member.email, name: member.name, circle: member.circle, contributionTier: member.contributionTier, status: member.status } } } // Get the Helcim plan ID const planId = getHelcimPlanId(body.contributionTier) // 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) { 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 } ) // Send Slack invitation even when no plan is configured await inviteToSlack(member) return { success: true, subscription: { subscriptionId: 'manual-' + Date.now(), status: 'needs_plan_setup', nextBillingDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) }, member: { id: member._id, email: member.email, name: member.name, circle: member.circle, contributionTier: member.contributionTier, status: member.status }, 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 // 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 } 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:', subscriptionResponse.status) // If it's a validation error, let's try to get more info about available plans if (subscriptionResponse.status === 400 || subscriptionResponse.status === 404) { // Plan might not exist -- update member status and proceed 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 } ) // Send Slack invitation even when subscription setup fails await inviteToSlack(member) return { success: true, subscription: { subscriptionId: 'manual-' + Date.now(), status: 'needs_setup', nextBillingDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) }, member: { id: member._id, email: member.email, name: member.name, circle: member.circle, contributionTier: member.contributionTier, status: member.status }, warning: 'Payment successful but recurring subscription needs manual setup' } } throw createError({ statusCode: subscriptionResponse.status, statusMessage: 'Subscription creation failed' }) } const subscriptionData = await subscriptionResponse.json() // 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 } ) // Send Slack invitation for paid tier members await inviteToSlack(member) return { success: true, subscription: { subscriptionId: subscription.id, status: subscription.status, nextBillingDate: subscription.nextBillingDate }, member: { id: member._id, email: member.email, name: member.name, circle: member.circle, contributionTier: member.contributionTier, status: member.status } } } 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 } ) // Send Slack invitation even when subscription fetch fails await inviteToSlack(member) return { success: true, subscription: { subscriptionId: 'manual-' + Date.now(), status: 'needs_setup', nextBillingDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) }, member: { id: member._id, email: member.email, name: member.name, circle: member.circle, contributionTier: member.contributionTier, status: member.status }, warning: 'Payment successful but recurring subscription needs manual setup' } } } catch (error) { if (error.statusCode) throw error console.error('Error creating Helcim subscription:', error) throw createError({ statusCode: 500, statusMessage: 'An unexpected error occurred' }) } })