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.
| Flow | Use it when | Carden key scopes |
|---|---|---|
| Linked QuickBooks source | The payment maps to one imported, fully unpaid invoice. | invoices:read + enrichment:write; the report worker also needs payments:write. |
| Inline invoice | The backend already has a complete CanonicalInvoice v1. | enrichment:write; add payments:write when the same service reports. |
| Reporting only | The service only records payment outcomes. | payments:write. |
Set up the linked source
- Connect the merchant's QuickBooks company and complete the initial invoice import.
- Review source exceptions and approve only factual product-code or unit mappings.
- Link QuickBooks to Stripe inside the same merchant and environment with authority over both integrations.
- 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.
Find
Load the invoice
Resolve one imported invoice and read its current revision.
Prepare
Create Stripe fields
Carden validates amount, currency, source state, and revision.
Save
Store the preparation
Attach immutable preparation evidence and the diagnostic request ID to the attempt.
Pay
Execute Stripe once
The backend merges enrichment and uses its own Stripe idempotency key.
Queue
Persist the observation
A durable worker sends the saved report independently of checkout.
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
| Identifier | Purpose |
|---|---|
| Preparation idempotencyKey | Reuses an identical preparation request while current source checks still pass. |
| preparation.id | Stable source-backed preparation identity used as the report enrichmentRequestId. |
| x-carden-request-id | Identifies one Carden HTTP request for diagnostics. |
| Stripe idempotency key | Protects one merchant-owned Stripe payment operation. |
| attemptId / eventId | Identify 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.