ghostguild-org/app/composables/useMemberPayment.js

201 lines
5.8 KiB
JavaScript

/**
* Member Payment Management Composable
* Handles payment setup and subscription creation for pending payment members
*/
export const useMemberPayment = () => {
const { memberData, checkMemberStatus } = useAuth()
const { initializeHelcimPay, verifyPayment, cleanup: cleanupHelcimPay } =
useHelcimPay()
const isProcessingPayment = ref(false)
const paymentError = ref(null)
const paymentSuccess = ref(false)
const customerId = ref('')
const customerCode = ref('')
/**
* Initiate payment setup for a member with pending_payment status
* This is the main entry point called from "Complete Payment" buttons
*/
const initiatePaymentSetup = async () => {
isProcessingPayment.value = true
paymentError.value = null
paymentSuccess.value = false
try {
// Fast-path: when both Helcim ids are already cached on the member doc
// AND a card's on file, we can skip the paid getOrCreateCustomer round
// trip entirely and go straight to subscription creation.
const hasCachedHelcimIds = Boolean(
memberData.value?.helcimCustomerId && memberData.value?.helcimCustomerCode
)
let existing = null
let probedExistingCard = false
let cardToken = null
if (hasCachedHelcimIds) {
existing = await $fetch('/api/helcim/existing-card').catch((err) => {
console.warn('[payment] existing-card lookup failed, falling back to verify flow:', err)
return null
})
probedExistingCard = true
if (existing?.cardToken) {
customerId.value = memberData.value.helcimCustomerId
customerCode.value = memberData.value.helcimCustomerCode
cardToken = existing.cardToken
}
}
if (!cardToken) {
// Skip HelcimPay verify if a card's already on file — Helcim refuses
// to re-save it, breaking retries after a partial-failed signup.
const [, existingFromFull] = await Promise.all([
getOrCreateCustomer(),
probedExistingCard
? Promise.resolve(existing)
: $fetch('/api/helcim/existing-card').catch((err) => {
console.warn('[payment] existing-card lookup failed, falling back to verify flow:', err)
return null
}),
])
cardToken = existingFromFull?.cardToken || null
}
if (!cardToken) {
await initializeHelcimPay(
customerId.value,
customerCode.value,
0,
)
const paymentResult = await verifyPayment()
if (!paymentResult.success) {
throw new Error('Payment verification failed')
}
const verifyResult = await $fetch('/api/helcim/verify-payment', {
method: 'POST',
body: {
cardToken: paymentResult.cardToken,
customerId: customerId.value,
},
})
if (!verifyResult.success) {
throw new Error('Payment verification failed on backend')
}
cardToken = paymentResult.cardToken
}
const subscriptionResponse = await $fetch('/api/helcim/subscription', {
method: 'POST',
body: {
customerId: customerId.value,
customerCode: customerCode.value,
contributionAmount: memberData.value?.contributionAmount ?? 5,
cardToken,
},
})
if (!subscriptionResponse.success) {
throw new Error('Subscription creation failed')
}
paymentSuccess.value = true
await checkMemberStatus()
// Clear success message after 3 seconds
setTimeout(() => {
paymentSuccess.value = false
}, 3000)
return {
success: true,
message: 'Payment completed successfully!',
}
} catch (error) {
console.error('Payment setup error:', error)
paymentError.value =
error.message || 'Payment setup failed. Please try again.'
throw error
} finally {
isProcessingPayment.value = false
cleanupHelcimPay()
}
}
/**
* Get or create Helcim customer for member
*/
const getOrCreateCustomer = async () => {
try {
if (!memberData.value?.helcimCustomerId) {
// Create new customer
const customerResponse = await $fetch(
'/api/helcim/get-or-create-customer',
{
method: 'POST',
},
)
customerId.value = customerResponse.customerId
customerCode.value = customerResponse.customerCode
console.log(
'Created new Helcim customer:',
customerId.value,
)
} else {
// Get customer code from existing customer
const customerResponse = await $fetch(
'/api/helcim/customer-code',
)
customerId.value = customerResponse.customerId
customerCode.value = customerResponse.customerCode
console.log(
'Using existing Helcim customer:',
customerId.value,
)
}
} catch (error) {
console.error('Failed to get or create customer:', error)
throw new Error('Failed to initialize payment. Please try again.')
}
}
/**
* Complete payment from status banner
* Entry point for clicking "Complete Payment" from any page
*/
const completePayment = async () => {
try {
await initiatePaymentSetup()
return true
} catch (error) {
console.error('Payment failed:', error)
return false
}
}
const resetPaymentState = () => {
isProcessingPayment.value = false
paymentError.value = null
paymentSuccess.value = false
}
return {
isProcessingPayment: readonly(isProcessingPayment),
paymentError: readonly(paymentError),
paymentSuccess: readonly(paymentSuccess),
initiatePaymentSetup,
completePayment,
resetPaymentState,
}
}