← back to Costa Rica

lib/plaid.js

53 lines

'use strict';
// Plaid client — bank-account verification + ACH for FOREIGN/US hosts only.
// (Costa Rican banks are NOT in Plaid's network — Tico hosts use SINPE, see
// lib/payouts.js.) Sandbox-safe: no PLAID creds => liveMode=false and Link
// token / exchange return deterministic sandbox values.
//
// Env (gated): PLAID_CLIENT_ID, PLAID_SECRET, PLAID_ENV (sandbox|production)

const crypto = require('crypto');
const { fetchT } = require('./payments/http'); // bound every live Plaid call (no infinite hang)
const CLIENT_ID = process.env.PLAID_CLIENT_ID || '';
const SECRET = process.env.PLAID_SECRET || '';
const ENV = process.env.PLAID_ENV || 'sandbox';
const LIVE = !!(CLIENT_ID && SECRET);
const BASE = `https://${ENV}.plaid.com`;

async function _post(path, body) {
  // fetchT bounds connect+headers AND the body read (shared with tilopay/onvo) so a
  // hung Plaid connection during host bank-linking can't leave the request pending
  // forever — closes the last unbounded live provider fetch (PRE-FLIGHT #4/#5 class).
  const res = await fetchT(BASE + path, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ client_id: CLIENT_ID, secret: SECRET, ...body }),
  });
  const j = await res.json();
  if (!res.ok) throw new Error(`plaid ${path} HTTP ${res.status}: ${j.error_code || ''}`);
  return j;
}

// Step 1 (app): create a Link token to open Plaid Link in the app.
async function createLinkToken(userId) {
  if (!LIVE) return { link_token: `link-sandbox-${crypto.randomBytes(6).toString('hex')}`, sandbox: true };
  return _post('/link/token/create', {
    user: { client_user_id: String(userId) },
    client_name: 'Costa Rica Marketplace',
    products: ['auth'], country_codes: ['US'], language: 'en',
  });
}

// Step 2 (app -> server): exchange the public_token for a persistent access_token.
async function exchangePublicToken(publicToken) {
  if (!LIVE) return { access_token: `access-sandbox-${crypto.randomBytes(8).toString('hex')}`, item_id: `item-${crypto.randomBytes(6).toString('hex')}`, sandbox: true };
  return _post('/item/public_token/exchange', { public_token: publicToken });
}

// Fetch the ACH numbers/account so we can store last4 + verified flag.
async function getAuth(accessToken) {
  if (!LIVE) return { accounts: [{ account_id: 'acc_sbx', mask: '4321' }], sandbox: true };
  return _post('/auth/get', { access_token: accessToken });
}

module.exports = { get liveMode() { return LIVE; }, ENV, createLinkToken, exchangePublicToken, getAuth };