All checks were successful
Build and Push Docker Images / docker (push) Successful in 2m28s
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>
134 lines
4.5 KiB
JavaScript
134 lines
4.5 KiB
JavaScript
/**
|
|
* 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',
|
|
};
|
|
}
|