Tax details in Stripe Checkout

If you sell with Stripe Checkout, the session can ask for the tax details that the Fiscalapi for Stripe app needs to issue the invoice in your customer's name as soon as the payment is confirmed. Without them, the payment goes into the global invoice (factura global, one CFDI (Mexico's electronic invoice) that groups a period's sales to the general public) for its period.

What the session asks for

An invoice in your customer's name needs five details. Stripe Checkout collects the RFC (Mexico's taxpayer ID, Registro Federal de Contribuyentes) and the legal name registered with the SAT (Mexico's tax authority, Servicio de Administración Tributaria) in its own fields, and the other three in custom fields whose key the app reads:

DetailSession parameterKey
RFCtax_id_collectionTax ID of type mx_rfc
Legal namename_collection.businessBusiness name
Tax regime (régimen fiscal, the SAT category a taxpayer is registered under, such as 601 or 626)custom_fields, dropdownfiscalapiTaxRegimeCode
CFDI use (how the recipient will use the CFDI for tax purposes)custom_fields, dropdownfiscalapiCfdiUseCode
Postal code of the tax addresscustom_fields, numeric, 5 digitsfiscalapiZipCode

For an individual (persona física), the app uses the individual name as the legal name if the session includes it, and otherwise the business name. That's why asking for the business name is enough. The invoice email is the one your customer enters in Checkout.

Create the session

Stripe Checkout has no conditional fields: if the five details are required, it also asks anyone who doesn't want an invoice. Ask on your page first whether your customer wants an invoice, and create the session with these fields only in that case.

Create a Checkout session

import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)

const taxRegimes = [
  ['601', 'General de Ley Personas Morales'],
  ['603', 'Personas Morales con Fines no Lucrativos'],
  ['605', 'Sueldos y Salarios e Ingresos Asimilados a Salarios'],
  ['606', 'Arrendamiento'],
  ['607', 'Régimen de Enajenación o Adquisición de Bienes'],
  ['608', 'Demás ingresos'],
  ['610', 'Residentes en el Extranjero sin Establecimiento Permanente en México'],
  ['611', 'Ingresos por Dividendos (socios y accionistas)'],
  ['612', 'Personas Físicas con Actividades Empresariales y Profesionales'],
  ['614', 'Ingresos por intereses'],
  ['615', 'Régimen de los ingresos por obtención de premios'],
  ['616', 'Sin obligaciones fiscales'],
  ['620', 'Sociedades Cooperativas de Producción que optan por diferir sus ingresos'],
  ['621', 'Incorporación Fiscal'],
  ['622', 'Actividades Agrícolas, Ganaderas, Silvícolas y Pesqueras'],
  ['623', 'Opcional para Grupos de Sociedades'],
  ['624', 'Coordinados'],
  ['625', 'Actividades Empresariales con ingresos a través de Plataformas Tecnológicas'],
  ['626', 'Régimen Simplificado de Confianza'],
]

const cfdiUses = [
  ['G01', 'Adquisición de mercancías'],
  ['G02', 'Devoluciones, descuentos o bonificaciones'],
  ['G03', 'Gastos en general'],
  ['I01', 'Construcciones'],
  ['I02', 'Mobiliario y equipo de oficina por inversiones'],
  ['I03', 'Equipo de transporte'],
  ['I04', 'Equipo de cómputo y accesorios'],
  ['I05', 'Dados, troqueles, moldes, matrices y herramental'],
  ['I06', 'Comunicaciones telefónicas'],
  ['I07', 'Comunicaciones satelitales'],
  ['I08', 'Otra maquinaria y equipo'],
  ['D01', 'Honorarios médicos, dentales y gastos hospitalarios'],
  ['D02', 'Gastos médicos por incapacidad o discapacidad'],
  ['D03', 'Gastos funerales'],
  ['D04', 'Donativos'],
  ['D05', 'Intereses reales pagados por créditos hipotecarios'],
  ['D06', 'Aportaciones voluntarias al SAR'],
  ['D07', 'Primas por seguros de gastos médicos'],
  ['D08', 'Gastos de transportación escolar obligatoria'],
  ['D09', 'Depósitos en cuentas para el ahorro y planes de pensiones'],
  ['D10', 'Pagos por servicios educativos (colegiaturas)'],
  ['S01', 'Sin efectos fiscales'],
]

const options = (catalog) =>
  catalog.map(([value, description]) => ({ value, label: `${value} - ${description}` }))

function fiscalDataFields(wantsInvoice) {
  if (!wantsInvoice) return {}
  return {
    tax_id_collection: { enabled: true, required: 'if_supported' },
    name_collection: { business: { enabled: true } },
    custom_fields: [
      {
        key: 'fiscalapiTaxRegimeCode',
        label: { type: 'custom', custom: 'Régimen fiscal' },
        type: 'dropdown',
        dropdown: { options: options(taxRegimes) },
      },
      {
        key: 'fiscalapiCfdiUseCode',
        label: { type: 'custom', custom: 'Uso del CFDI' },
        type: 'dropdown',
        dropdown: { options: options(cfdiUses), default_value: 'G03' },
      },
      {
        key: 'fiscalapiZipCode',
        label: { type: 'custom', custom: 'Código postal fiscal' },
        type: 'numeric',
        numeric: { minimum_length: 5, maximum_length: 5 },
      },
    ],
  }
}

// wantsInvoice comes from your page, for example from a checkbox labeled "I want an invoice".
export async function createCheckoutSession(wantsInvoice) {
  return stripe.checkout.sessions.create({
    mode: 'payment',
    line_items: [
      {
        price_data: {
          currency: 'mxn',
          unit_amount: 11600,
          product_data: { name: 'Suscripción anual' },
        },
        quantity: 1,
      },
    ],
    customer_creation: 'always',
    success_url: 'https://example.com/gracias',
    ...fiscalDataFields(wantsInvoice),
  })
}

The lists include the tax regimes and CFDI uses that the SAT allows on a sales invoice. CP01 and CN01 are left out because they belong to the payment complement (the complement that records each payment received against a PPD invoice) and to the payroll complement. A session allows up to three custom fields, so these three use all of the available ones.

What the app does with the session

When the payment is confirmed, the app reads the session's details and issues the invoice. If the details are valid and the session creates or uses a Customer, the app also saves them in it, so that customer's later purchases are invoiced even if the session no longer asks for them.

SituationWhat the app does
The session includes all five details, and they're validIssues the invoice in your customer's name.
The session has no RFCThe payment goes into the global invoice.
The session includes an RFC, but another detail is missing or isn't validDoesn't issue the invoice: the payment goes into the global invoice, unless you correct them from the app's panel on the Customer in the Stripe Dashboard before the payment goes into the global invoice for its period.
It's a subscription renewalUses the details saved in the Customer.

The app checks that the tax regime matches whether the RFC belongs to an individual or a legal entity, and that the CFDI use is allowed for that regime, with the same SAT rules that Fiscalapi applies before stamping (the certification by a SAT-authorized provider that makes a CFDI valid).

Was this page helpful?