201 lines
5.8 KiB
JavaScript
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,
|
|
}
|
|
}
|