Adding features
This commit is contained in:
parent
600fef2b7c
commit
2b55ca4104
75 changed files with 9796 additions and 2759 deletions
354
server/api/members/update-contribution.post.js
Normal file
354
server/api/members/update-contribution.post.js
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
// Update member's contribution tier
|
||||
import jwt from "jsonwebtoken";
|
||||
import {
|
||||
getHelcimPlanId,
|
||||
requiresPayment,
|
||||
isValidContributionValue,
|
||||
} 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);
|
||||
const token = getCookie(event, "auth-token");
|
||||
|
||||
if (!token) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "Not authenticated",
|
||||
});
|
||||
}
|
||||
|
||||
// Decode JWT token
|
||||
let decoded;
|
||||
try {
|
||||
decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
} catch (err) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "Invalid or expired token",
|
||||
});
|
||||
}
|
||||
|
||||
// Validate contribution tier
|
||||
if (
|
||||
!body.contributionTier ||
|
||||
!isValidContributionValue(body.contributionTier)
|
||||
) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid contribution tier",
|
||||
});
|
||||
}
|
||||
|
||||
// Get member
|
||||
const member = await Member.findById(decoded.memberId);
|
||||
if (!member) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: "Member not found",
|
||||
});
|
||||
}
|
||||
|
||||
const oldTier = member.contributionTier;
|
||||
const newTier = body.contributionTier;
|
||||
|
||||
// If same tier, nothing to do
|
||||
if (oldTier === newTier) {
|
||||
return {
|
||||
success: true,
|
||||
message: "Already on this tier",
|
||||
member,
|
||||
};
|
||||
}
|
||||
|
||||
const helcimToken =
|
||||
config.public.helcimToken || process.env.NUXT_PUBLIC_HELCIM_TOKEN;
|
||||
const oldRequiresPayment = requiresPayment(oldTier);
|
||||
const newRequiresPayment = requiresPayment(newTier);
|
||||
|
||||
// Case 1: Moving from free to paid tier
|
||||
if (!oldRequiresPayment && newRequiresPayment) {
|
||||
// Check if member has Helcim customer ID with saved payment method
|
||||
if (!member.helcimCustomerId) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage:
|
||||
"Please use the subscription creation flow to upgrade to a paid tier",
|
||||
data: { requiresPaymentSetup: true },
|
||||
});
|
||||
}
|
||||
|
||||
// Try to fetch customer info from Helcim to check for saved cards
|
||||
const helcimToken =
|
||||
config.public.helcimToken || process.env.NUXT_PUBLIC_HELCIM_TOKEN;
|
||||
|
||||
try {
|
||||
const customerResponse = await fetch(
|
||||
`${HELCIM_API_BASE}/customers/${member.helcimCustomerId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"api-token": helcimToken,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!customerResponse.ok) {
|
||||
throw new Error("Failed to fetch customer info");
|
||||
}
|
||||
|
||||
const customerData = await customerResponse.json();
|
||||
const customerCode = customerData.customerCode;
|
||||
|
||||
if (!customerCode) {
|
||||
throw new Error("No customer code found");
|
||||
}
|
||||
|
||||
// Check if customer has saved cards
|
||||
const cardsResponse = await fetch(
|
||||
`${HELCIM_API_BASE}/card-terminals?customerId=${member.helcimCustomerId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"api-token": helcimToken,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
let hasCards = false;
|
||||
if (cardsResponse.ok) {
|
||||
const cardsData = await cardsResponse.json();
|
||||
hasCards = cardsData.cards && cardsData.cards.length > 0;
|
||||
}
|
||||
|
||||
if (!hasCards) {
|
||||
throw new Error("No saved payment methods");
|
||||
}
|
||||
|
||||
// Create new subscription with saved payment method
|
||||
const newPlanId = getHelcimPlanId(newTier);
|
||||
|
||||
if (!newPlanId) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: `Plan not configured for tier ${newTier}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Generate idempotency key
|
||||
const chars =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
let idempotencyKey = "";
|
||||
for (let i = 0; i < 25; i++) {
|
||||
idempotencyKey += chars.charAt(
|
||||
Math.floor(Math.random() * chars.length),
|
||||
);
|
||||
}
|
||||
|
||||
// Get tier amount
|
||||
const { getContributionTierByValue } = await import(
|
||||
"../../config/contributions.js"
|
||||
);
|
||||
const tierInfo = getContributionTierByValue(newTier);
|
||||
|
||||
const subscriptionResponse = await fetch(
|
||||
`${HELCIM_API_BASE}/subscriptions`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"api-token": helcimToken,
|
||||
"idempotency-key": idempotencyKey,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
subscriptions: [
|
||||
{
|
||||
dateActivated: new Date().toISOString().split("T")[0],
|
||||
paymentPlanId: parseInt(newPlanId),
|
||||
customerCode: customerCode,
|
||||
recurringAmount: parseFloat(tierInfo.amount),
|
||||
paymentMethod: "card",
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
if (!subscriptionResponse.ok) {
|
||||
const errorText = await subscriptionResponse.text();
|
||||
console.error("Failed to create subscription:", errorText);
|
||||
throw new Error(`Failed to create subscription: ${errorText}`);
|
||||
}
|
||||
|
||||
const subscriptionData = await subscriptionResponse.json();
|
||||
const subscription = subscriptionData.data?.[0];
|
||||
|
||||
if (!subscription) {
|
||||
throw new Error("No subscription returned in response");
|
||||
}
|
||||
|
||||
// Update member record
|
||||
member.contributionTier = newTier;
|
||||
member.helcimSubscriptionId = subscription.id;
|
||||
member.paymentMethod = "card";
|
||||
member.status = "active";
|
||||
await member.save();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Successfully upgraded to paid tier",
|
||||
member,
|
||||
subscription: {
|
||||
subscriptionId: subscription.id,
|
||||
status: subscription.status,
|
||||
nextBillingDate: subscription.nextBillingDate,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error creating subscription with saved payment:", error);
|
||||
// If we can't use saved payment, require new payment setup
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage:
|
||||
"Payment information required. You'll be redirected to complete payment setup.",
|
||||
data: { requiresPaymentSetup: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Case 2: Moving from paid to free tier (cancel subscription)
|
||||
if (oldRequiresPayment && !newRequiresPayment) {
|
||||
if (member.helcimSubscriptionId) {
|
||||
try {
|
||||
// Cancel Helcim subscription
|
||||
const response = await fetch(
|
||||
`${HELCIM_API_BASE}/subscriptions/${member.helcimSubscriptionId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"api-token": helcimToken,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(
|
||||
"Failed to cancel Helcim subscription:",
|
||||
response.status,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error canceling Helcim subscription:", error);
|
||||
// Continue anyway - we'll update the member record
|
||||
}
|
||||
}
|
||||
|
||||
// Update member to free tier
|
||||
member.contributionTier = newTier;
|
||||
member.helcimSubscriptionId = null;
|
||||
member.paymentMethod = "none";
|
||||
await member.save();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Successfully downgraded to free tier",
|
||||
member,
|
||||
};
|
||||
}
|
||||
|
||||
// Case 3: Moving between paid tiers
|
||||
if (oldRequiresPayment && newRequiresPayment) {
|
||||
const newPlanId = getHelcimPlanId(newTier);
|
||||
|
||||
if (!newPlanId) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: `Plan not configured for tier ${newTier}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!member.helcimSubscriptionId) {
|
||||
// No subscription exists - they need to go through payment flow
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage:
|
||||
"Payment information required. You'll be redirected to complete payment setup.",
|
||||
data: { requiresPaymentSetup: true },
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Update subscription plan in Helcim
|
||||
const response = await fetch(
|
||||
`${HELCIM_API_BASE}/subscriptions/${member.helcimSubscriptionId}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"api-token": helcimToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
paymentPlanId: parseInt(newPlanId),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error(
|
||||
"Failed to update Helcim subscription:",
|
||||
response.status,
|
||||
errorText,
|
||||
);
|
||||
throw new Error(`Failed to update subscription: ${errorText}`);
|
||||
}
|
||||
|
||||
const subscriptionData = await response.json();
|
||||
|
||||
// Update member record
|
||||
member.contributionTier = newTier;
|
||||
await member.save();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Successfully updated contribution level",
|
||||
member,
|
||||
subscription: subscriptionData,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error updating Helcim subscription:", error);
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: error.message || "Failed to update subscription",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Case 4: Moving between free tiers (shouldn't happen but handle it)
|
||||
member.contributionTier = newTier;
|
||||
await member.save();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Successfully updated contribution level",
|
||||
member,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error updating contribution tier:", error);
|
||||
throw createError({
|
||||
statusCode: error.statusCode || 500,
|
||||
statusMessage: error.message || "Failed to update contribution tier",
|
||||
});
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue