Skip to main content

Overview

Connect World’s order management system handles the complete subscription lifecycle, from customer creation through payment processing to subscription activation and expiration tracking.

Order Creation

Secure order processing with validation

Customer Management

Automatic customer profile creation

Status Tracking

Real-time order status updates

Order Entity

Order Interface

export type PaymentMethod = "stripe" | "paypal";
export type OrderStatus = "pending" | "completed" | "failed";

export interface Order {
  id?: string;
  customerId: string;
  planId: string;
  devices: number;
  months: number;
  amount: number;
  paymentMethod: PaymentMethod;
  paymentReceiptId: string;
  status: OrderStatus;
  activationDate: Date;
  expirationDate: Date;
  createdAt?: Date;
}
Source: src/domain/entities/Order.ts:1-17

Order Status Types

Orders can have three status values:
  • pending: Order created but payment not yet confirmed
  • completed: Payment confirmed and subscription active
  • failed: Payment failed or order could not be processed
export type OrderStatus = "pending" | "completed" | "failed";
Source: src/domain/entities/Order.ts:2

Order Creation

Factory Function

Orders are created using a factory function that automatically calculates activation and expiration dates:
export function createOrder(data: Omit<Order, "id" | "createdAt" | "activationDate" | "expirationDate">): Order {
  const activationDate = new Date();
  const expirationDate = new Date();
  expirationDate.setMonth(expirationDate.getMonth() + data.months);

  return {
    ...data,
    activationDate,
    expirationDate,
    createdAt: new Date(),
  };
}
Source: src/domain/entities/Order.ts:19-30
  • activationDate: Set to current date/time when order is created
  • expirationDate: Calculated by adding the subscription duration (months) to the activation date
  • createdAt: Timestamp of order creation
Example: A 6-month subscription created on January 1st will:
  • activationDate: 2026-01-01
  • expirationDate: 2026-07-01

Order API Endpoint

Order Creation Endpoint

The /api/orders endpoint handles complete order creation with comprehensive security measures:
export async function POST(req: NextRequest) {
  // Rate limit: 5 order creations per IP per 10 minutes (anti-spam)
  const ip = getClientIp(req);
  if (!checkRateLimit(`orders:${ip}`, 5, 10 * 60 * 1000)) {
    return NextResponse.json(
      { error: "Demasiadas solicitudes. Intenta de nuevo en unos minutos." },
      { status: 429 }
    );
  }

  try {
    const body = await req.json();

    // Honeypot: bots fill hidden fields humans don't see
    if (body._hp && body._hp !== "") {
      // Silently reject — return fake success so bots don't retry aggressively
      return NextResponse.json({ orderId: "bot", expirationDate: new Date().toISOString() }, { status: 201 });
    }

    // Sanitize and validate every field
    const name  = sanitizeString(body.name, 100);
    const email = sanitizeEmail(body.email);
    const phone = sanitizePhone(body.phone);
    const planId = sanitizeString(body.planId, 50);
    const devices = sanitizeNumber(body.devices, 1, 10);
    const months  = sanitizeNumber(body.months, 1, 12);
    const amount  = sanitizeNumber(body.amount, 1, 10000);
    const paymentMethod = sanitizeEnum<PaymentMethod>(body.paymentMethod, PAYMENT_METHODS);
    const paymentReceiptId = sanitizeString(body.paymentReceiptId, 200);

    // Validation and order creation...
  } catch (error: unknown) {
    // Error handling...
  }
}
Source: src/app/api/orders/route.ts:19-97

Rate Limiting

Anti-Spam Protection: Orders are rate-limited to 5 creations per IP per 10 minutes to prevent abuse.
if (!checkRateLimit(`orders:${ip}`, 5, 10 * 60 * 1000)) {
  return NextResponse.json(
    { error: "Demasiadas solicitudes. Intenta de nuevo en unos minutos." },
    { status: 429 }
  );
}
Source: src/app/api/orders/route.ts:21-27

Honeypot Protection

The endpoint implements honeypot fields to catch bots:
// Honeypot: bots fill hidden fields humans don't see
if (body._hp && body._hp !== "") {
  // Silently reject — return fake success so bots don't retry aggressively
  return NextResponse.json({ orderId: "bot", expirationDate: new Date().toISOString() }, { status: 201 });
}
Source: src/app/api/orders/route.ts:32-36
Honeypot fields are hidden in the UI. Legitimate users don’t see or fill them, but bots often auto-fill all fields. The endpoint returns a fake success response to avoid triggering aggressive bot retries.

Input Validation

Comprehensive Field Validation

Every field is sanitized and validated before processing:
const name  = sanitizeString(body.name, 100);
const email = sanitizeEmail(body.email);
const phone = sanitizePhone(body.phone);
const planId = sanitizeString(body.planId, 50);
const devices = sanitizeNumber(body.devices, 1, 10);
const months  = sanitizeNumber(body.months, 1, 12);
const amount  = sanitizeNumber(body.amount, 1, 10000);
const paymentMethod = sanitizeEnum<PaymentMethod>(body.paymentMethod, PAYMENT_METHODS);
const paymentReceiptId = sanitizeString(body.paymentReceiptId, 200);
Source: src/app/api/orders/route.ts:39-47

Validation Rules

Plan Validation

const plan = PLANS.find((p) => p.id === planId);
if (!plan) {
  return NextResponse.json(
    { error: "Plan inválido." },
    { status: 400 }
  );
}
Source: src/app/api/orders/route.ts:54-57

Duration Validation

if (!(VALID_MONTHS as readonly number[]).includes(months)) {
  return NextResponse.json(
    { error: "Duración inválida." },
    { status: 400 }
  );
}
Source: src/app/api/orders/route.ts:60-62

Price Validation

const expectedPrice = plan.prices.find(
  (p) => p.months === months
)?.price;
if (expectedPrice === undefined || 
    Math.abs(amount - expectedPrice) > 0.01) {
  return NextResponse.json(
    { error: "Monto inválido." },
    { status: 400 }
  );
}
Source: src/app/api/orders/route.ts:65-68

Device Validation

if (devices !== plan.devices) {
  return NextResponse.json(
    { error: "Número de dispositivos inválido." },
    { status: 400 }
  );
}
Source: src/app/api/orders/route.ts:71-73
Price Tampering Prevention: The server validates that the submitted amount matches the expected price for the plan and duration. This prevents users from manipulating prices in the client.

Customer Management

Customer Entity

export interface Customer {
  id?: string;
  email: string;
  phone: string;
  name: string;
  createdAt?: Date;
}

export function createCustomer(data: Omit<Customer, "id" | "createdAt">): Customer {
  return {
    ...data,
    createdAt: new Date(),
  };
}
Source: src/domain/entities/Customer.ts:1-14

Email and Phone Validation

/** Validate and normalize email */
export function sanitizeEmail(value: unknown): string {
  const s = sanitizeString(value, 254);
  const emailRe = /^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/;
  return emailRe.test(s) ? s.toLowerCase() : "";
}
Source: src/lib/sanitize.ts:16-21
Emails are automatically converted to lowercase and must match a valid email pattern.

Order Use Case

CreateOrderUseCase

The use case orchestrates customer and order creation:
export class CreateOrderUseCase {
  constructor(
    private readonly customerRepo: ICustomerRepository,
    private readonly orderRepo: IOrderRepository
  ) {}

  async execute(dto: CreateOrderDto): Promise<OrderResponseDto> {
    const email = new Email(dto.email);
    const phone = new Phone(dto.phone);

    const customerData = createCustomer({
      name: dto.name,
      email: email.toString(),
      phone: phone.toString(),
    });

    const customer = await this.customerRepo.create(customerData);

    const orderData = createOrder({
      customerId: customer.id!,
      planId: dto.planId,
      devices: dto.devices,
      months: dto.months,
      amount: dto.amount,
      paymentMethod: dto.paymentMethod,
      paymentReceiptId: dto.paymentReceiptId,
      status: "completed",
    });

    const order = await this.orderRepo.create(orderData);

    return {
      orderId: order.id!,
      customerId: customer.id!,
      planId: order.planId,
      devices: order.devices,
      months: order.months,
      amount: order.amount,
      paymentMethod: order.paymentMethod,
      paymentReceiptId: order.paymentReceiptId,
      status: order.status,
      activationDate: order.activationDate.toISOString(),
      expirationDate: order.expirationDate.toISOString(),
    };
  }
}
Source: src/application/use-cases/CreateOrderUseCase.ts:9-54

Use Case Flow

  1. Value Object Validation: Wraps email and phone in value objects for domain validation
  2. Customer Creation: Creates or retrieves customer profile
  3. Order Creation: Creates order with activation/expiration dates
  4. Data Persistence: Saves customer and order to repositories
  5. Response Mapping: Converts domain entities to DTOs for API response

Order Response

Successful Order Response

return NextResponse.json(result, { status: 201 });
Response structure:
{
  "orderId": "507f1f77bcf86cd799439011",
  "customerId": "507f191e810c19729de860ea",
  "planId": "plan-2",
  "devices": 2,
  "months": 6,
  "amount": 70,
  "paymentMethod": "stripe",
  "paymentReceiptId": "pi_3MtwBwLkdIwHu7ix28a3tqPa",
  "status": "completed",
  "activationDate": "2026-03-09T10:30:00.000Z",
  "expirationDate": "2026-09-09T10:30:00.000Z"
}
Source: src/app/api/orders/route.ts:91

Security Checklist

Rate Limiting

5 orders per IP per 10 minutes

Honeypot

Hidden field catches bot submissions

Input Sanitization

All fields sanitized and validated

Price Validation

Server-side price verification

Plan Validation

Validates plan exists in system

Device Validation

Ensures devices match plan

Email Validation

Regex pattern and normalization

Enum Validation

Payment methods validated against enum

Best Practices

  1. Always use the use case layer - Don’t directly create orders from API routes
  2. Validate payment receipt IDs - Ensure payment was completed before creating order
  3. Set status to “completed” - Only after successful payment verification
  4. Calculate expiration dates server-side - Never trust client calculations
  5. Store customer data - Link orders to customer profiles for history tracking
  6. Log order creation - Include order ID, customer ID, and payment method
  7. Handle errors gracefully - Return clear error messages for validation failures
  8. Use repositories - Abstract database operations behind repository interfaces
Never create an order without first verifying the payment was successful. Always require a paymentReceiptId from the payment processor.

Order Lifecycle

  • Pending → Completed: Payment processor confirms successful payment
  • Pending → Failed: Payment processor reports failed transaction
  • Completed → Expired: System checks expiration date and marks subscription as expired
  • Failed → Archive: Failed orders are archived for review

Build docs developers (and LLMs) love