Datos fiscales en Stripe Checkout
Si vendes con Stripe Checkout, la sesión puede pedir los datos fiscales que la app de Fiscalapi para Stripe necesita para emitir la factura a nombre de tu cliente en cuanto se confirma el pago. Sin ellos, el pago entra en la factura global de su periodo.
Qué datos pide la sesión
Una factura a nombre de tu cliente necesita cinco datos. Stripe Checkout captura el RFC y la razón social con sus propios campos, y los otros tres con campos personalizados cuya clave lee la app:
| Dato | Parámetro de la sesión | Clave |
|---|---|---|
| RFC | tax_id_collection | Tax ID de tipo mx_rfc |
| Razón social | name_collection.business | Nombre del negocio |
| Régimen fiscal | custom_fields, lista desplegable | fiscalapiTaxRegimeCode |
| Uso del CFDI | custom_fields, lista desplegable | fiscalapiCfdiUseCode |
| Código postal fiscal | custom_fields, numérico de 5 dígitos | fiscalapiZipCode |
Para una persona física, la app usa como razón social el nombre individual si la sesión lo trae y, si no, el nombre del negocio; por eso basta con pedir este último. El correo de la factura es el que tu cliente captura en Checkout.
Crear la sesión
Stripe Checkout no tiene campos condicionales: si los cinco datos son obligatorios, también los pide a quien no quiere factura. Pregunta antes en tu página si tu cliente quiere factura y crea la sesión con los campos solo en ese caso.
Crear una sesión de Checkout
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 sale de tu página: por ejemplo, una casilla «Quiero factura».
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),
})
}
Las listas incluyen los regímenes y los usos del CFDI que el SAT admite en una factura de venta; quedan fuera CP01 y CN01, que son de los complementos de pago y de nómina. Una sesión admite hasta tres campos personalizados, así que estos tres ocupan todos los disponibles.
Qué hace la app con la sesión
Cuando se confirma el pago, la app lee los datos de la sesión y emite la factura. Si los datos son válidos y la sesión crea o usa un Customer, la app también los guarda en él, así que las siguientes compras de ese cliente se facturan aunque la sesión ya no los pida.
| Situación | Qué hace la app |
|---|---|
| La sesión trae los cinco datos y son válidos | Emite la factura a nombre de tu cliente. |
| La sesión no trae RFC | El pago entra en la factura global. |
| La sesión trae RFC, pero falta otro dato o no es válido | No emite la factura: el pago entra en la factura global, salvo que corrijas los datos desde el panel del cliente en Stripe antes de que el pago entre en la factura global de su periodo. |
| Es la renovación de una suscripción | Usa los datos guardados en el Customer. |
La app valida que el régimen fiscal corresponda al tipo de persona del RFC y que el uso del CFDI esté permitido para ese régimen, con las mismas reglas del SAT que aplica Fiscalapi antes de timbrar.