Un solo contrato

Tu sistema no tiene que conocer al proveedor.

La tienda, el ERP o el punto de venta llaman siempre al mismo cobro. Puente traduce, firma el aviso de vuelta y, si la clave se repite, responde el cargo original.

POST /v1/cobros

En esta consola el motor corre en el navegador con esa misma máquina de estados. No hay un cargo real a una tarjeta: sirve para probar la ruta, la idempotencia y la firma antes de llevar el contrato a tu backend.

curl https://sistemas.tu-empresa.example/webhooks/puente \
  -H "Authorization: Bearer $PUENTE_KEY" \
  -H "Idempotency-Key: orden-1042" \
  -H "Content-Type: application/json" \
  -d '{
    "monto": 129900,
    "moneda": "MXN",
    "metodo": "card",
    "referencia": "ORD-1042",
    "sistema": "tienda-online",
    "cliente": "Ana Ruiz"
  }'

Normalizado

{
  "monto": 129900,
  "moneda": "MXN",
  "metodo": "card",
  "referencia": "ORD-1042",
  "clave_idempotencia": "orden-1042"
}

Traducido al adaptador

{
  "pago": {
    "importe": 1299,
    "moneda": "MXN",
    "folio": "ORD-1042"
  }
}

Mapa: monto pago.importe, moneda pago.moneda, referencia pago.folio. Unidad en unidades.

Webhook de regreso

Header `Puente-Signature` del primer movimiento del libro:

v1=482c68eb21d03bd0c3a37335b3499782

// Misma firma que usa esta consola: v1 + 4 mezclas del secreto y el cuerpo.
// En producción cámbiala por HMAC-SHA256; el contrato del header no cambia.
function firmar(secreto, cuerpo) {
  // Implementa el mismo esquema que Puente-Signature.
  return "v1=…";
}

const ok = firmar("whsec_demo_puente", cuerpo) === firmaRecibida;

Google Pay y Apple Pay

El botón de Google Pay carga pay.google.com en modo TEST y, si el navegador tiene una cuenta, abre la hoja oficial. Apple Pay no puede completar la validación del comercio sin tu Merchant ID; el adaptador ya está en la ruta para cuando el token llegue.

// Google Pay — API oficial, entorno TEST.
// En producción cambia environment a PRODUCTION y el gateway real.
const client = new google.payments.api.PaymentsClient({ environment: "TEST" });
const data = await client.loadPaymentData({
  apiVersion: 2,
  apiVersionMinor: 0,
  allowedPaymentMethods: [{
    type: "CARD",
    parameters: {
      allowedAuthMethods: ["PAN_ONLY", "CRYPTOGRAM_3DS"],
      allowedCardNetworks: ["VISA", "MASTERCARD"],
    },
    tokenizationSpecification: {
      type: "PAYMENT_GATEWAY",
      parameters: { gateway: "example", gatewayMerchantId: "exampleGatewayMerchantId" },
    },
  }],
  transactionInfo: { totalPriceStatus: "FINAL", totalPrice: "860.00", currencyCode: "MXN", countryCode: "US" },
  merchantInfo: { merchantName: "Puente" },
});

// Apple Pay — la web exige Merchant ID, certificado y
// /.well-known/apple-developer-merchantid-domain-association
const session = new ApplePaySession(3, {
  countryCode: "US",
  currencyCode: "MXN",
  supportedNetworks: ["visa", "masterCard", "amex"],
  merchantCapabilities: ["supports3DS"],
  total: { label: "Puente", amount: "860.00" },
});
session.onvalidatemerchant = (event) => {
  // Tu servidor cambia event.validationURL por una sesión de Apple.
};