Actores

Hay dos actores en cada integración con Chytapay. Distinguirlos es clave para evitar errores.

RolQuién esQué hace
Administrador de integraciónVos — la empresa o producto que integra con Chytapay.Tu backend habla con la Integration API usando clientId/clientSecret. Tu equipo configura el portal admin (webhook, redirect URIs).
Cuenta de comercioEl usuario Chytapay (cliente final) que recibe los pagos.Autoriza tu integración vía OAuth una vez. Su identidad es email + password de Chytapay. Después, vos podés crear payment requests en su nombre usando el refreshToken que guardaste.
💡Confundir estos dos es el error más frecuente — intentar OAuth con tu cuenta de admin del portal en vez de con la cuenta de comercio del merchant. La cuenta de comercio es la que recibe los pagos; vos sos el que orquesta.

Vinculación inicial vs uso recurrente

Hay dos flujos distintos. Confundirlos es el error más frecuente en integraciones nuevas.

Flujo de vinculación (una sola vez)
Error: Object.hasOwn is not a function

Después de vincular una vez, cada cobro sigue este flujo más simple — todo server-side, sin intervención del usuario.

Uso recurrente (cada cobro)
Error: Object.hasOwn is not a function
pseudo-code — oauth-callback handler
// GET /oauth/callback  (your redirect_uri — Chytapay redirects here with ?code)
async function oauthCallback(req, res) {
  const { code } = req.query;

  const tokens = await authApi.post('/integration/oauth2/token', {
    code,
    clientId: CLIENT_ID,
    clientSecret: CLIENT_SECRET,
    redirectUri: REDIRECT_URI,
  });

  // Persist the refreshToken linked to the user
  await db.saveTokens(userId, tokens.idToken, tokens.refreshToken);

  res.redirect('/success');
}
pseudo-code — recurrent use (payment-request)
// Every time you need to charge the user
async function chargeUser(userId, amount) {
  let { idToken, refreshToken } = await db.getTokens(userId);

  // If idToken is expired (every 1h), refresh it
  if (isExpired(idToken)) {
    const refreshed = await authApi.post('/integration/oauth2/refresh', {
      refreshToken,
      clientId: CLIENT_ID,
      clientSecret: CLIENT_SECRET,
    });
    idToken = refreshed.idToken;
    await db.saveIdToken(userId, idToken);
  }

  // Create the charge with the current token
  return integrationApi.post('/payment-request', {
    referenceId: 'cuota-feb-2025',
    amount,
    description: 'Cuota mensual',
    dueDates: ['2025-02-15'],
    sendWhatsappNotification: true,
    sendEmailNotification: true,
    customer: {
      name: 'Juan Pérez',
      phoneNumber: { countryCode: '+54', number: '1112345678' },
      email: '[email protected]',
    },
  }, { headers: { Authorization: `Bearer ${idToken}` } });
}
💡El redirect_uri solo importa en la vinculación inicial. En el flujo recurrente nunca abrís el navegador — es todo server-side.