import { db } from '../config/database.js'; import { randomUUID } from 'crypto'; export class Order { /** * Create a new order in the database * @param {Object} orderData - Order data * @param {string} orderData.stripeSessionId - Stripe checkout session ID * @param {string} orderData.customerEmail - Customer email * @param {number} orderData.tons - Carbon offset tons * @param {number} orderData.portfolioId - Portfolio ID * @param {number} orderData.baseAmount - Base amount in cents * @param {number} orderData.processingFee - Processing fee in cents * @param {number} orderData.totalAmount - Total amount in cents * @param {string} orderData.currency - Currency code (default: USD) * @returns {Object} Created order */ static create({ stripeSessionId, customerEmail, tons, portfolioId, baseAmount, processingFee, totalAmount, currency = 'USD' }) { const id = randomUUID(); const now = new Date().toISOString(); const stmt = db.prepare(` INSERT INTO orders ( id, stripe_session_id, customer_email, tons, portfolio_id, base_amount, processing_fee, total_amount, currency, status, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?) `); stmt.run( id, stripeSessionId, customerEmail, tons, portfolioId, baseAmount, processingFee, totalAmount, currency, now, now ); return this.findById(id); } /** * Find order by ID * @param {string} id - Order ID * @returns {Object|null} Order or null */ static findById(id) { const stmt = db.prepare('SELECT * FROM orders WHERE id = ?'); return stmt.get(id); } /** * Find order by Stripe session ID * @param {string} sessionId - Stripe session ID * @returns {Object|null} Order or null */ static findBySessionId(sessionId) { const stmt = db.prepare('SELECT * FROM orders WHERE stripe_session_id = ?'); return stmt.get(sessionId); } /** * Update order status * @param {string} id - Order ID * @param {string} status - New status (pending, paid, fulfilled, failed) * @returns {Object} Updated order */ static updateStatus(id, status) { const now = new Date().toISOString(); const stmt = db.prepare('UPDATE orders SET status = ?, updated_at = ? WHERE id = ?'); stmt.run(status, now, id); return this.findById(id); } /** * Update order with payment intent ID * @param {string} id - Order ID * @param {string} paymentIntentId - Stripe payment intent ID * @returns {Object} Updated order */ static updatePaymentIntent(id, paymentIntentId) { const now = new Date().toISOString(); const stmt = db.prepare('UPDATE orders SET stripe_payment_intent = ?, updated_at = ? WHERE id = ?'); stmt.run(paymentIntentId, now, id); return this.findById(id); } /** * Update order with Wren order ID after fulfillment * @param {string} id - Order ID * @param {string} wrenOrderId - Wren API order ID * @param {string} status - Order status (fulfilled or failed) * @returns {Object} Updated order */ static updateWrenOrder(id, wrenOrderId, status = 'fulfilled') { const now = new Date().toISOString(); const stmt = db.prepare('UPDATE orders SET wren_order_id = ?, status = ?, updated_at = ? WHERE id = ?'); stmt.run(wrenOrderId, status, now, id); return this.findById(id); } /** * Atomically update payment intent and status * This prevents race conditions by updating both fields in a single transaction * @param {string} id - Order ID * @param {string} paymentIntentId - Stripe payment intent ID * @param {string} status - New status * @returns {Object} Updated order */ static updatePaymentAndStatus(id, paymentIntentId, status) { const now = new Date().toISOString(); const stmt = db.prepare(` UPDATE orders SET stripe_payment_intent = ?, status = ?, updated_at = ? WHERE id = ? `); stmt.run(paymentIntentId, status, now, id); return this.findById(id); } /** * Get all orders (with optional filters) * @param {Object} filters - Filter options * @param {string} filters.status - Filter by status * @param {number} filters.limit - Limit results * @param {number} filters.offset - Offset for pagination * @returns {Array} Array of orders */ static findAll({ status, limit = 100, offset = 0 } = {}) { let query = 'SELECT * FROM orders'; const params = []; if (status) { query += ' WHERE status = ?'; params.push(status); } query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'; params.push(limit, offset); const stmt = db.prepare(query); return stmt.all(...params); } /** * Get order count by status * @returns {Object} Count by status */ static getStatusCounts() { const stmt = db.prepare('SELECT status, COUNT(*) as count FROM orders GROUP BY status'); const rows = stmt.all(); return rows.reduce((acc, row) => { acc[row.status] = row.count; return acc; }, {}); } /** * Delete order (for testing purposes only) * @param {string} id - Order ID * @returns {boolean} Success */ static delete(id) { const stmt = db.prepare('DELETE FROM orders WHERE id = ?'); const result = stmt.run(id); return result.changes > 0; } } export default Order;