puffin-app/server/utils/nocodbClient.js

102 lines
2.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 Client for Server
* Simple REST API client for NocoDB operations
*/
class NocoDBClient {
constructor() {
this.config = {
baseUrl: process.env.NOCODB_BASE_URL || '',
baseId: process.env.NOCODB_BASE_ID || '',
apiKey: process.env.NOCODB_API_KEY || '',
ordersTableId: process.env.NOCODB_ORDERS_TABLE_ID || '',
};
if (!this.config.baseUrl || !this.config.baseId || !this.config.apiKey || !this.config.ordersTableId) {
console.warn('⚠️ NocoDB configuration incomplete. Database integration disabled.');
}
this.baseUrl = `${this.config.baseUrl}/api/v2/tables/${this.config.ordersTableId}/records`;
}
/**
* Make authenticated request to NocoDB
*/
async request(endpoint, options = {}) {
const url = endpoint.startsWith('http') ? endpoint : `${this.baseUrl}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
'xc-token': this.config.apiKey,
'Content-Type': 'application/json',
...options.headers,
},
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`NocoDB request failed: ${response.status} - ${errorText}`);
}
return response.json();
}
/**
* Create new order record
*/
async createOrder(orderData) {
return this.request('', {
method: 'POST',
body: JSON.stringify(orderData),
});
}
/**
* Update order fields
*/
async updateOrder(recordId, data) {
return this.request(`/${recordId}`, {
method: 'PATCH',
body: JSON.stringify(data),
});
}
/**
* Find order by orderId field
*/
async findOrderByOrderId(orderId) {
const params = new URLSearchParams();
params.append('where', `(orderId,eq,${orderId})`);
params.append('limit', '1');
const response = await this.request(`?${params.toString()}`);
return response.list?.[0] || null;
}
/**
* Update order with Wren fulfillment data
*/
async updateOrderFulfillment(orderId, fulfillmentData) {
// First find the NocoDB record ID
const order = await this.findOrderByOrderId(orderId);
if (!order) {
throw new Error(`Order not found in NocoDB: ${orderId}`);
}
// Update the record
return this.updateOrder(order.Id, fulfillmentData);
}
/**
* Check if NocoDB is configured
*/
isConfigured() {
return !!(this.config.baseUrl && this.config.baseId && this.config.apiKey && this.config.ordersTableId);
}
}
// Export singleton instance
export const nocodbClient = new NocoDBClient();