Ask me anything
Ithy Logo
Sign in to save

Best Practices for Implementing Order and Payment Creation in Directus

Seamlessly integrate robust order and payment systems using Directus

directus order payment management

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 the users collection 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 the orders collection.
  • product_id: Foreign Key - References the products collection.
  • 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:

  1. Sign up for a Stripe account at stripe.com.
  2. Navigate to the Developers section to retrieve your Publishable Key and Secret Key.
  3. 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:

  1. In your Stripe dashboard, navigate to Developers > Webhooks.
  2. 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).
  3. Select the relevant events to listen for, such as payment_intent.succeeded, payment_intent.failed, and charge.refunded.
  4. 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 orders collection and associate each item with the order in the order_items collection.

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:

  1. 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;
}
  1. Send the client_secret to 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:

  1. Listen for the payment_intent.succeeded webhook event from Stripe.
  2. Update the order_status to completed and store the payment_id in the orders collection.
// 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:

  1. Navigate to Flows in the Directus admin panel.
  2. Create a new Flow with the trigger set to After Item Create on the orders collection.
  3. 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:

  1. Create a Flow with a custom trigger that listens for incoming webhook data.
  2. Parse the webhook event to determine the type of payment event (e.g., succeeded, failed).
  3. Update the corresponding order's status in the orders collection 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

AI-assisted research. Sources, methodology and corrections · Source-checked guides and tools · Explore published answers

Ask Ithy AI