Skip to main content

Overview

The MTB Backend integrates with Flow, a Chilean payment gateway, to process registration payments for events. This integration handles payment creation, token management, order tracking, and payment state management.
Flow provides sandbox and production environments. Configure the appropriate base URL via the FLOW_BASE_URL environment variable.

Payment Flow Architecture

1

Initiate Payment

The frontend requests a payment by sending the inscripcionId to the /api/flow/pagar endpoint.
2

Create Flow Transaction

The backend creates a payment transaction with Flow, generating a unique commerceOrder identifier.
3

Receive Payment Token

Flow returns a payment token that’s stored in the tokenFlow field of the inscripcion record.
4

Redirect to Payment

The user is redirected to Flow’s payment page using the generated payment URL with the token.
5

Payment Confirmation

Flow sends a webhook notification to the confirmation endpoint with the payment result.
6

Update Payment Status

The backend updates the estadoPago field based on the payment outcome.

Payment States

The estadoPago field in the inscripcion schema tracks the current payment status using an enumeration with three possible values:
EstadoDescription
PendienteDefault state. Payment has not been completed. This is the initial state when a registration is created.
PagadoPayment was successfully processed and confirmed by Flow.
RechazadoPayment was rejected or failed. This can occur due to insufficient funds, card issues, or user cancellation.
Only inscriptions with estadoPago: "Pendiente" can initiate a new payment flow. Attempting to pay for an already paid or rejected inscription will result in a bad request error.

Schema Fields

The inscripcion schema includes three key fields for Flow integration:

tokenFlow

  • Type: string
  • Purpose: Stores the unique payment token generated by Flow
  • Usage: Used to construct the payment URL and track the transaction
  • Set: When the payment is created via the /pagar endpoint
  • Example: "A1B2C3D4E5F6G7H8I9J0"

ordenFlow

  • Type: string
  • Required: false
  • Purpose: Stores the commerce order identifier
  • Format: INS{timestamp} (e.g., INS1678901234567)
  • Usage: Links the internal registration with the Flow transaction
  • Set: Generated automatically when creating the payment

estadoPago

  • Type: enumeration
  • Default: "Pendiente"
  • Values: ["Pendiente", "Pagado", "Rechazado"]
  • Purpose: Tracks the current payment status
  • Usage: Determines if a payment can be initiated and displays status to users

Payment Creation Endpoint

POST /api/flow/pagar

Creates a Flow payment transaction for a registration. Request Body:
{
  "inscripcionId": "abc123def456"
}
Response (Success):
{
  "ok": true,
  "url": "https://sandbox.flow.cl/app/web/pay.php?token=ABC123&flowOrder=123456",
  "token": "ABC123DEF456",
  "commerceOrder": "INS1678901234567"
}
Implementation Details: The endpoint (src/api/flow/controllers/flow.ts:8) performs the following operations:
  1. Validation - Verifies the inscripcion exists and has required fields
  2. State Check - Ensures estadoPago is "Pendiente"
  3. Order Generation - Creates a unique commerceOrder using timestamp
  4. Signature Creation - Generates HMAC-SHA256 signature for security
  5. Flow API Call - Sends payment creation request to Flow
  6. Token Storage - Updates inscripcion with tokenFlow and ordenFlow
  7. URL Construction - Builds the complete payment URL for redirection
async pagar(ctx) {
  const { inscripcionId } = ctx.request.body;
  
  const inscripcion = await strapi.documents("api::inscripcion.inscripcion").findOne({
    documentId: inscripcionId,
    fields: ["documentId", "nombreCompleto", "email", "monto", "estadoPago"],
  });
  
  if (inscripcion.estadoPago !== "Pendiente") {
    return ctx.badRequest("Inscripción no pendiente");
  }
  
  const commerceOrder = `INS${Date.now()}`;
  
  const params = {
    apiKey,
    commerceOrder,
    subject: `Inscripcion MTB ${inscripcion.nombreCompleto}`,
    currency: "CLP",
    amount: Number(inscripcion.monto),
    email: inscripcion.email.trim(),
    urlConfirmation: `${process.env.BACKEND_URL}/api/flow/confirmacion`,
    urlReturn: `${process.env.FRONTEND_URL}/pago-exitoso`,
  };
  
  // Create signature and call Flow API
  // Store token and return payment URL
}

Payment Confirmation Webhook

POST /api/flow/confirmacion

Flow calls this endpoint after a payment is processed (successful, rejected, or cancelled). Purpose:
  • Receive payment status updates from Flow
  • Update the estadoPago field accordingly
  • Trigger any post-payment business logic
The webhook endpoint (src/api/flow/controllers/flow.ts:131) currently logs the received data. You should implement the logic to verify the payment signature and update the inscripcion’s estadoPago field.
Expected Webhook Payload:
{
  "token": "ABC123DEF456",
  "flowOrder": 123456,
  "commerceOrder": "INS1678901234567",
  "status": 2,
  "subject": "Inscripcion MTB Juan Perez",
  "amount": 15000,
  "payer": "[email protected]"
}
Status Codes:
  • 1 - Pending
  • 2 - Paid/Successful
  • 3 - Rejected
  • 4 - Cancelled by user

Environment Configuration

Configure the following environment variables for Flow integration:
VariableDescriptionRequiredExample
FLOW_API_KEYYour Flow API keyYesA1B2C3-D4E5F6-G7H8I9
FLOW_SECRET_KEYYour Flow secret key for signaturesYessecret123key456
FLOW_BASE_URLFlow API base URLNohttps://sandbox.flow.cl/api (default for sandbox)
BACKEND_URLYour backend URL for webhooksYeshttps://api.example.com
FRONTEND_URLFrontend URL for return after paymentYeshttps://example.com
Never commit your FLOW_API_KEY or FLOW_SECRET_KEY to version control. Always use environment variables.

Security Implementation

The Flow integration implements several security measures:

HMAC-SHA256 Signature

All requests to Flow are signed using HMAC-SHA256 (src/api/flow/controllers/flow.ts:64):
const sortedKeys = Object.keys(params).sort();
let toSign = "";
sortedKeys.forEach(key => {
  toSign += key + params[key];
});

const signature = crypto
  .createHmac("sha256", secretKey)
  .update(toSign)
  .digest("hex");

Webhook Validation

The confirmation webhook should validate the signature sent by Flow to ensure the request is authentic. This validation is not yet implemented in the current codebase.
To implement webhook signature validation:
  1. Extract the s parameter from the webhook payload
  2. Reconstruct the signature using the same sorted-key method
  3. Compare the signatures to verify authenticity
  4. Only process the webhook if signatures match

Testing with Sandbox

Flow provides a sandbox environment for testing:
  1. Set Sandbox URL:
    FLOW_BASE_URL=https://sandbox.flow.cl/api
    
  2. Use Test Credentials:
    • Obtain sandbox API key and secret from Flow’s developer portal
  3. Test Cards:
    • Flow sandbox provides test card numbers for different scenarios
    • Test successful payments, rejections, and timeouts
  4. Webhook Testing:
    • Use ngrok or similar tools to expose your local webhook endpoint
    • Update BACKEND_URL to point to the public URL

Common Payment Scenarios

Successful Payment Flow

  1. User completes registration → estadoPago: "Pendiente", monto: 15000
  2. Frontend calls /pagar → Receives payment URL
  3. User redirected to Flow → Completes payment
  4. Flow calls webhook → Backend updates estadoPago: "Pagado"
  5. User redirected to success page

Rejected Payment

  1. Payment initiated → estadoPago: "Pendiente"
  2. User enters invalid card → Payment fails
  3. Flow calls webhook with status 3 → Update estadoPago: "Rechazado"
  4. User can retry by calling /pagar again (requires manual reset to “Pendiente”)

Abandoned Payment

  1. Payment initiated → tokenFlow and ordenFlow set
  2. User closes browser without completing
  3. Payment remains estadoPago: "Pendiente"
  4. Manual review may be required to reset or cancel
Consider implementing a scheduled job to handle abandoned payments that remain in “Pendiente” state for extended periods.

Error Handling

The payment endpoint handles various error scenarios:
ErrorHTTP StatusDescription
Missing inscripcionId400Request body doesn’t include the inscription ID
Inscripcion not found404No inscription exists with the provided ID
Missing required data400Inscription lacks nombreCompleto, email, or monto
Not in pending state400estadoPago is not “Pendiente”
Missing API credentials500FLOW_API_KEY or FLOW_SECRET_KEY not configured
Flow API error500Flow returned an error or no token
Example error response:
{
  "error": {
    "status": 400,
    "message": "Inscripción no pendiente"
  }
}

Best Practices

1

Validate Before Payment

Always verify the inscription has all required data (email, monto, nombreCompleto) before allowing payment initiation.
2

Check Payment State

Only allow payment for inscriptions with estadoPago: "Pendiente". Prevent double payments.
3

Store Transaction References

Always save both tokenFlow and ordenFlow to enable payment tracking and reconciliation.
4

Implement Webhook Validation

Verify webhook signatures to prevent fraudulent payment confirmations.
5

Handle Timeouts

Set appropriate timeouts for Flow API calls (currently 30 seconds) and handle timeout errors gracefully.
6

Log Transaction Details

Maintain detailed logs of payment attempts, tokens, and responses for debugging and auditing.

Next Steps

Inscripcion API

Explore the complete Inscripcion API documentation

Content Types

Learn about the Inscripcion content type schema

Build docs developers (and LLMs) love