Best Practices for Implementing Order and Payment Creation in Directus
Seamlessly integrate robust order and payment systems using Directus
Key Takeaways
- Structured Data Modeling: Define comprehensive collections to manage orders, products, and payments effectively.
- Secure Payment Gateway Integration: Implement reliable and secure payment processing using gateways like Stripe, with proper webhook handling.
- Automation and Workflow Management: Utilize Directus Flows to automate order processing and payment status updates for a seamless operation.
1. Define Your Data Model
a. Orders Collection
Establish a central orders collection to capture all order-related information. This collection should include:
id: Primary Key - Unique identifier for each order.user_id: Foreign Key - References theuserscollection to associate orders with customers.order_status: String - Tracks the current status of the order (e.g., pending, completed, failed).total_amount: Decimal - Represents the total payable amount for the order.payment_id: String - Stores the transaction ID from the payment gateway.created_at: Timestamp - Records the date and time when the order was created.updated_at: Timestamp - Tracks the last update time of the order.
b. Products Collection
The products collection should encapsulate all product-related details necessary for order processing:
id: Primary Key - Unique identifier for each product.name: String - Name of the product.price: Decimal - Price of the product.description: Text - Detailed description of the product.
c. Order Items Collection
Create an order_items collection to manage the individual items within each order:
id: Primary Key - Unique identifier for each order item.order_id: Foreign Key - Links to theorderscollection.product_id: Foreign Key - References theproductscollection.quantity: Integer - Number of units ordered for the product.price: Decimal - Price per unit of the product at the time of order.
d. Payments Collection
Establish a payments collection to handle all payment transactions:
id: Primary Key - Unique identifier for each payment.order_id: Foreign Key - Links to the associated order.payment_status: String - Indicates the status of the payment (e.g., pending, successful, failed).payment_gateway: String - Name of the payment processor used (e.g., Stripe, PayPal).transaction_details: JSON - Stores payment gateway-specific transaction metadata.created_at: Timestamp - Records when the payment was initiated.
e. Customers Collection
Create a customers collection to manage customer information:
id: Primary Key - Unique identifier for each customer.name: String - Customer's full name.email: String - Customer's email address.address: Text - Shipping or billing address of the customer.phone_number: String - Contact number of the customer.
2. Integrate a Payment Gateway
a. Choose a Payment Gateway
Select a reliable payment gateway that aligns with your business needs. Popular options include:
- Stripe: Offers a comprehensive API, supports multiple payment methods, and provides extensive documentation.
- PayPal: Widely recognized, supports various payment options, and easy to integrate.
- Razorpay: Ideal for businesses operating in specific regions, offering seamless payment solutions.
For this guide, we'll focus on integrating Stripe due to its robust feature set and developer-friendly tools.
b. Set Up Stripe
Begin by creating a Stripe account and obtaining your API keys:
- Sign up for a Stripe account at stripe.com.
- Navigate to the Developers section to retrieve your Publishable Key and Secret Key.
- Install the Stripe SDK in your Directus project using the following command:
npm install stripe
c. Create Webhooks for Payment Events
Webhooks are essential for handling asynchronous payment events. Follow these steps to set them up:
- In your Stripe dashboard, navigate to Developers > Webhooks.
- Create a new webhook endpoint, specifying the URL where your Directus instance will listen for events (e.g.,
https://your-directus-instance.com/webhooks/stripe). - Select the relevant events to listen for, such as
payment_intent.succeeded,payment_intent.failed, andcharge.refunded. - In Directus, configure a custom Flow or Hook to process these webhook events:
// Example: Processing a Stripe webhook in Directus
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
module.exports = async function handleStripeWebhook(request, response) {
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(request.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
response.status(400).send(`Webhook Error: ${err.message}`);
return;
}
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
// Update order status to 'completed' in Directus
await updateOrderStatus(paymentIntent.metadata.order_id, 'completed', paymentIntent.id);
break;
case 'payment_intent.failed':
const failedIntent = event.data.object;
// Update order status to 'failed' in Directus
await updateOrderStatus(failedIntent.metadata.order_id, 'failed', failedIntent.id);
break;
// Handle other event types as needed
default:
console.log(`Unhandled event type ${event.type}`);
}
response.json({ received: true });
};
3. Implement Order Creation
a. Frontend Integration
Develop a user-friendly checkout interface using a modern frontend framework such as React, Next.js, or Vue.js. The frontend should:
- Display available products with detailed information.
- Allow users to add products to a shopping cart.
- Collect necessary order details like product IDs, quantities, and customer information.
- Submit the collected data to the Directus API for order creation.
Example of submitting an order using JavaScript:
const submitOrder = async (orderData) => {
const response = await fetch('https://your-directus-instance.com/items/orders', {
method: 'POST',
headers: {
Authorization: `Bearer YOUR_ACCESS_TOKEN`,
'Content-Type': 'application/json',
},
body: JSON.stringify(orderData),
});
return response.json();
};
// Usage
const orderData = {
user_id: 'user_12345',
order_status: 'pending',
total_amount: 150.00,
payment_id: '',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
// Additional fields as necessary
};
submitOrder(orderData).then(order => {
console.log('Order Created:', order);
});
b. Backend Logic
Implement server-side logic to handle order creation and ensure data integrity:
- Validation: Ensure all incoming order data is valid and complete.
- Calculation: Compute the total amount based on product prices and quantities.
- Database Operations: Create a new record in the
orderscollection and associate each item with the order in theorder_itemscollection.
Example of a Directus Flow or Hook to handle order creation:
// Example: Order Creation Hook in Directus
module.exports = async function createOrder(data, { services }) {
const { Items } = services;
// Validate order data
if (!data.user_id || !data.products || data.products.length === 0) {
throw new Error('Invalid order data');
}
// Calculate total amount
let totalAmount = 0;
for (const item of data.products) {
const product = await Items.getItem('products', item.product_id);
totalAmount += product.price * item.quantity;
}
// Create order
const order = await Items.createOne('orders', {
user_id: data.user_id,
order_status: 'pending',
total_amount: totalAmount,
payment_id: '',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
});
// Create order items
for (const item of data.products) {
await Items.createOne('order_items', {
order_id: order.id,
product_id: item.product_id,
quantity: item.quantity,
price: item.price,
});
}
return order;
};
4. Handle Payment Processing
a. Create a Payment Intent
Utilize Stripe's PaymentIntent API to handle the payment process:
- On the server side, create a PaymentIntent with the total amount and currency:
// Example: Creating a PaymentIntent with Stripe
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
async function createPaymentIntent(order) {
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(order.total_amount * 100), // Amount in cents
currency: 'usd',
metadata: { order_id: order.id },
});
return paymentIntent;
}
- Send the
client_secretto the frontend to complete the payment:
// Example: Sending client_secret to Frontend
app.post('/create-payment-intent', async (req, res) => {
const order = await getOrderFromDatabase(req.body.order_id);
const paymentIntent = await createPaymentIntent(order);
res.send({
clientSecret: paymentIntent.client_secret,
});
});
b. Update Order Status
After the payment is successfully processed, update the corresponding order's status:
- Listen for the
payment_intent.succeededwebhook event from Stripe. - Update the
order_statusto completed and store thepayment_idin theorderscollection.
// Example: Updating Order Status on Payment Success
async function updateOrderStatus(orderId, status, paymentId) {
await directus.items('orders').updateOne(orderId, {
order_status: status,
payment_id: paymentId,
updated_at: new Date().toISOString(),
});
}
5. Automate with Directus Flows
a. Trigger on Order Creation
Set up a Directus Flow to automatically handle actions when a new order is created:
- Navigate to Flows in the Directus admin panel.
- Create a new Flow with the trigger set to After Item Create on the
orderscollection. - Add an action to call a custom endpoint or execute server-side logic that initiates the payment process by creating a PaymentIntent with Stripe.
b. Handle Webhook Events
Configure another Flow to process Stripe webhook events and update order statuses accordingly:
- Create a Flow with a custom trigger that listens for incoming webhook data.
- Parse the webhook event to determine the type of payment event (e.g., succeeded, failed).
- Update the corresponding order's status in the
orderscollection based on the event type.
// Example: Directus Flow for Webhook Handling
module.exports = async function handleWebhook(flow, event) {
const { type, data } = event;
if (type === 'payment_intent.succeeded') {
const orderId = data.object.metadata.order_id;
await flow.services.items.updateOne('orders', orderId, {
order_status: 'completed',
payment_id: data.object.id,
updated_at: new Date().toISOString(),
});
} else if (type === 'payment_intent.failed') {
const orderId = data.object.metadata.order_id;
await flow.services.items.updateOne('orders', orderId, {
order_status: 'failed',
payment_id: data.object.id,
updated_at: new Date().toISOString(),
});
}
};
6. Security Considerations
a. Authentication
Ensure that all API endpoints and the Directus instance are secured using authentication mechanisms such as:
- Directus Auth Tokens: Use bearer tokens to authenticate API requests.
- OAuth: Implement OAuth 2.0 for more secure and flexible authentication.
b. Data Validation
Implement rigorous validation for all incoming data to prevent the insertion of invalid or malicious information into your Directus collections:
- Use schema validation within Directus to enforce data integrity.
- Sanitize inputs on both frontend and backend.
c. Webhook Security
Protect your webhook endpoints by verifying the authenticity of incoming requests:
- Signature Verification: Use Stripe's signature in the webhook headers to verify the request source.
- Secret Tokens: Implement shared secret tokens to ensure that only legitimate requests are processed.
// Example: Verifying Stripe Webhook Signature
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
console.error('Webhook signature verification failed.', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Proceed with event handling
handleStripeEvent(event);
res.json({ received: true });
});
7. Testing and Deployment
a. Test the Workflow
Before going live, thoroughly test your order and payment system to ensure reliability:
- Simulate Transactions: Use Stripe's test mode to create dummy transactions without processing real payments.
- Validate Data Flow: Ensure that data moves correctly from the frontend to Directus and that order statuses update appropriately based on payment events.
- Error Handling: Test scenarios where payments fail or webhooks are delayed to ensure your system handles these gracefully.
b. Deploy to Production
Once testing is complete, prepare your system for production deployment:
- Switch API Keys: Replace Stripe's test API keys with live keys.
- Configure Webhooks: Update webhook URLs in Stripe to point to your live Directus instance.
- Monitor Transactions: Implement logging and monitoring to keep track of real-time transactions and system performance.
8. Frontend Workflow Example
a. Submit Order via API
Integrate order submission functionality within your frontend application:
const submitOrder = async (orderData) => {
const response = await fetch('https://your-directus-instance.com/items/orders', {
method: 'POST',
headers: {
Authorization: `Bearer YOUR_ACCESS_TOKEN`,
'Content-Type': 'application/json',
},
body: JSON.stringify(orderData),
});
return response.json();
};
// Example usage:
const orderData = {
user_id: 'user_12345',
order_status: 'pending',
total_amount: 150.00,
payment_id: '',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
products: [
{ product_id: 'prod_001', quantity: 2, price: 50.00 },
{ product_id: 'prod_002', quantity: 1, price: 50.00 }
],
};
submitOrder(orderData).then(order => {
console.log('Order Submitted:', order);
// Proceed to create PaymentIntent with the received order details
});
b. Handle Payment Status
Implement functionality to listen for payment status updates and reflect changes in the frontend:
-
Use webhooks to receive real-time updates from Stripe about payment events.
-
Update the UI to inform the user about the payment status (e.g., success, failure).
-
Handle retries or cancellations based on the payment outcome.
// Example: Listening for Payment Status Updates
import { useEffect } from 'react';
const usePaymentStatus = (orderId) => {
useEffect(() => {
const eventSource = new EventSource(`https://your-directus-instance.com/events/payment-status?order_id=${orderId}`);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.status === 'completed') {
// Notify user of successful payment
alert('Payment Successful!');
} else if (data.status === 'failed') {
// Notify user of payment failure
alert('Payment Failed. Please try again.');
}
};
return () => {
eventSource.close();
};
}, [orderId]);
};
export default usePaymentStatus;
Conclusion
Implementing a robust order and payment system in Directus requires careful planning and execution. By defining a structured data model, integrating a secure payment gateway like Stripe, and utilizing Directus Flows for automation, you can create a seamless and efficient e-commerce platform. Ensuring security through proper authentication, data validation, and webhook verification further solidifies the reliability of your system. Comprehensive testing and strategic deployment will guarantee that your order and payment processes function smoothly in a production environment.
References
Last updated January 19, 2025