Node.js (Express)
Configuración
Copy
// 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
Copy
// 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
Copy
// 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
Copy
// 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
Copy
# 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
Copy
# 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
Copy
# 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
Copy
// 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
Copy
<?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
Copy
<?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
Copy
// routes/api.php
Route::post('/webhooks/cobrix', [WebhookController::class, 'cobrix'])
->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);