puffin-app/docs/NOCODB_SCHEMA.md
Matt dc4fc45c4f
All checks were successful
Build and Push Docker Images / docker (push) Successful in 2m28s
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

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 Session
  • stripePaymentIntent - Link to Stripe Payment Intent
  • stripeCustomerId - Reusable Stripe Customer ID
  • baseAmount - Pre-fee amount from Stripe metadata
  • processingFee - Stripe fee from metadata

Billing Address:

  • billingCity - From Stripe address
  • billingCountry - From Stripe address
  • billingLine1 - From Stripe address
  • billingLine2 - From Stripe address
  • billingPostalCode - From Stripe address
  • billingState - From Stripe address

B2B Customer Support:

  • businessName - Business name for B2B purchases
  • taxIdType - 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 enabled
  • customerName - Now serves as display name (either business_name or individual_name)

Removed Fields

  • customerCompany - Not provided by Stripe, can be added manually if needed
  • paymentReference - Redundant with stripePaymentIntent

⚠️ 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, fulfilledAt use NocoDB's DateTime type
  • ISO 8601 format: 2025-11-03T14:30:00.000Z

Enum constraints:

  • status: Must be one of pending, paid, fulfilled, cancelled
  • currency: Must be one of USD, EUR, GBP, CHF
  • source: Typically web, mobile-app, manual, or api