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

# Chase Orbital - Bank

> Connect to Chase Orbital to accept ACH bank payments.

export const connector = {
  displayName: "Chase Orbital",
  method: "bank",
  features: "direct_capture partial_refunds refunds verify_credentials",
  supportedCountries: "CA US",
  supportedCurrencies: "CAD USD"
};

export const ConnectorRegions = ({data, kind, name: nameOverride}) => {
  const [query, setQuery] = useState("");
  const [open, setOpen] = useState(false);
  const isCountries = kind === "countries";
  const raw = data && (isCountries ? data.supportedCountries : data.supportedCurrencies);
  const codes = typeof raw === "string" ? raw.split(/\s+/).filter(Boolean) : Array.isArray(raw) ? raw : [];
  const DISPLAY_NAME_OVERRIDES = {
    authorizenet: "Authorize.net",
    cardpointe: "Fiserv CardPointe",
    dlocal: "dLocal",
    shift4i4go: "Shift4 i4go",
    tokenex: "TokenEx"
  };
  const rawName = data && data.displayName || "";
  const name = nameOverride || DISPLAY_NAME_OVERRIDES[rawName.toLowerCase()] || rawName || "This connector";
  const verb = isCountries ? "supports transactions from buyers in" : "supports processing payments in";
  const noun = isCountries ? "countries" : "currencies";
  if (codes.length === 0) return null;
  let displayNames = null;
  try {
    displayNames = new Intl.DisplayNames(["en"], {
      type: isCountries ? "region" : "currency"
    });
  } catch (e) {
    displayNames = null;
  }
  const resolve = code => {
    if (!displayNames) return null;
    try {
      const resolved = displayNames.of(code);
      return resolved && resolved !== code ? resolved : null;
    } catch (e) {
      return null;
    }
  };
  const MAJOR_CURRENCIES = ["USD", "EUR", "GBP", "CAD", "AUD", "JPY", "CHF", "CNY", "SGD", "HKD", "NZD", "SEK", "NOK", "DKK", "MXN", "BRL", "INR"];
  const items = codes.map(code => ({
    code,
    label: resolve(code)
  }));
  if (isCountries) {
    items.sort((a, b) => (a.label || a.code).localeCompare(b.label || b.code));
  } else {
    const rank = code => {
      const i = MAJOR_CURRENCIES.indexOf(code);
      return i === -1 ? MAJOR_CURRENCIES.length : i;
    };
    items.sort((a, b) => rank(a.code) - rank(b.code) || a.code.localeCompare(b.code));
  }
  if (codes.length <= 3) {
    const parts = items.map(it => isCountries || !it.label ? it.label || it.code : `${it.label} (${it.code})`);
    const joined = parts.length === 1 ? parts[0] : parts.length === 2 ? `${parts[0]} and ${parts[1]}` : `${parts.slice(0, -1).join(", ")}, and ${parts[parts.length - 1]}`;
    return <p>
        {name} {verb} {joined}.
      </p>;
  }
  const chipStyle = {
    display: "inline-flex",
    alignItems: "baseline",
    gap: "0.4rem",
    padding: "0.15rem 0.55rem",
    borderRadius: "0.375rem",
    border: "1px solid rgba(128, 128, 128, 0.25)",
    fontSize: "0.875rem",
    lineHeight: 1.5
  };
  const codeStyle = {
    fontFamily: "var(--font-mono, ui-monospace, monospace)",
    fontWeight: 600,
    fontSize: "0.8125rem"
  };
  const controlStyle = {
    color: "inherit",
    background: "transparent",
    border: "1px solid rgba(128, 128, 128, 0.3)",
    borderRadius: "0.5rem",
    fontSize: "0.875rem"
  };
  const renderChip = it => <span key={it.code} style={chipStyle} title={isCountries ? it.code : it.label || it.code}>
      {isCountries ? it.label || it.code : <span style={codeStyle}>{it.code}</span>}
      {!isCountries && it.label ? <span style={{
    opacity: 0.7
  }}>{it.label}</span> : null}
    </span>;
  const PREVIEW = 5;
  const collapsible = items.length > PREVIEW;
  const q = query.trim().toLowerCase();
  const filtered = q ? items.filter(it => it.code.toLowerCase().includes(q) || it.label && it.label.toLowerCase().includes(q)) : items;
  const expanded = open || q !== "";
  const visible = !collapsible ? items : expanded ? filtered : items.slice(0, PREVIEW);
  const toggle = () => {
    const next = !open;
    setOpen(next);
    if (!next) setQuery("");
  };
  return <div>
      <p>
        {name} {verb} the following {codes.length} {noun}:
      </p>

      {collapsible ? <input type="text" value={query} onChange={e => setQuery(e.target.value)} placeholder={`Filter ${noun}…`} aria-label={`Filter ${noun}`} style={{
    ...controlStyle,
    display: "block",
    width: "100%",
    maxWidth: "22rem",
    padding: "0.4rem 0.7rem",
    margin: "0 0 0.75rem"
  }} /> : null}

      <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: "0.4rem"
  }}>
        {visible.map(renderChip)}
      </div>

      {q && filtered.length === 0 ? <p style={{
    opacity: 0.7,
    marginTop: "0.6rem"
  }}>
          No {noun} match “{query.trim()}”.
        </p> : null}
      {q && filtered.length > 0 ? <p style={{
    opacity: 0.6,
    fontSize: "0.8125rem",
    marginTop: "0.6rem"
  }}>
          Showing {filtered.length} of {items.length}.
        </p> : null}

      {collapsible && !q ? <button type="button" aria-expanded={open} onClick={toggle} style={{
    ...controlStyle,
    display: "inline-flex",
    alignItems: "center",
    gap: "0.4rem",
    padding: "0.35rem 0.75rem",
    marginTop: "0.75rem",
    cursor: "pointer"
  }}>
          <span aria-hidden="true" style={{
    display: "inline-block",
    transform: open ? "rotate(90deg)" : "none",
    transition: "transform 0.15s ease"
  }}>
            ›
          </span>
          {open ? "Show fewer" : `and ${items.length - PREVIEW} more`}
        </button> : null}
    </div>;
};

export const ConnectorCapabilities = ({data}) => {
  const CAPABILITIES = [{
    keys: ["three_d_secure_hosted"],
    label: "3-D Secure (hosted)",
    description: "Gr4vy-hosted 3DS authentication flow.",
    cardOnly: true
  }, {
    keys: ["three_d_secure_pass_through"],
    label: "3-D Secure (pass-through)",
    description: "Pass through 3DS data authenticated by a third party.",
    cardOnly: true
  }, {
    keys: ["partial_authorization"],
    label: "Partial authorization",
    description: "Support partial approval responses."
  }, {
    keys: ["zero_auth"],
    label: "Zero auth",
    description: "Verify a card without charging it."
  }, {
    keys: ["void"],
    label: "Void",
    description: "Cancel an authorized transaction before capture."
  }, {
    keys: ["direct_capture"],
    label: "Direct capture",
    description: "Capture a payment immediately at authorization.",
    hideWhenUnsupported: true
  }, {
    keys: ["delayed_capture"],
    label: "Delayed capture",
    description: "Authorize a payment and capture it at a later time."
  }, {
    keys: ["partial_capture"],
    label: "Partial capture",
    description: "Capture a portion of the authorized amount."
  }, {
    keys: ["over_capture"],
    label: "Over capture",
    description: "Capture more than the originally authorized amount."
  }, {
    keys: ["refunds"],
    label: "Refunds",
    description: "Refund a captured payment."
  }, {
    keys: ["partial_refunds"],
    label: "Partial refunds",
    description: "Refund a portion of the captured amount."
  }, {
    keys: ["settlement_reporting"],
    label: "Settlement reporting",
    description: "Automatic settlement and reconciliation reporting."
  }, {
    keys: ["create_session"],
    label: "Create session",
    description: "Create a connector session for client-side flows."
  }, {
    keys: ["network_tokens_default", "network_tokens_toggle"],
    label: "Network tokens",
    description: "Network-level tokenization for improved approval rates.",
    cardOnly: true
  }, {
    keys: ["digital_wallets"],
    label: "Digital wallets",
    description: "Apple Pay, Google Pay, and other wallet integrations."
  }, {
    keys: ["payment_method_tokenization", "payment_method_tokenization_toggle"],
    label: "Payment method tokenization",
    description: "Store payment methods outside of transactions."
  }, {
    keys: ["transaction_sync"],
    label: "Transaction sync",
    description: "Synchronize transaction state from the connector."
  }, {
    keys: ["create_token"],
    label: "Tokenization",
    description: "Create a token from card details collected via Secure Fields.",
    hideWhenUnsupported: true
  }, {
    keys: ["delete_token"],
    label: "Delete token",
    description: "Delete a stored token.",
    hideWhenUnsupported: true
  }, {
    keys: ["verify_credentials"],
    label: "Verify credentials",
    description: "Validate the configured credentials against the connector.",
    hideWhenUnsupported: true
  }];
  const raw = data && data.features;
  const enabled = typeof raw === "string" ? new Set(raw.split(/\s+/).filter(Boolean)) : Array.isArray(raw) ? new Set(raw) : new Set(Object.keys(raw || ({})).filter(key => raw[key]));
  const isOn = entry => entry.keys.some(key => enabled.has(key));
  const isNonCard = data && data.method && data.method !== "card";
  const renderGroup = (title, entries, supported) => {
    if (entries.length === 0) return null;
    const mark = supported ? "✓" : "✕";
    const markColor = supported ? "#16a34a" : "#9ca3af";
    return <div style={{
      marginTop: "1rem"
    }}>
        <div style={{
      fontSize: "0.75rem",
      fontWeight: 600,
      letterSpacing: "0.05em",
      textTransform: "uppercase",
      opacity: 0.6,
      marginBottom: "0.25rem"
    }}>
          {title}
        </div>
        {}
        <div role="list">
          {entries.map(entry => <div role="listitem" key={entry.label} style={{
      display: "flex",
      gap: "0.5rem",
      alignItems: "baseline",
      padding: "0.3rem 0",
      opacity: supported ? 1 : 0.7
    }}>
              <span aria-hidden="true" style={{
      color: markColor,
      fontWeight: 700,
      flexShrink: 0
    }}>
                {mark}
              </span>
              <span>
                <strong>{entry.label}</strong>
                {entry.description ? <span style={{
      opacity: 0.85
    }}> — {entry.description}</span> : null}
              </span>
            </div>)}
        </div>
      </div>;
  };
  const visible = isNonCard ? CAPABILITIES.filter(entry => !entry.cardOnly) : CAPABILITIES;
  const supported = visible.filter(isOn);
  const unsupported = visible.filter(entry => !isOn(entry) && !entry.hideWhenUnsupported);
  return <div>
      {renderGroup("Supported", supported, true)}
      {renderGroup("Not supported", unsupported, false)}
    </div>;
};

Chase Orbital is a payment gateway from JPMorgan Chase. The bank connector debits a
buyer's bank account directly using the account number and routing number instead of card
details.

Chase calls this product Electronic Check Processing (ECP). It is boarded separately from
card processing, so a Chase Orbital account that already takes cards does not
automatically accept bank payments.

## Setup

Speak to your Chase Orbital account manager to have ECP enabled on the merchant ID you
want to use, and to obtain sandbox credentials.

To sign up for a Chase Orbital test account, visit the
[Developer Center](https://secure.paymentech.com/devcenter/products) page and
select **Get Access**.

Ask your account manager to confirm two more things before you go live.

* **The processing currency of the merchant ID.** Accepting a second currency requires a
  second merchant ID, and therefore a second Chase Orbital bank connection in Gr4vy.
* **Soft descriptor support.** The merchant ID and terminal ID have to be enabled for soft
  descriptors before your merchant name and product description reach the buyer's bank
  statement.

## Connector configuration

Bank payments are routed directly rather than through Flow. When you create a transaction,
set the `payment_service_id` parameter to the ID of your Chase Orbital bank connection.

The connector ID can be found in the dashboard under **Connections** ->
**Configured connections**.

## Credentials

When setting up Chase Orbital in the dashboard, configure the following credentials. They
are the same credentials used by the Chase Orbital card connector, so a merchant ID that
is boarded for both products uses one set of values.

* **Merchant ID** - The merchant ID assigned by Chase Orbital for your account. You can see
  this in the Chase Orbital Development Center **Dashboard** -> **Overview** ->
  **Project** -> **Acquirer ID**.
* **Username** - The Chase Orbital username shared by your account manager.
* **Password** - The Chase Orbital password shared by your account manager.

## Capabilities

<ConnectorCapabilities data={connector} />

## Supported countries

<ConnectorRegions data={connector} kind="countries" />

## Supported currencies

<ConnectorRegions data={connector} kind="currencies" />

## Limitations

The following features are not supported by this connector:

* **Delayed capture** - Authorization and capture always happen together
* **Partial capture** - Cannot capture a portion of the authorized amount
* **Void** - Cannot cancel a transaction once it is submitted. Use a refund instead
* **Payment method tokenization** - Bank accounts cannot be stored outside of a
  transaction
* **Transaction sync** - Transaction synchronization is not supported
* **Settlement reporting** - Settlement reporting is available for Chase Orbital card
  payments only

## Integration

The connection requires the following fields in the transaction request.

* `payment_method.account_number` - The buyer's bank account number.
* `payment_method.routing_number` - The buyer's routing number. US routing numbers are 9
  digits. For Canada, use the routing number format provided by Chase Orbital.
* `payment_method.account_type` - Either `checking` or `savings`.
* `payment_method.account_holder` - The account holder's name.
* `buyer.billing_details.first_name` and `buyer.billing_details.last_name` - Required by
  Chase for internet-authorized bank debits.

Sending the rest of `buyer.billing_details.address` is recommended: Gr4vy forwards it to
Chase for address verification and returns the result on the transaction.

The following samples also set `payment_service_id`, as described under
[Connector configuration](#connector-configuration).

<CodeGroup>
  ```csharp C# theme={"system"}
  var transaction = await client.Transactions.CreateAsync(
    transactionCreate: new TransactionCreate()
    {
      Amount = 1299,
      Currency = "USD",
      Country = "US",
      Intent = "capture",
      PaymentMethod = TransactionCreatePaymentMethod.CreateACHBankPaymentMethodCreate(
        new ACHBankPaymentMethodCreate()
        {
          Method = ACHBankPaymentMethodCreate.MethodEnum.Bank,
          Scheme = ACHBankPaymentMethodCreate.SchemeEnum.Ach,
          AccountNumber = "012345678",
          RoutingNumber = "011000138",
          AccountType = ACHBankPaymentMethodCreate.AccountTypeEnum.Checking,
          AccountHolder = new BankAccountHolder()
          {
            FirstName = "John",
            LastName = "Doe"
          }
        }
      ),
      Buyer = new TransactionCreateBuyer()
      {
        BillingDetails = new BillingDetails()
        {
          FirstName = "John",
          LastName = "Doe"
        }
      },
      PaymentServiceId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    }
  );
  ```

  ```go Go theme={"system"}
  amount := int64(1299)
  currency := "USD"
  country := "US"
  firstName := "John"
  lastName := "Doe"
  paymentServiceId := "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

  achPaymentMethod := components.ACHBankPaymentMethodCreate{
    Method:        components.ACHBankPaymentMethodCreateMethodBank,
    Scheme:        components.ACHBankPaymentMethodCreateSchemeAch,
    AccountNumber: "012345678",
    RoutingNumber: "011000138",
    AccountType:   components.ACHBankPaymentMethodCreateAccountTypeChecking,
    AccountHolder: &components.BankAccountHolder{
      FirstName: &firstName,
      LastName:  &lastName,
    },
  }
  paymentMethod := components.CreateTransactionCreatePaymentMethodACHBankPaymentMethodCreate(achPaymentMethod)

  transactionCreate := components.TransactionCreate{
    Amount:        amount,
    Currency:      currency,
    Country:       &country,
    Intent:        components.CreateTransactionIntentCapture,
    PaymentMethod: &paymentMethod,
    Buyer: &components.TransactionCreateBuyer{
      BillingDetails: &components.BillingDetails{
        FirstName: &firstName,
        LastName:  &lastName,
      },
    },
    PaymentServiceId: &paymentServiceId,
  }

  transaction, err := client.Transactions.Create(ctx, transactionCreate, nil, nil)
  ```

  ```java Java theme={"system"}
  CreateTransactionResponse transactionResponse = gr4vyClient.transactions().create()
    .transactionCreate(TransactionCreate.builder()
      .amount(1299L)
      .currency("USD")
      .country("US")
      .intent(Intent.CAPTURE)
      .paymentMethod(TransactionCreatePaymentMethod.of(ACHBankPaymentMethodCreate.builder()
        .method(ACHBankPaymentMethodCreate.Method.BANK)
        .scheme(ACHBankPaymentMethodCreate.Scheme.ACH)
        .accountNumber("012345678")
        .routingNumber("011000138")
        .accountType(ACHBankPaymentMethodCreate.AccountType.CHECKING)
        .accountHolder(BankAccountHolder.builder()
          .firstName("John")
          .lastName("Doe")
          .build())
        .build()))
      .buyer(TransactionCreateBuyer.builder()
        .billingDetails(BillingDetails.builder()
          .firstName("John")
          .lastName("Doe")
          .build())
        .build())
      .paymentServiceId("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
      .build())
    .call();

  Transaction transaction = transactionResponse.transaction().orElse(null);
  ```

  ```php PHP theme={"system"}
  $transactionCreate = new TransactionCreate(
    amount: 1299,
    currency: 'USD',
    country: 'US',
    intent: Intent::Capture,
    paymentMethod: new ACHBankPaymentMethodCreate(
      method: ACHBankPaymentMethodCreateMethod::Bank,
      scheme: ACHBankPaymentMethodCreateScheme::Ach,
      accountNumber: '012345678',
      routingNumber: '011000138',
      accountType: ACHBankPaymentMethodCreateAccountType::Checking,
      accountHolder: new BankAccountHolder(
        firstName: 'John',
        lastName: 'Doe'
      )
    ),
    buyer: new TransactionCreateBuyer(
      billingDetails: new BillingDetails(
        firstName: 'John',
        lastName: 'Doe'
      )
    ),
    paymentServiceId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
  );
  $response = $sdk->transactions->create($transactionCreate);
  $transaction = $response->transaction;
  ```

  ```python Python theme={"system"}
  transaction = client.transactions.create(
    amount=1299,
    currency="USD",
    country="US",
    intent="capture",
    payment_method={
      "method": "bank",
      "scheme": "ach",
      "account_number": "012345678",
      "routing_number": "011000138",
      "account_type": "checking",
      "account_holder": {
        "first_name": "John",
        "last_name": "Doe"
      }
    },
    buyer={
      "billing_details": {
        "first_name": "John",
        "last_name": "Doe"
      }
    },
    payment_service_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  )
  ```

  ```ts TypeScript theme={"system"}
  const transaction = await client.transactions.create({
    amount: 1299,
    currency: "USD",
    country: "US",
    intent: "capture",
    paymentMethod: {
      method: "bank",
      scheme: "ach",
      accountNumber: "012345678",
      routingNumber: "011000138",
      accountType: "checking",
      accountHolder: {
        firstName: "John",
        lastName: "Doe"
      }
    },
    buyer: {
      billingDetails: {
        firstName: "John",
        lastName: "Doe"
      }
    },
    paymentServiceId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  });
  ```
</CodeGroup>

An approved transaction comes back as captured, with no buyer approval step. For the field
reference, vaulting, and recurring payment flows shared by every bank connector, see
[Direct bank payments](/connections/payments/bank).

### Statement descriptors

Chase shows two values on the buyer's bank statement: your merchant name, truncated to 15
characters, and the product description, truncated to 10 characters. The merchant name
comes from your Gr4vy merchant profile and the product description from the transaction
description.

When no merchant name is set, Gr4vy sends the transaction description as the merchant
name instead. The product description can be empty on its own without affecting the
merchant name. Only when neither value is available does Gr4vy omit the soft descriptor
altogether, and Chase falls back to the defaults configured on the Transaction Division.

## Testing

Ask your Chase implementation contact for test account details for your sandbox merchant
ID. The sandbox validates routing numbers, so an account with an invalid routing number is
declined with `Invalid Transit Routing Number` rather than approved.
