Customers & payment methods

How Billing reuses customers and stored methods from Payments.

View as MarkdownInstall skills

Billing does not own a separate notion of "who pays" — it reuses the exact customer and stored payment method objects you already created in Payments. This page covers the billing-specific fields layered on top of that shared model: which method a subscription charges, how addresses drive tax, and what happens when the method on file stops working.

One customer, two pillarsAsk

A customer created for a one-off payment is the same record a subscription bills. There is no migration step and no "billing customer" — a cust_ id is portable across both pillars. Billing simply reads extra fields from it.

FieldOwned byWhat Billing uses it for
emailSharedSending invoices and dunning notices.
default_payment_methodSharedThe method each invoice is collected against.
billing_addressBilling-relevantComputing tax and rendering invoices.
tax_idsBilling-relevantReverse-charge / VAT exemption logic.
balanceBillingCredit applied to future invoices (negative = credit owed to customer).
import { Vinr } from '@vinr/sdk';
const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

// Same object Payments uses — billing fields are just additional properties.
const customer = await vinr.customers.create({
  email: 'ops@northwind.example',
  name: 'Northwind GmbH',
  billingAddress: {
    line1: 'Friedrichstrasse 12',
    city: 'Berlin',
    postalCode: '10117',
    country: 'DE',
  },
  taxIds: [{ type: 'eu_vat', value: 'DE123456789' }],
});                               // "cust_..."

Default payment methodAsk

Every recurring invoice is collected against a single method. VINR resolves it in priority order:

Subscription-level method

If the subscription has its own default_payment_method, it wins. Use this when one customer pays for several subscriptions with different cards.

Customer-level method

Otherwise VINR falls back to the customer's default_payment_method.

No method on file

If neither is set, the invoice finalizes but cannot be collected and emits invoice.payment_failed, which starts dunning.

// Pin a specific stored method to one subscription, overriding the customer default.
await vinr.subscriptions.update('sub_4Qe1...', {
  defaultPaymentMethod: 'pm_9Hh2...',
});

Stored methods are created and tokenized entirely in Payments. Billing never sees raw card data — it only references the pm_ token. See Saving payment methods.

Billing details & addressesAsk

The billing_address is the input to tax calculation, so keep it current. Changing it on the customer affects future invoices only; already-finalized invoices keep the address that was in effect when they locked. The tax_ids array determines whether VINR applies a reverse charge (B2B cross-border) or treats the sale as taxable in the customer's country.

await vinr.customers.update('cust_8Tg3...', {
  billingAddress: { line1: 'Rue de la Loi 100', city: 'Brussels', postalCode: '1000', country: 'BE' },
});

Updating the method on fileAsk

Customers update cards through a hosted update flow or by saving a new method in your own UI. The common pattern is: collect and tokenize the new method in Payments, then point the customer's default at it.

// 1. New method tokenized client-side (returns "pm_...").
// 2. Promote it to the customer default — the next invoice uses it automatically.
await vinr.customers.update('cust_8Tg3...', {
  defaultPaymentMethod: 'pm_NewK4...',
});

Prop

Type

Failed-method handlingAsk

When collection fails, VINR distinguishes a transient failure (insufficient funds, soft decline) from a permanent one (expired or revoked card). Both emit invoice.payment_failed, but they route differently:

  • Transient — dunning retries the same method on its schedule. No action needed unless retries exhaust.
  • Permanent — VINR flags the method as requires_action; retrying it will never succeed, so dunning prompts the customer to add a new card.

Subscribe to the event to drive your own recovery emails or in-app banners.

// In your webhook handler. Signature header: x-vinr-signature.
const event = vinr.webhooks.verify(payload, signature);

if (event.type === 'invoice.payment_failed') {
  const invoice = event.data.object;            // "inv_..."
  if (invoice.lastPaymentError?.type === 'permanent') {
    await sendUpdateCardEmail(invoice.customer);
  }
}

In sandbox, use 4000 0000 0000 0002 to simulate a decline and exercise your failed-method path before going live. See the sandbox test cards.

Next stepsAsk

Was this page helpful?
Edit on GitHub

Last updated on

On this page