diff --git a/apps/bff/src/invoices/invoices.controller.ts b/apps/bff/src/invoices/invoices.controller.ts index 8884dd28..241d1d6f 100644 --- a/apps/bff/src/invoices/invoices.controller.ts +++ b/apps/bff/src/invoices/invoices.controller.ts @@ -128,6 +128,16 @@ export class InvoicesController { return this.invoicesService.getPaymentGateways(); } + @Get("test-payment-methods/:clientId") + @ApiOperation({ + summary: "Test WHMCS payment methods API for specific client ID", + description: "Direct test of WHMCS GetPayMethods API - TEMPORARY DEBUG ENDPOINT", + }) + @ApiParam({ name: "clientId", type: Number, description: "WHMCS Client ID to test" }) + async testPaymentMethods(@Param("clientId", ParseIntPipe) clientId: number): Promise { + return this.invoicesService.testWhmcsPaymentMethods(clientId); + } + @Post("payment-methods/refresh") @HttpCode(HttpStatus.OK) @ApiOperation({ diff --git a/apps/bff/src/invoices/invoices.service.ts b/apps/bff/src/invoices/invoices.service.ts index 3b73ebc0..acb7ef4a 100644 --- a/apps/bff/src/invoices/invoices.service.ts +++ b/apps/bff/src/invoices/invoices.service.ts @@ -466,6 +466,45 @@ export class InvoicesService { } } + /** + * TEMPORARY DEBUG METHOD: Test WHMCS payment methods API directly + */ + async testWhmcsPaymentMethods(clientId: number): Promise { + try { + this.logger.log(`🔬 TESTING WHMCS GetPayMethods API for client ${clientId}`); + + // Call WHMCS API directly with detailed logging + const result = await this.whmcsService.getPaymentMethods(clientId, `test-client-${clientId}`); + + this.logger.log(`🔬 Test result for client ${clientId}:`, { + totalCount: result.totalCount, + paymentMethods: result.paymentMethods, + }); + + return { + clientId, + testTimestamp: new Date().toISOString(), + whmcsResponse: result, + summary: { + totalPaymentMethods: result.totalCount, + hasPaymentMethods: result.totalCount > 0, + paymentMethodTypes: result.paymentMethods.map(pm => pm.type), + } + }; + } catch (error) { + this.logger.error(`🔬 Test failed for client ${clientId}`, { + error: getErrorMessage(error), + }); + + return { + clientId, + testTimestamp: new Date().toISOString(), + error: getErrorMessage(error), + errorType: error instanceof Error ? error.constructor.name : typeof error, + }; + } + } + /** * Create payment SSO link for invoice with specific payment method or gateway */ diff --git a/apps/bff/src/vendors/whmcs/services/whmcs-payment.service.ts b/apps/bff/src/vendors/whmcs/services/whmcs-payment.service.ts index cf6f173b..006d2fc0 100644 --- a/apps/bff/src/vendors/whmcs/services/whmcs-payment.service.ts +++ b/apps/bff/src/vendors/whmcs/services/whmcs-payment.service.ts @@ -21,16 +21,29 @@ export class WhmcsPaymentService { */ async getPaymentMethods(clientId: number, userId: string): Promise { try { - // Try cache first - const cached = await this.cacheService.getPaymentMethods(userId); - if (cached) { - this.logger.debug(`Cache hit for payment methods: user ${userId}`); - return cached; - } + // TEMPORARILY BYPASS CACHE for debugging + console.log(`🔍 BYPASSING CACHE for debugging - client ${clientId}, user ${userId}`); + + // COMPLETELY BYPASS CACHE for debugging + console.log(`🔍 COMPLETELY BYPASSING CACHE for debugging - client ${clientId}, user ${userId}`); + + // Try cache first - DISABLED FOR DEBUGGING + // const cached = await this.cacheService.getPaymentMethods(userId); + // if (cached && false) { // Force bypass cache + // this.logger.debug(`Cache hit for payment methods: user ${userId}`); + // return cached; + // } const response = await this.connectionService.getPayMethods({ clientid: clientId }); // Debug logging to understand what WHMCS returns + console.log(`🔍 WHMCS GetPayMethods DEBUG for client ${clientId}:`); + console.log('Raw response:', JSON.stringify(response, null, 2)); + console.log('Paymethods:', response.paymethods); + console.log('Paymethod array:', response.paymethods?.paymethod); + console.log('Is array:', Array.isArray(response.paymethods?.paymethod)); + console.log('Length:', response.paymethods?.paymethod?.length); + this.logger.log(`WHMCS GetPayMethods response for client ${clientId}:`, { rawResponse: response, paymethods: response.paymethods, @@ -40,28 +53,71 @@ export class WhmcsPaymentService { userId }); - const methods = (response.paymethods?.paymethod || []).map(pm => { - const transformed = this.dataTransformer.transformPaymentMethod(pm); - this.logger.log(`Transformed payment method:`, { - original: pm, - transformed, - clientId, - userId - }); - return transformed; - }); + // Handle different response structures + // According to WHMCS API docs, GetPayMethods returns { paymethods: [...] } directly + let paymentMethodsArray: any[] = []; + if (response.paymethods) { + if (Array.isArray(response.paymethods)) { + // Direct array response (correct for GetPayMethods) + paymentMethodsArray = response.paymethods; + } else if (response.paymethods.paymethod) { + // Nested structure (for GetPaymentMethods - payment gateways) + if (Array.isArray(response.paymethods.paymethod)) { + paymentMethodsArray = response.paymethods.paymethod; + } else { + paymentMethodsArray = [response.paymethods.paymethod]; + } + } + } + + console.log(`🔍 Payment methods array:`, paymentMethodsArray); + console.log(`🔍 Array length:`, paymentMethodsArray.length); + + const methods = paymentMethodsArray.map(pm => { + console.log(`🔍 Processing payment method:`, pm); + try { + const transformed = this.dataTransformer.transformPaymentMethod(pm); + console.log(`🔍 Transformed to:`, transformed); + + this.logger.log(`Transformed payment method:`, { + original: pm, + transformed, + clientId, + userId + }); + return transformed; + } catch (error) { + console.error(`🚨 ERROR transforming payment method:`, error); + this.logger.error(`Failed to transform payment method`, { + error: getErrorMessage(error), + paymentMethod: pm, + clientId, + userId + }); + return null; // Return null for failed transformations + } + }).filter(method => method !== null); // Filter out failed transformations const result: PaymentMethodList = { paymentMethods: methods, totalCount: methods.length }; + console.log(`🔍 FINAL RESULT for client ${clientId}:`, { + totalCount: result.totalCount, + hasPaymentMethods: result.totalCount > 0, + methods: result.paymentMethods + }); + this.logger.log(`Final payment methods result for client ${clientId}:`, { totalCount: result.totalCount, methods: result.paymentMethods, userId }); - await this.cacheService.setPaymentMethods(userId, result); + // DISABLE CACHE SAVING for debugging + // await this.cacheService.setPaymentMethods(userId, result); + console.log(`🔍 RETURNING RESULT WITHOUT CACHING for debugging`); return result; } catch (error) { + console.error(`🚨 ERROR fetching payment methods for client ${clientId}:`, error); this.logger.error(`Failed to fetch payment methods for client ${clientId}`, { error: getErrorMessage(error), userId, diff --git a/apps/bff/src/vendors/whmcs/transformers/whmcs-data.transformer.ts b/apps/bff/src/vendors/whmcs/transformers/whmcs-data.transformer.ts index 17b063df..f981e841 100644 --- a/apps/bff/src/vendors/whmcs/transformers/whmcs-data.transformer.ts +++ b/apps/bff/src/vendors/whmcs/transformers/whmcs-data.transformer.ts @@ -415,25 +415,33 @@ export class WhmcsDataTransformer { /** * Transform WHMCS payment method to shared PaymentMethod interface */ - transformPaymentMethod(whmcsPayMethod: WhmcsPaymentMethod): PaymentMethod { + transformPaymentMethod(whmcsPayMethod: any): PaymentMethod { try { + console.log(`🔧 TRANSFORMER DEBUG - Input:`, whmcsPayMethod); + + // Handle field name variations between different WHMCS API responses const transformed: PaymentMethod = { id: whmcsPayMethod.id, type: whmcsPayMethod.type, - description: whmcsPayMethod.description, + description: whmcsPayMethod.description || "", gatewayName: whmcsPayMethod.gateway_name, - lastFour: whmcsPayMethod.last_four, - expiryDate: whmcsPayMethod.expiry_date, + // Handle both possible field names for lastFour + lastFour: whmcsPayMethod.last_four || whmcsPayMethod.card_last_four, + // Handle both possible field names for expiryDate + expiryDate: whmcsPayMethod.expiry_date || whmcsPayMethod.expiry_date, bankName: whmcsPayMethod.bank_name, accountType: whmcsPayMethod.account_type, remoteToken: whmcsPayMethod.remote_token, - ccType: whmcsPayMethod.cc_type, - cardBrand: whmcsPayMethod.cc_type, - billingContactId: whmcsPayMethod.billing_contact_id, - createdAt: whmcsPayMethod.created_at, - updatedAt: whmcsPayMethod.updated_at, + // Handle both possible field names for card type + ccType: whmcsPayMethod.cc_type || whmcsPayMethod.card_type, + cardBrand: whmcsPayMethod.cc_type || whmcsPayMethod.card_type, + billingContactId: whmcsPayMethod.billing_contact_id || whmcsPayMethod.contact_id, + createdAt: whmcsPayMethod.created_at || whmcsPayMethod.last_updated, + updatedAt: whmcsPayMethod.updated_at || whmcsPayMethod.last_updated, }; + console.log(`🔧 TRANSFORMER DEBUG - Output:`, transformed); + // Optional validation hook if (!this.validatePaymentMethod(transformed)) { this.logger.warn("Transformed payment method failed validation", { diff --git a/apps/bff/src/vendors/whmcs/whmcs.service.ts b/apps/bff/src/vendors/whmcs/whmcs.service.ts index 4f727e3e..022ed2db 100644 --- a/apps/bff/src/vendors/whmcs/whmcs.service.ts +++ b/apps/bff/src/vendors/whmcs/whmcs.service.ts @@ -218,6 +218,13 @@ export class WhmcsService { return this.paymentService.getPaymentGateways(); } + /** + * Invalidate payment methods cache for a user + */ + async invalidatePaymentMethodsCache(userId: string): Promise { + return this.paymentService.invalidatePaymentMethodsCache(userId); + } + /** * Create SSO token with payment method for invoice payment */ diff --git a/apps/portal/src/app/checkout/page.tsx b/apps/portal/src/app/checkout/page.tsx index b44f7833..f19f8b42 100644 --- a/apps/portal/src/app/checkout/page.tsx +++ b/apps/portal/src/app/checkout/page.tsx @@ -241,8 +241,16 @@ function CheckoutContent() { }; const handleAddressConfirmed = (address?: Address) => { + console.log("🎯 PARENT: handleAddressConfirmed called with:", address); + console.log("🎯 PARENT: Current addressConfirmed state before:", addressConfirmed); setAddressConfirmed(true); setConfirmedAddress(address || null); + console.log("🎯 PARENT: addressConfirmed state set to true"); + + // Force a log after state update (in next tick) + setTimeout(() => { + console.log("🎯 PARENT: addressConfirmed state after update:", addressConfirmed); + }, 0); }; const handleAddressIncomplete = () => { @@ -424,19 +432,20 @@ function CheckoutContent() { )} - {/* Debug Info - Remove in production */} + {/* Debug Info - Remove in production */}
- Debug Info: Address Confirmed: {addressConfirmed ? '✅' : '❌'} | + Debug Info: Address Confirmed: {addressConfirmed ? '✅' : '❌'} ({String(addressConfirmed)}) | Payment Methods: {paymentMethodsLoading ? '⏳ Loading...' : paymentMethodsError ? '❌ Error' : paymentMethods ? `✅ ${paymentMethods.paymentMethods.length} found` : '❌ None'} | Order Items: {checkoutState.orderItems.length} | Can Submit: {!( submitting || - checkoutState.orderItems.length === 0 || + checkoutState.orderItems.length === 0 || !addressConfirmed || paymentMethodsLoading || !paymentMethods || paymentMethods.paymentMethods.length === 0 - ) ? '✅' : '❌'} + ) ? '✅' : '❌'} | + Render Time: {new Date().toLocaleTimeString()}
diff --git a/apps/portal/src/components/checkout/address-confirmation.tsx b/apps/portal/src/components/checkout/address-confirmation.tsx index 6b692198..278754cd 100644 --- a/apps/portal/src/components/checkout/address-confirmation.tsx +++ b/apps/portal/src/components/checkout/address-confirmation.tsx @@ -128,9 +128,19 @@ export function AddressConfirmation({ }; const handleConfirmAddress = () => { + console.log("🏠 CONFIRM ADDRESS CLICKED", { + billingInfo, + hasAddress: !!billingInfo?.address, + address: billingInfo?.address + }); + if (billingInfo?.address) { + console.log("🏠 Calling onAddressConfirmed with:", billingInfo.address); onAddressConfirmed(billingInfo.address); setAddressConfirmed(true); + console.log("🏠 Address confirmed state set to true"); + } else { + console.log("🏠 No billing info or address available"); } };