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>
8.3 KiB
8.3 KiB
NocoDB Orders Table Schema
Proposed Schema (Updated 2025-11-03)
Core Identification
| Field | Type | Required | Description |
|---|---|---|---|
Id |
Number | Yes (auto) | NocoDB record ID |
CreatedAt |
DateTime | Yes (auto) | Record creation timestamp |
UpdatedAt |
DateTime | Yes (auto) | Record last update timestamp |
orderId |
String | Yes | Unique order identifier (UUID) |
status |
Enum | Yes | Order status: pending, paid, fulfilled, cancelled |
source |
String | No | Order source: web, mobile-app, manual, api |
Payment Information
| Field | Type | Required | Description |
|---|---|---|---|
stripeSessionId |
String | No | Stripe Checkout Session ID |
stripePaymentIntent |
String | No | Stripe Payment Intent ID (for refunds) |
baseAmount |
String | Yes | Pre-fee amount in cents (e.g., "160174") |
processingFee |
String | Yes | Stripe processing fee in cents (e.g., "4805") |
totalAmount |
String | Yes | Total charged amount in cents (baseAmount + processingFee) |
currency |
String | Yes | Currency code: USD, EUR, GBP, CHF |
amountUSD |
String | No | Amount converted to USD for reporting |
paymentMethod |
String | No | Payment method type (e.g., "card", "bank_transfer") |
Carbon Offset Details
| Field | Type | Required | Description |
|---|---|---|---|
co2Tons |
String | Yes | Tons of CO2 offset (e.g., "6.73") |
portfolioId |
String | Yes | Wren portfolio ID (e.g., "37") |
portfolioName |
String | No | Human-readable portfolio name |
wrenOrderId |
String | No | Wren API order ID (populated after fulfillment) |
certificateUrl |
String | No | URL to offset certificate |
fulfilledAt |
DateTime | No | Timestamp when order was fulfilled with Wren |
Customer Information
| Field | Type | Required | Description |
|---|---|---|---|
customerName |
String | Yes | Customer display name (business or individual) |
customerEmail |
String | Yes | Customer email address |
customerPhone |
String | No | Customer phone (if phone collection enabled in Stripe) |
businessName |
String | No | Business name for B2B purchases |
stripeCustomerId |
String | No | Stripe Customer ID (for recurring customers) |
taxIdType |
String | No | Tax ID type (e.g., "eu_vat", "us_ein", "gb_vat") |
taxIdValue |
String | No | Tax identification number |
billingCity |
String | No | Billing address city |
billingCountry |
String | No | Billing address country code (e.g., "FR") |
billingLine1 |
String | No | Billing address line 1 |
billingLine2 |
String | No | Billing address line 2 |
billingPostalCode |
String | No | Billing address postal/zip code |
billingState |
String | No | Billing address state/region |
Vessel Information (Optional - for yacht calculations)
| Field | Type | Required | Description |
|---|---|---|---|
vesselName |
String | No | Name of vessel |
imoNumber |
String | No | IMO vessel identification number |
vesselType |
String | No | Type of vessel (e.g., "Motor Yacht") |
vesselLength |
String | No | Vessel length in meters |
Trip Details (Optional - for trip-based calculations)
| Field | Type | Required | Description |
|---|---|---|---|
departurePort |
String | No | Departure port name |
arrivalPort |
String | No | Arrival port name |
distance |
String | No | Distance in nautical miles |
avgSpeed |
String | No | Average speed in knots |
duration |
String | No | Trip duration in hours |
enginePower |
String | No | Engine power in horsepower |
Administrative
| Field | Type | Required | Description |
|---|---|---|---|
notes |
LongText | No | Internal admin notes |
Changes from Original Schema
✅ Added Fields
Payment & Stripe Integration:
stripeSessionId- Link to Stripe Checkout SessionstripePaymentIntent- Link to Stripe Payment IntentstripeCustomerId- Reusable Stripe Customer IDbaseAmount- Pre-fee amount from Stripe metadataprocessingFee- Stripe fee from metadata
Billing Address:
billingCity- From Stripe addressbillingCountry- From Stripe addressbillingLine1- From Stripe addressbillingLine2- From Stripe addressbillingPostalCode- From Stripe addressbillingState- From Stripe address
B2B Customer Support:
businessName- Business name for B2B purchasestaxIdType- Tax ID type (eu_vat, us_ein, gb_vat, etc.)taxIdValue- Tax identification number
⚠️ Modified Fields
customerPhone- Now optional, populated from Stripe if phone collection is enabledcustomerName- Now serves as display name (either business_name or individual_name)
❌ Removed Fields
customerCompany- Not provided by Stripe, can be added manually if neededpaymentReference- Redundant withstripePaymentIntent
⚠️ Made Optional
All vessel and trip fields are now optional since they only apply to specific order types (not all orders are for vessel trips).
Stripe Webhook Mapping
When receiving Stripe webhook checkout.session.completed:
{
// Payment Information
stripeSessionId: session.id,
stripePaymentIntent: session.payment_intent,
baseAmount: session.metadata.baseAmount, // in cents
processingFee: session.metadata.processingFee, // in cents
totalAmount: session.amount_total.toString(), // in cents
currency: session.currency,
paymentMethod: session.payment_method_types[0], // 'card', 'us_bank_account', etc.
// Carbon Offset Details
co2Tons: session.metadata.tons,
portfolioId: session.metadata.portfolioId,
// Customer Information
customerName: session.customer_details.name, // Display name (business or individual)
customerEmail: session.customer_details.email,
customerPhone: session.customer_details.phone, // if phone collection enabled
// Business Customer Fields (B2B)
businessName: session.customer_details.business_name, // For B2B purchases
stripeCustomerId: session.customer, // Reusable customer ID
// Tax Collection (if enabled)
taxIdType: session.customer_details.tax_ids?.[0]?.type, // 'eu_vat', 'us_ein', etc.
taxIdValue: session.customer_details.tax_ids?.[0]?.value, // Tax number
// Billing Address
billingCity: session.customer_details.address?.city,
billingCountry: session.customer_details.address?.country,
billingLine1: session.customer_details.address?.line1,
billingLine2: session.customer_details.address?.line2,
billingPostalCode: session.customer_details.address?.postal_code,
billingState: session.customer_details.address?.state,
// Order Status
status: 'paid'
}
Real-World Example (Business Purchase)
From actual Stripe payload evt_1SPPa3Pdj1mnVT5kscrqB21t:
{
stripeSessionId: "cs_test_b1HSYDGs73Ail2Vumu0qC3yu96ce9X4qnozsDr5hDwRndpZOsq8H47flLc",
stripePaymentIntent: "pi_3SPPa2Pdj1mnVT5k2qsmDiV1",
stripeCustomerId: "cus_TM7pU6vRGh0N5N",
baseAmount: "16023588", // $160,235.88
processingFee: "480708", // $4,807.08
totalAmount: "16504296", // $165,042.96
currency: "usd",
co2Tons: "673.26",
portfolioId: "37",
customerName: "LetsBe Solutions LLC", // Business name used as display name
customerEmail: "matt@letsbe.solutions",
customerPhone: "+33633219796",
businessName: "LetsBe Solutions LLC",
taxIdType: "eu_vat",
taxIdValue: "FRAB123456789",
billingLine1: "108 Avenue du Trois Septembre",
billingLine2: null,
billingCity: "Cap-d'Ail",
billingState: null,
billingPostalCode: "06320",
billingCountry: "FR",
paymentMethod: "card",
status: "paid"
}
Field Type Notes
Why String for numeric fields? NocoDB stores all custom fields as strings by default. Numeric calculations should be done in application code by parsing these strings. This prevents precision issues with currency and decimal values.
Date/DateTime fields:
CreatedAt,UpdatedAt,fulfilledAtuse NocoDB's DateTime type- ISO 8601 format:
2025-11-03T14:30:00.000Z
Enum constraints:
status: Must be one ofpending,paid,fulfilled,cancelledcurrency: Must be one ofUSD,EUR,GBP,CHFsource: Typicallyweb,mobile-app,manual, orapi