Skip to documentation content

QuickBooks to Stripe

Prepare a Stripe payment from one authoritative QuickBooks invoice and report the outcome from saved evidence.

On this page

Choose the linked flow deliberately

Carden reads an imported QuickBooks invoice and returns fields for the merchant's existing Stripe PaymentIntent request. The merchant backend executes the payment with its own Stripe credentials and later sends Carden normalized observations.

FlowUse it whenCarden key scopes
Linked QuickBooks sourceThe payment maps to one imported, fully unpaid invoice.invoices:read + enrichment:write; the report worker also needs payments:write.
Inline invoiceThe backend already has a complete CanonicalInvoice v1.enrichment:write; add payments:write when the same service reports.
Reporting onlyThe service only records payment outcomes.payments:write.

Set up the linked source

  1. Connect the merchant's QuickBooks company and complete the initial invoice import.
  2. Review source exceptions and approve only factual product-code or unit mappings.
  3. Link QuickBooks to Stripe inside the same merchant and environment with authority over both integrations.
  4. Create a Stripe-integration Carden key with invoices:read and enrichment:write; give the report worker payments:write.

Linking does not change existing keys. Create or rotate a key when a service needs invoice access, then keep it in server-only secret configuration.

Use one ready, fully unpaid invoice

Look up the invoice by exact QuickBooks ID or exact invoice number. Retrieve the detail and check ready, issues, snapshotId, and revision immediately before preparation.

  • The invoice must be open, positive, fully unpaid, and unambiguous.
  • The PaymentIntent amount and currency must match the invoice total and balance.
  • The source must be active, current, and free of pending refresh work.
  • Partial payments, combined invoices, overpayments, and ambiguous allocations require another supported workflow.

Prepare, pay, and queue the report

The example assumes order and attempt are saved application records. attemptStore, executeStripePayment, and reportOutbox belong to the merchant backend. The Stripe operation returns an expanded latest_charge so the observation uses actual captured and refunded totals.

The linked payment flow

Save each identifier before the operation that depends on it.

  1. Find

    Load the invoice

    Resolve one imported invoice and read its current revision.

  2. Prepare

    Create Stripe fields

    Carden validates amount, currency, source state, and revision.

  3. Save

    Store the preparation

    Attach immutable preparation evidence and the diagnostic request ID to the attempt.

  4. Pay

    Execute Stripe once

    The backend merges enrichment and uses its own Stripe idempotency key.

  5. Queue

    Persist the observation

    A durable worker sends the saved report independently of checkout.

Server-side linked payment
import { paymentObservationFromStripePaymentIntent } from "@carden/node";

const matches = await carden.invoices.list({
  quickbooksInvoiceId: order.quickbooksInvoiceId,
  limit: 2,
});
if (matches.invoices.length !== 1 || matches.nextCursor) {
  throw new Error("Resolve the missing or ambiguous invoice before continuing.");
}

const invoice = await carden.invoices.retrieve(matches.invoices[0].id);
if (!invoice.ready || !invoice.revision) {
  throw new Error("Resolve the invoice issues before continuing.");
}

const { enrichment, preparation, requestId } =
  await carden.stripe.createPaymentIntentEnrichmentFromInvoice({
    invoiceId: invoice.id,
    amountMinor: order.amountMinor,
    currency: order.currency,
    expectedRevision: invoice.revision,
    idempotencyKey: attempt.preparationIdempotencyKey,
  });

await attemptStore.attachPreparation(attempt.attemptId, { preparation, requestId });

const result = await executeStripePayment({
  amount: preparation.amountMinor,
  currency: preparation.currency,
  ...enrichment,
}, { idempotencyKey: attempt.stripeIdempotencyKey });

await reportOutbox.enqueue({
  eventId: attempt.eventId,
  attemptId: attempt.attemptId,
  occurredAt: new Date().toISOString(),
  source: "api",
  enrichmentRequestId: preparation.id,
  ...(invoice.invoiceNumber ? { invoiceReference: invoice.invoiceNumber } : {}),
  ...paymentObservationFromStripePaymentIntent(result),
});

The outbox worker calls carden.stripe.reportPayment(report, { maxRetries: 0 }), keeps the same eventId across delivery retries, and treats duplicate: true as acknowledgment.

Keep identities separate

IdentifierPurpose
Preparation idempotencyKeyReuses an identical preparation request while current source checks still pass.
preparation.idStable source-backed preparation identity used as the report enrichmentRequestId.
x-carden-request-idIdentifies one Carden HTTP request for diagnostics.
Stripe idempotency keyProtects one merchant-owned Stripe payment operation.
attemptId / eventIdIdentify one payment attempt and one immutable observation.

A preparation retry can have a new HTTP request ID while retaining preparation.id. Changed invoice, amount, currency, source revision, or payment intent requires reviewed new work rather than reuse of an old identity.

Report later outcomes from saved evidence

Captures, refunds, cancellations, and later verified Stripe events reuse the original attemptId and preparation.id, while every new observation has a new eventId. Verify Stripe webhook signatures in the merchant backend and report cumulative amounts.

Use the saved preparation even if QuickBooks changes or disconnects after payment. A report amount or currency difference remains an actual payment observation and creates a correlation mismatch exception; Carden never rewrites it to fit the invoice.