puffin-app/server/utils/nocodbMapper.js

134 lines
4.5 KiB
JavaScript
Raw Normal View History

Add NocoDB integration for order management with comprehensive Stripe webhook logging Features: - Complete NocoDB schema with 42 fields supporting B2B and B2C customers - Server-side NocoDB client (REST API integration) - Stripe session data mapper with automatic field mapping - Enhanced webhook handler with comprehensive logging - Automatic order creation in NocoDB after payment - Fulfillment data updates with Wren order IDs - Support for business customers (VAT/EIN, business names) - Complete billing address capture - Non-blocking error handling (webhook succeeds even if NocoDB fails) Files Added: - server/utils/nocodbClient.js - NocoDB REST API client - server/utils/nocodbMapper.js - Stripe to NocoDB data mapper - docs/NOCODB_SCHEMA.md - Complete field reference (42 columns) - docs/NOCODB_INTEGRATION_GUIDE.md - Testing and deployment guide - docs/TESTING_STRIPE_WEBHOOK.md - Webhook testing instructions - docs/STRIPE_INTEGRATION_SUMMARY.md - Project overview Files Modified: - server/routes/webhooks.js - Added NocoDB integration and enhanced logging - src/types.ts - Updated OrderRecord interface with new fields - src/api/nocodbClient.ts - Added createOrder() method - .env.example - Added NocoDB configuration template Schema includes: - Payment tracking (Stripe session/intent/customer IDs, amounts, fees) - Carbon offset details (tons, portfolio, Wren order ID) - Customer information (name, email, phone, business name) - Tax ID collection (VAT, EIN, etc.) - Complete billing address - Optional vessel/trip details for yacht calculations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 16:35:15 +01:00
/**
* NocoDB Order Mapper
* Maps Stripe Checkout Session data to NocoDB OrderRecord format
*/
/**
* Map Stripe session data to NocoDB OrderRecord format
* @param {Object} session - Stripe checkout session object
* @param {Object} order - Local database order object
* @returns {Object} NocoDB-compatible order record
*/
export function mapStripeSessionToNocoDBOrder(session, order) {
const metadata = session.metadata || {};
const customerDetails = session.customer_details || {};
const address = customerDetails.address || {};
const taxIds = customerDetails.tax_ids || [];
return {
// Order identification
orderId: order.id,
status: 'paid', // Will be updated to 'fulfilled' after Wren order
source: determineOrderSource(session),
// Payment information
stripeSessionId: session.id,
stripePaymentIntent: session.payment_intent || null,
stripeCustomerId: session.customer || null,
baseAmount: metadata.baseAmount || order.base_amount?.toString() || '0',
processingFee: metadata.processingFee || order.processing_fee?.toString() || '0',
totalAmount: session.amount_total?.toString() || order.total_amount?.toString() || '0',
currency: session.currency?.toUpperCase() || order.currency || 'USD',
amountUSD: calculateUSDAmount(session.amount_total, session.currency),
paymentMethod: session.payment_method_types?.[0] || null,
// Carbon offset details
co2Tons: metadata.tons || order.tons?.toString() || '0',
portfolioId: metadata.portfolioId || order.portfolio_id?.toString() || '',
portfolioName: null, // Will be populated later from Wren API
wrenOrderId: null, // Will be populated after fulfillment
certificateUrl: null, // Will be populated after fulfillment
fulfilledAt: null, // Will be set when order is fulfilled
// Customer information
customerName: customerDetails.name || 'Unknown',
customerEmail: customerDetails.email || order.customer_email || '',
customerPhone: customerDetails.phone || null,
businessName: customerDetails.business_name || null,
taxIdType: taxIds[0]?.type || null,
taxIdValue: taxIds[0]?.value || null,
// Billing address
billingLine1: address.line1 || null,
billingLine2: address.line2 || null,
billingCity: address.city || null,
billingState: address.state || null,
billingPostalCode: address.postal_code || null,
billingCountry: address.country || null,
// Vessel information (from metadata or order, if available)
vesselName: metadata.vesselName || order.vessel_name || null,
imoNumber: metadata.imoNumber || order.imo_number || null,
vesselType: metadata.vesselType || null,
vesselLength: metadata.vesselLength || null,
// Trip details (from metadata, if available)
departurePort: metadata.departurePort || null,
arrivalPort: metadata.arrivalPort || null,
distance: metadata.distance || null,
avgSpeed: metadata.avgSpeed || null,
duration: metadata.duration || null,
enginePower: metadata.enginePower || null,
// Administrative
notes: null,
};
}
/**
* Determine order source from session data
* @param {Object} session - Stripe checkout session object
* @returns {string} Order source
*/
function determineOrderSource(session) {
const metadata = session.metadata || {};
// Check if source is specified in metadata
if (metadata.source) {
return metadata.source;
}
// Check success URL for mobile app indicator
if (session.success_url?.includes('mobile-app')) {
return 'mobile-app';
}
// Default to web
return 'web';
}
/**
* Calculate USD amount for reporting
* @param {number} amount - Amount in cents
* @param {string} currency - Currency code
* @returns {string} USD amount in cents
*/
function calculateUSDAmount(amount, currency) {
if (!amount) return '0';
// If already USD, return as-is
if (currency?.toLowerCase() === 'usd') {
return amount.toString();
}
// TODO: Implement currency conversion using real-time rates
// For now, return the original amount
// In production, fetch rates from an API or use a conversion service
return amount.toString();
}
/**
* Update NocoDB order with Wren fulfillment data
* @param {string} orderId - Order ID
* @param {Object} wrenOrder - Wren API order response
* @returns {Object} Update data for NocoDB
*/
export function mapWrenFulfillmentData(orderId, wrenOrder) {
return {
wrenOrderId: wrenOrder.id,
certificateUrl: wrenOrder.certificate_url || null,
fulfilledAt: new Date().toISOString(),
status: 'fulfilled',
};
}