> ## Documentation Index
> Fetch the complete documentation index at: /docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Accept Payments

> Offer Stripe online payment and certified POS-native pay at store from one order-first flow.

> [!WARNING]
>
> Managed Cloud projects use their included storefront and pinned runtime. The
> published SDK version and this platform source candidate do not establish
> compatibility with a Cloud installation. Verify the deployed API and caller
> pair using the [Cloud release
> results](/docs/guides/crave-cloud-overview.md#developer-package-compatibility).

Stripe is the only online payment processor. Square, Clover, and Toast receive
fulfillment orders and provide certified in-person tender evidence; they never
expose browser checkout.

## Read the server-owned choices

Do not infer payment choices from a connected provider or a location setting.
Load the current cart's `collection-options` first:

```ts theme={null}
const choices = await storefront.checkout.getCollectionOptions(
  locationId,
  cartId,
  { revision: cart.revision },
);

const online = choices.options.find(
  ({ collectionMode }) => collectionMode === "ONLINE",
);
const payAtStore = choices.options.find(
  ({ collectionMode }) => collectionMode === "PAY_AT_STORE",
);
```

The response is a read-only preflight. Submission rechecks the current quote,
catalog, policy, customer eligibility, unpaid-order exposure, provider binding,
and native-payment certification. If an option is unavailable, show its
`unavailableReasonCode`; never override it in the browser.

## Pay online with Stripe

```ts theme={null}
const outcome = await storefront.checkout.submitOrder(
  locationId,
  cartId,
  {
    acceptedCatalogEvidence,
    acceptedDiscountEvidence,
    collectionMode: "ONLINE",
    customerConfirmationRevision,
    quoteFingerprint,
  },
  { idempotencyKey, revision: cart.revision },
);
```

The SDK sends the cart capability, current revision, and idempotency key. A raw REST request must send the same headers:

```bash theme={null}
curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/cart_456/submissions" \
  -H "X-Cart-Token: $CART_ACCESS_TOKEN" \
  -H 'If-Match: "cart-4"' \
  -H "Idempotency-Key: payment_01"
```

When the outcome is `CUSTOMER_ACTION_REQUIRED`, initialize Stripe.js with its
`connectedAccountId` and confirm its `clientSecret`. The server captures and
settles the already-persisted order. No provider-selected confirmation endpoint exists.

## Confirm a Stripe session with Stripe Elements

```tsx theme={null}
import {
  Elements,
  PaymentElement,
  useElements,
  useStripe,
} from "@stripe/react-stripe-js";
import type { OrderSubmissionOutcome } from "@craveup/storefront-sdk";
import { loadStripe } from "@stripe/stripe-js";

if (outcome.status !== "CUSTOMER_ACTION_REQUIRED") return;

const stripePromise = loadStripe(
  process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
  {
    stripeAccount: outcome.connectedAccountId,
  },
);

<Elements
  stripe={stripePromise}
  options={{ clientSecret: outcome.clientSecret }}
>
  <PaymentForm outcome={outcome} />
</Elements>;

export function PaymentForm({ outcome }: { outcome: OrderSubmissionOutcome }) {
  const stripe = useStripe();
  const elements = useElements();

  async function submit(event: React.FormEvent) {
    event.preventDefault();
    if (!stripe || !elements) return;

    const { error, paymentIntent } = await stripe.confirmPayment({
      elements,
      confirmParams: {
        return_url: `${window.location.origin}/checkout/stripe-return`,
      },
      redirect: "if_required",
    });

    if (error) throw new Error(error.message);
    if (
      !paymentIntent ||
      !["requires_capture", "succeeded"].includes(paymentIntent.status)
    ) {
      throw new Error("Stripe payment is not ready for capture");
    }
    await storefront.checkout.getOrderSubmissionOutcome(
      locationId,
      cartId,
      outcome.attemptId,
    );
  }

  return (
    <form onSubmit={submit}>
      <PaymentElement />
      <button>Pay now</button>
    </form>
  );
}
```

Cache Stripe.js by the immutable connected-account id and rebuild Elements whenever the payment
outcome changes. Stripe secret keys remain on Crave-managed services. Never collect or log raw card
numbers. If Stripe redirects for authentication, the return page must recover the same cart session
from tab-scoped storage and make the same idempotent server-capture request before showing success.

After Stripe returns, poll `storefront.checkout.getOrderSubmissionOutcome(locationId, cartId, attemptId)`. Treat only `PAID` as paid, keep `PROCESSING` retryable, and surface `ACTION_REQUIRED` without creating another order.

## Pay at the store

Offer this choice only when the server returns an available `PAY_AT_STORE`
option and identifies `SQUARE`, `CLOVER`, or `TOAST` as its settlement adapter.
Submit the same order-first contract without a saved payment method:

```ts theme={null}
if (!payAtStore?.available || !payAtStore.settlementAdapter) return;

const outcome = await storefront.checkout.submitOrder(
  locationId,
  cartId,
  {
    acceptedCatalogEvidence,
    acceptedDiscountEvidence,
    collectionMode: "PAY_AT_STORE",
    customerConfirmationRevision,
    quoteFingerprint,
  },
  { idempotencyKey, revision: cart.revision },
);
```

An accepted pay-at-store order returns `status: "ACCEPTED"` and remains unpaid
until the selected POS reports native tender evidence. Tell the customer that
payment is due at the store and keep order state separate from payment state.
Poll the same `attemptId`; never submit a replacement order and never render a
POS card form, saved card, gift-card entry, or provider-hosted checkout.

When counter-verification is unresolved, show **Verifying payment — do not
charge again**. In `outcome.verification.state`, `PENDING` and `WARNING`
continue reconciliation, while `ACTION_REQUIRED` needs staff review. Only the
top-level `PAID` outcome proves collection. The POS provider owns the original
in-person tender and its refund path. A provider order cancellation is not
refund evidence.

> [card] [Checkout Flow](/docs/guides/checkout-flow.md)
>
> See the complete checkout state machine.

> [card] [Order Tracking](/docs/guides/order-tracking.md)
>
> Poll the order result and load protected receipts.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.