> This page is for NT Connect.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://partner.ninjatrader.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://partner.ninjatrader.com/_mcp/server.

# Wallet Pay Funding

NinjaTrader Connect supports deposits funded with Apple Pay or Google Pay. The deposit does not save a card. For deposits from a saved card, see [Debit-card funding](/connect/overview/funding/debit-card-funding).

> **Warning**
>
> Apple Pay can only be tested on Apple devices. Neither Apple Pay nor Google Pay can work end to end with Stripe sandbox (test cards) in NinjaTrader.

> **Warning**
>
> These endpoints are Live-only. Do not send them to the Demo base URL.

> **Warning**
>
> **Prerequisite: register your hostnames with NinjaTrader**
>
> Before implementing or testing Apple Pay or Google Pay, provide NinjaTrader with every hostname that will host the integration so it can be registered as a Stripe Payment Method Domain. Submit hostnames only, without `https://`, paths, or ports. Wait for confirmation that the domains are registered and that Apple Pay and Google Pay are active before continuing. Each preview or temporary hostname used for testing must also be registered.

This requirement is separate from the Live-only warning above. The hostname must be registered before you initialize or test a wallet. NinjaTrader's funding endpoints are Live-only and must not be sent to the Demo API base URL.

Stripe shows Apple Pay only when its requirements are met, including an active payment-method domain, a supported browser and device, a configured wallet, and a supported currency and region. See Stripe's [Express Checkout Element](https://docs.stripe.com/elements/express-checkout-element) and [Payment Method Domains](https://docs.stripe.com/api/payment_method_domains) documentation.

For end-to-end testing, serve the integration from one of the registered HTTPS hostnames, and test on every supported target device and browser.

## Prerequisites

Your integration needs:

* a bearer access token for the user
* the user's eligible live `accountId` and the USD `currencyId`
* Stripe.js or an applicable Stripe client integration with Apple Pay or Google Pay available on the user's device and browser

## Complete the funding disclosures

Before the wallet flow, the user must review and sign both funding disclosures:

1. **Payment Processor Disclosure and Authorization**
2. **Form of Risk Disclosure and Customer Authorization**

Both disclosures must be signed, but the signing order is not enforced. Previously signed copies satisfy the requirement. `startStripeFunding` rejects requests when either disclosure is missing. Use the published `signUserDocument` operation by following [Funding disclosures](/connect/overview/funding/funding-disclosures). If you prefer a hosted signing and funding flow, see [Embedding the Funding Page with an iframe](/connect/overview/partner-integration/embedding-funding-page-with-i-frame).

## Get the Stripe configuration

Call [`getStripeConfiguration`](/connect/api/rest-api-endpoints/users/get-stripe-configuration) to obtain the Stripe publishable key. Never send Stripe secret keys to the browser.

## Initialize Stripe.js

Call `loadStripe(publishableKey)` with the key from `getStripeConfiguration`. It can return `null`, so check the result before you continue.

## Check eligibility and limits

Call [`checkStripeFundingEligibility`](/connect/api/rest-api-endpoints/funds/check-stripe-funding-eligibility) before the wallet flow:

```json
{
  "accountId": 123456,
  "currencyId": 1
}
```

Use the returned `minimumAmount` and `maximumAmount` for the amount UI. Do not hard-code them. To validate a specific deposit, repeat the request with `amount`.

Stop when `errorText` is non-empty. While a deposit is still `Pending`, `Suspended`, `Locked`, or `InTransit`, a check without `amount` returns a `maximumAmount` lowered by that deposit's amount, and an amount-specific check that is otherwise within limits returns a non-empty `errorText` and no `maximumAmount`. Reconcile that deposit and wait for it to settle instead of retrying.

## Wallet funding flow

The wallet flow does not create a saved card and does not use a NinjaTrader funding-method ID. NinjaTrader performs the server-side step with the ConfirmationToken, so you don't create or confirm a PaymentIntent. Stripe's documentation covers the client-side details: see the [Stripe Express Checkout Element](https://docs.stripe.com/elements/express-checkout-element) and the [Stripe ConfirmationToken flow](https://docs.stripe.com/payments/finalize-payments-on-the-server).

1. The customer selects Apple Pay or Google Pay in Stripe's UI, and Stripe produces a ConfirmationToken. NinjaTrader calls this value `stripeConfirmationToken`. If Stripe doesn't produce a token, nothing was sent to NinjaTrader, so don't call it.
2. Once you have the token, call `startStripeFunding` as described in [Submit the NinjaTrader funding request](#submit-the-ninjatrader-funding-request).

## Submit the NinjaTrader funding request

Before you send the request, capture the baseline described in [Reconcile the funding outcome](/connect/overview/funding/reconcile-the-funding-outcome#capture-a-baseline).

### Guard against duplicates

Your client keeps two in-memory guards:

* `confirmationInFlight` prevents concurrent handling while the token is being created.
* `fundingRequestSent` becomes true for the life of that flow after your [durable marker](/connect/overview/funding/reconcile-the-funding-outcome#keep-a-durable-marker-wallet-funding) is written and confirmed, and immediately before you call `startStripeFunding`. Don't reset it. Create new guards with a new flow instead.

The order is fixed: once you have the ConfirmationToken, write your marker and wait until the write is confirmed, set `fundingRequestSent` to `true`, and only then call [`startStripeFunding`](/connect/api/rest-api-endpoints/funds/start-stripe-funding).

If the marker write fails or can't be confirmed, don't send the request. Fail the Stripe interaction (the example does this in its `catch` block), and tell the user to retry or use the [NinjaTrader-hosted funding flow](/connect/overview/partner-integration/embedding-funding-page-with-i-frame). Nothing was sent to NinjaTrader and the token is unused.

A ConfirmationToken is single-use client output. Pass it to NinjaTrader immediately. Never log it, persist it, place it in a URL, resend it, or use it in a different funding request. Creating a ConfirmationToken doesn't prove that funding succeeded.

### Send the request

Call `startStripeFunding` with `stripeConfirmationToken` set to the token ID, `flowType` set to `WalletFunding`, and `amount` in major units:

```json
{
  "accountId": 123456,
  "amount": 250,
  "currencyId": 1,
  "stripeConfirmationToken": "ctoken_example",
  "flowType": "WalletFunding"
}
```

### Convert the amount

`startStripeFunding` takes `amount` in major units (`250` for \$250.00), while Stripe uses minor units (`25000`).

* Reject amounts with more than two decimal places.
* Round when you derive one amount from the other, as the example does.
* Send NinjaTrader the same amount you gave Stripe. If it changes before any request is sent, update it in Stripe and capture a fresh baseline, or start a new flow. After `fundingRequestSent` is `true`, it can't change.

### Handle the response

While the flow is locked (`fundingRequestSent` is `true`), keep the wallet actions unavailable and show your own "request in progress, check status" state.

* **Error, empty, missing, or timed out:** the deposit may have been submitted. Keep the flow locked, don't resend the token or send a second request, and reconcile.
* **`HTTP 429` or `p-ticket`:** stop, don't retry with the ticket, and discard the old token.
* **Anything else:** a response never proves that NinjaTrader created the deposit. Follow [Reconcile the funding outcome](/connect/overview/funding/reconcile-the-funding-outcome) for the outcome, rate limits and CAPTCHA, and the durable marker.

## Example

The following example shows the flow in a single block. It omits UI state, error display, the `FundTransaction` baseline and reconciliation, penalty-ticket handling, and your server-side marker implementation, which [Submit the NinjaTrader funding request](#submit-the-ninjatrader-funding-request) and [Reconcile the funding outcome](/connect/overview/funding/reconcile-the-funding-outcome) describe. Write and confirm the marker between creating the token and setting `fundingRequestSent`.

```javascript
import { loadStripe } from "@stripe/stripe-js";

const stripe = await loadStripe(publishableKey);
if (!stripe) {
  throw new Error("Stripe.js did not initialize.");
}

const amount = 250; // NinjaTrader amount, major units
const amountInCents = Math.round(amount * 100); // Stripe Elements amount, minor units

const elements = stripe.elements({
  mode: "payment",
  amount: amountInCents,
  currency: "usd",
});

const expressCheckout = elements.create("expressCheckout", {
  paymentMethodOrder: ["apple_pay", "google_pay"],
});

// Show or enable a wallet action only while Stripe reports that wallet available.
function setWalletAvailable(wallet, available) {
  // Update your UI.
}

expressCheckout.on("ready", ({ availablePaymentMethods }) => {
  setWalletAvailable("applePay", Boolean(availablePaymentMethods?.applePay));
  setWalletAvailable("googlePay", Boolean(availablePaymentMethods?.googlePay));
});

expressCheckout.on("availablepaymentmethodschange", ({ paymentMethods }) => {
  setWalletAvailable("applePay", Boolean(paymentMethods?.applePay?.available));
  setWalletAvailable("googlePay", Boolean(paymentMethods?.googlePay?.available));
});

expressCheckout.on("loaderror", () => {
  setWalletAvailable("applePay", false);
  setWalletAvailable("googlePay", false);
});

let confirmationInFlight = false;
let fundingRequestSent = false;

expressCheckout.on("confirm", async (event) => {
  if (confirmationInFlight || fundingRequestSent) {
    return;
  }

  if (event.expressPaymentType !== "apple_pay" && event.expressPaymentType !== "google_pay") {
    event.paymentFailed({ reason: "fail" });
    return;
  }

  confirmationInFlight = true;

  try {
    const submitted = await elements.submit();

    if (submitted.error) {
      confirmationInFlight = false;
      event.paymentFailed({
        reason: "invalid_payment_data",
        message: submitted.error.message,
      });
      return;
    }

    const result = await stripe.createConfirmationToken({ elements });

    if (result.error || !result.confirmationToken) {
      confirmationInFlight = false;
      event.paymentFailed({ reason: "fail" });
      return;
    }

    // Your marker write goes here (omitted). If it fails, throw before setting
    // fundingRequestSent so nothing is sent.
    fundingRequestSent = true;

    // Your function that calls NinjaTrader's startStripeFunding endpoint.
    await startStripeFunding({
      accountId: 123456,
      amount,
      currencyId: 1,
      stripeConfirmationToken: result.confirmationToken.id,
      flowType: "WalletFunding",
    });
    // The response does not prove NinjaTrader created the deposit: inspect
    // errorText and p-ticket, then reconcile with FundTransaction records.
  } catch (error) {
    // The error is intentionally not logged here.
    if (!fundingRequestSent) {
      confirmationInFlight = false;
      event.paymentFailed({ reason: "fail" });
    }

    // If the funding request might have been sent, keep the flow locked and
    // reconcile against FundTransaction records.
  }
});

// Start unavailable until Stripe reports a wallet available.
setWalletAvailable("applePay", false);
setWalletAvailable("googlePay", false);

// Mount Express Checkout into your page container.
expressCheckout.mount("#express-checkout");
```

## Next steps

* Call `startStripeFunding` with a wallet confirmation token to start the Apple Pay or Google Pay deposit.
* [Reconcile the funding outcome](/connect/overview/funding/reconcile-the-funding-outcome) with the linked DCR `FundTransaction` and `CashBalanceLog` records before you report it.
* If you prefer a NinjaTrader-hosted flow, see [Embedding the Funding Page with an iframe](/connect/overview/partner-integration/embedding-funding-page-with-i-frame).
* To fund a deposit from a saved card instead, see [Debit-card funding](/connect/overview/funding/debit-card-funding).