Skip to main content

Node.js (Express)

Configuración

// config/cobrix.js
module.exports = {
  accessToken: process.env.COBRIX_ACCESS_TOKEN,
  webhookSecret: process.env.COBRIX_WEBHOOK_SECRET,
  companyId: process.env.COBRIX_COMPANY_ID,
  baseUrl: process.env.NODE_ENV === 'production'
    ? 'http://api.cobrix.co/api'
    : 'https://sandbox-api.cobrix.co/api'
};

Cliente API

// services/cobrixClient.js
const axios = require('axios');
const config = require('../config/cobrix');

const client = axios.create({
  baseURL: config.baseUrl,
  headers: {
    'Authorization': `Bearer ${config.accessToken}`,
    'Content-Type': 'application/json'
  }
});

module.exports = client;

Servicio de Checkout

// services/checkoutService.js
const client = require('./cobrixClient');
const config = require('../config/cobrix');

class CheckoutService {
  // PENDIENTE - Endpoint en desarrollo
  // async upsertProduct(productData) {
  //   const { data } = await client.post('/checkout/product', {
  //     companyId: config.companyId,
  //     ...productData
  //   });
  //   return data;
  // }

  async resolveCustomer(customerData) {
    const { data } = await client.post('/checkout/resolve-customer', {
      companyId: config.companyId,
      ...customerData
    });
    return data;
  }

  async createSession(sessionData) {
    const { data } = await client.post('/checkout/session', {
      companyId: config.companyId,
      ...sessionData
    });
    return data;
  }

  async processOrder(order) {
    // Paso 1: Producto (PENDIENTE - endpoint en desarrollo)
    // Por ahora, pasar el monto directamente en la sesión
    // const product = await this.upsertProduct({
    //   sku: order.productSku,
    //   name: order.productName,
    //   amountMinor: order.priceInCents,
    //   currency: 'USD'
    // });

    // Paso 2: Cliente
    const customer = await this.resolveCustomer({
      email: order.customerEmail,
      name: order.customerName,
      phone: order.customerPhone
    });

    // Paso 3: Checkout Session
    const session = await this.createSession({
      companyCustomerId: customer.companyCustomerId,
      // productId: product.id, // Usar cuando endpoint de productos esté disponible
      amountMinor: order.priceInCents, // Pasar monto directamente por ahora
      currency: 'USD',
      successUrl: `${process.env.APP_URL}/success?order=${order.id}`,
      cancelUrl: `${process.env.APP_URL}/cancel`,
      metadata: { orderId: order.id }
    });

    return session.checkoutUrl;
  }
}

module.exports = new CheckoutService();

Webhook Handler

// routes/webhooks.js
const express = require('express');
const crypto = require('crypto');
const config = require('../config/cobrix');

const router = express.Router();

router.post('/cobrix',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const signature = req.headers['x-cobrix-signature'];
    const rawBody = req.body.toString();

    if (!verifySignature(rawBody, signature, config.webhookSecret)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    const event = JSON.parse(rawBody);
    res.status(200).json({ received: true });

    // Procesar en background
    setImmediate(() => processEvent(event));
  }
);

function verifySignature(payload, signature, secret) {
  if (!signature) return false;

  const parts = signature.split(',');
  const timestamp = parts.find(p => p.startsWith('t='))?.replace('t=', '');
  const sig = parts.find(p => p.startsWith('v1='))?.replace('v1=', '');

  if (!timestamp || !sig) return false;
  if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${payload}`)
    .digest('hex');

  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

async function processEvent(event) {
  switch (event.event) {
    case 'payment.succeeded':
      console.log('✅ Pago exitoso:', event.data.payment.transactionId);
      // Actualizar orden...
      break;
    case 'payment.failed':
      console.log('❌ Pago fallido');
      break;
  }
}

module.exports = router;

Python (Flask)

Configuración

# config.py
import os

class Config:
    COBRIX_ACCESS_TOKEN = os.environ.get('COBRIX_ACCESS_TOKEN')
    COBRIX_WEBHOOK_SECRET = os.environ.get('COBRIX_WEBHOOK_SECRET')
    COBRIX_COMPANY_ID = os.environ.get('COBRIX_COMPANY_ID')
    COBRIX_BASE_URL = (
        'http://api.cobrix.co/api'
        if os.environ.get('FLASK_ENV') == 'production'
        else 'https://sandbox-api.cobrix.co/api'
    )

Cliente API

# services/cobrix_client.py
import requests
from config import Config

class CobrixClient:
    def __init__(self):
        self.base_url = Config.COBRIX_BASE_URL
        self.headers = {
            'Authorization': f'Bearer {Config.COBRIX_ACCESS_TOKEN}',
            'Content-Type': 'application/json'
        }
        self.company_id = Config.COBRIX_COMPANY_ID

    def _request(self, method, endpoint, data=None):
        response = requests.request(
            method=method,
            url=f"{self.base_url}{endpoint}",
            headers=self.headers,
            json=data
        )
        response.raise_for_status()
        return response.json()

    # PENDIENTE - Endpoint en desarrollo
    # def upsert_product(self, product_data):
    #     return self._request('POST', '/checkout/product', {
    #         'companyId': self.company_id,
    #         **product_data
    #     })

    def resolve_customer(self, customer_data):
        return self._request('POST', '/checkout/resolve-customer', {
            'companyId': self.company_id,
            **customer_data
        })

    def create_checkout_session(self, session_data):
        return self._request('POST', '/checkout/session', {
            'companyId': self.company_id,
            **session_data
        })

cobrix = CobrixClient()

Webhook Handler

# routes/webhooks.py
import hmac
import hashlib
import time
import json
from flask import Blueprint, request, jsonify
from config import Config

webhooks = Blueprint('webhooks', __name__)

@webhooks.route('/cobrix', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Cobrix-Signature')
    payload = request.get_data(as_text=True)

    if not verify_signature(payload, signature, Config.COBRIX_WEBHOOK_SECRET):
        return jsonify({'error': 'Invalid signature'}), 401

    event = json.loads(payload)
    
    if event['event'] == 'payment.succeeded':
        handle_payment_success(event['data'])
    
    return jsonify({'received': True}), 200

def verify_signature(payload, signature, secret):
    if not signature:
        return False
    
    parts = dict(p.split('=') for p in signature.split(','))
    timestamp = parts.get('t')
    sig = parts.get('v1')
    
    if not timestamp or not sig:
        return False
    
    if abs(time.time() - int(timestamp)) > 300:
        return False
    
    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.{payload}".encode(),
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(sig, expected)

def handle_payment_success(data):
    print(f"✅ Pago exitoso: {data['payment']['transactionId']}")

PHP (Laravel)

Configuración

// config/services.php
return [
    'cobrix' => [
        'access_token' => env('COBRIX_ACCESS_TOKEN'),
        'webhook_secret' => env('COBRIX_WEBHOOK_SECRET'),
        'company_id' => env('COBRIX_COMPANY_ID'),
        'base_url' => env('APP_ENV') === 'production'
            ? 'http://api.cobrix.co/api'
            : 'https://sandbox-api.cobrix.co/api',
    ],
];

Cliente API

<?php
// app/Services/CobrixClient.php

namespace App\Services;

use Illuminate\Support\Facades\Http;

class CobrixClient
{
    private string $baseUrl;
    private string $accessToken;
    private string $companyId;

    public function __construct()
    {
        $this->baseUrl = config('services.cobrix.base_url');
        $this->accessToken = config('services.cobrix.access_token');
        $this->companyId = config('services.cobrix.company_id');
    }

    // PENDIENTE - Endpoint en desarrollo
    // public function upsertProduct(array $data): array
    // {
    //     return $this->post('/checkout/product', [
    //         'companyId' => $this->companyId,
    //         ...$data
    //     ]);
    // }

    public function resolveCustomer(array $data): array
    {
        return $this->post('/checkout/resolve-customer', [
            'companyId' => $this->companyId,
            ...$data
        ]);
    }

    public function createSession(array $data): array
    {
        return $this->post('/checkout/session', [
            'companyId' => $this->companyId,
            ...$data
        ]);
    }

    private function post(string $endpoint, array $data): array
    {
        $response = Http::withHeaders([
            'Authorization' => 'Bearer ' . $this->accessToken
        ])->post($this->baseUrl . $endpoint, $data);

        return $response->json();
    }
}

Webhook Controller

<?php
// app/Http/Controllers/WebhookController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class WebhookController extends Controller
{
    public function cobrix(Request $request)
    {
        $signature = $request->header('X-Cobrix-Signature');
        $payload = $request->getContent();
        $secret = config('services.cobrix.webhook_secret');

        if (!$this->verifySignature($payload, $signature, $secret)) {
            return response()->json(['error' => 'Invalid signature'], 401);
        }

        $event = json_decode($payload, true);

        match ($event['event']) {
            'payment.succeeded' => $this->handleSuccess($event['data']),
            'payment.failed' => $this->handleFailure($event['data']),
            default => null
        };

        return response()->json(['received' => true]);
    }

    private function verifySignature(string $payload, ?string $signature, string $secret): bool
    {
        if (!$signature) return false;

        $parts = [];
        foreach (explode(',', $signature) as $part) {
            [$key, $value] = explode('=', $part, 2);
            $parts[$key] = $value;
        }

        $timestamp = $parts['t'] ?? null;
        $sig = $parts['v1'] ?? null;

        if (!$timestamp || !$sig) return false;
        if (abs(time() - (int)$timestamp) > 300) return false;

        $expected = hash_hmac('sha256', "{$timestamp}.{$payload}", $secret);
        return hash_equals($expected, $sig);
    }

    private function handleSuccess(array $data): void
    {
        \Log::info('✅ Pago exitoso', $data['payment']);
    }

    private function handleFailure(array $data): void
    {
        \Log::warning('❌ Pago fallido', $data);
    }
}

Ruta

// routes/api.php
Route::post('/webhooks/cobrix', [WebhookController::class, 'cobrix'])
    ->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);