Skip to content

feat(connector): [JPMORGAN ORBITAL] add JP Morgan Orbital connector with Card Authorize (3DS + non-3DS) - #2187

Open
shuklatushar226 wants to merge 5 commits into
mainfrom
feat/grace-JP_Morgan_Orbital
Open

feat(connector): [JPMORGAN ORBITAL] add JP Morgan Orbital connector with Card Authorize (3DS + non-3DS)#2187
shuklatushar226 wants to merge 5 commits into
mainfrom
feat/grace-JP_Morgan_Orbital

Conversation

@shuklatushar226

Copy link
Copy Markdown
Contributor

Summary

Adds a new connector jpmorganorbital / ConnectorEnum::JpmorganOrbital / display name "JP Morgan Orbital", implementing Card Authorize (non-3DS and 3DS) plus PSync.

This is the JPMorgan Orbital Gateway JSON API v4 (orbitalvar1.chasepaymentech.com/gwapi/v4/gateway) — a different product from the existing jpmorgan connector (Payments API v2, api-ms.payments.jpmorgan.com). The existing jpmorgan connector is untouched. The naming mirrors the existing worldpay / worldpayvantiv sibling-suffix convention.

Scope

Card one-time payments only. Out of scope for this PR: mandates / MIT / recurring, wallets, bank transfers, bank debits, and BNPL.

3DS model

Orbital is external-3DS passthrough only. There is no Orbital-hosted challenge, no ACS redirect, and no redirect URL in the response. Authorize is a single synchronous call for both non-3DS and 3DS; externally-produced authentication data is mapped onto the Orbital cryptogram and additionalAuthInfo objects. No RedirectForm flow exists.

Auth

Three plain HTTP headers — orbitalConnectionUsername, orbitalConnectionPassword, merchantID — plus merchant.bin and merchant.terminalID in the body, supplied via ConnectorSpecificConfig::JpmorganOrbital.

Notable correctness details

These are non-obvious and worth reviewer attention:

  • order.amount uses two implied decimals for EVERY currency, including zero-exponent ones. ¥100 is sent as 10000, not 100. A plain StringMinorUnit would have been a 100x under-charge for JPY/KRW. Currency is never sent — it is implied by the MID.
  • Mastercard ECI scale inversion. UCS ECI 02 maps to Orbital authenticationECIInd 5, not 2 (Orbital's Mastercard scale is 5/6/7). Sending 2 would be read by Orbital as "recurring CDPT".
  • order.retryTrace is both Orbital's idempotency key and the PSync lookup key (/inquiryorder.inquiryRetryNumber). It is numeric and ≤16 chars, so a UUID does not fit. It is derived deterministically (FNV-1a/64 folded into [1e15, 1e16)) so PSync can re-derive it, and is also persisted to connector_metadata.
  • txRefIdx is persisted alongside txRefNum for future Capture/Void.
  • procStatus / respCode / avsRespCode / cvvRespCode are treated as opaque strings. The exhaustive tables are not published in any machine-readable JPM asset, so no enum was invented.
  • The production base URL is UNCONFIRMED. JPM does not publish it. It is committed with an explicit comment to confirm with JPM Merchant Services during onboarding.

Files

Added

  • crates/integrations/connector-integration/src/connectors/jpmorganorbital.rs
  • crates/integrations/connector-integration/src/connectors/jpmorganorbital/transformers.rs

Modified

  • crates/integrations/connector-integration/src/connectors.rs
  • crates/integrations/connector-integration/src/types.rs
  • crates/integrations/connector-integration/src/default_implementations.rs
  • crates/types-traits/domain_types/src/connector_types.rs
  • crates/types-traits/domain_types/src/router_data.rs
  • crates/types-traits/domain_types/src/types.rs
  • crates/types-traits/grpc-api-types/proto/payment.proto
  • crates/internal/field-probe/src/auth.rs
  • config/development.toml, config/sandbox.toml, config/production.toml, config/superposition.toml

Testing

⚠️ Please read: no real sandbox credentials exist.
NO real JPMorgan Orbital sandbox credentials were available for this integration. Validation was therefore performed against a LOCAL MOCK Orbital server that captured the exact outbound HTTP request the connector produces. The captured outbound requests below are the real evidence of correctness — they show byte-for-byte what would go on the wire to Orbital. This has not been executed against JPMorgan's live VAR environment.

cargo build passes on the full workspace with zero warnings from the new code.

grpcurl calls and responses (credentials redacted)
--- (1) Card Authorize, non-3DS (Mastercard, AUTOMATIC -> transType "AC") ---
grpcurl -plaintext \
  -H 'x-connector: jpmorgan_orbital' \
  -H 'x-merchant-id: <REDACTED>' \
  -H 'x-request-id: req-jpmorganorbital-non3ds-001' \
  -H 'x-connector-config: {"config":{"JpmorganOrbital":{"username":"<REDACTED>","password":"<REDACTED>","merchant_id":"<REDACTED>","bin":"<REDACTED>","terminal_id":"<REDACTED>"}}}' \
  -d '{
    "merchant_transaction_id": "hs_orbital_non3ds_001",
    "amount": { "minor_amount": 10000, "currency": "USD" },
    "payment_method": { "card": {
        "card_number": { "value": "<REDACTED>" },
        "card_exp_month": { "value": "06" },
        "card_exp_year": { "value": "2030" },
        "card_cvc": { "value": "<REDACTED>" },
        "card_holder_name": { "value": "John Doe" },
        "card_network": "MASTERCARD" } },
    "capture_method": "AUTOMATIC",
    "auth_type": "NO_THREE_DS",
    "enrolled_for_3ds": false
  }' \
  localhost:8000 types.PaymentService/Authorize

Response:
{
  "merchantTransactionId": "MOCKORDER",
  "connectorTransactionId": "625EC0E49LK88FCB0906BB8AAD435F42A2FC553E0",
  "status": "CHARGED",
  "statusCode": 200,
  "connectorReferenceId": "MOCKORDER"
}

--- (2) Card Authorize, 3DS external passthrough (Visa Secure, MANUAL -> transType "A") ---
grpcurl -plaintext \
  -H 'x-connector: jpmorgan_orbital' \
  -H 'x-merchant-id: <REDACTED>' \
  -H 'x-request-id: req-jpmorganorbital-3ds-001' \
  -H 'x-connector-config: {"config":{"JpmorganOrbital":{"username":"<REDACTED>","password":"<REDACTED>","merchant_id":"<REDACTED>","bin":"<REDACTED>","terminal_id":"<REDACTED>"}}}' \
  -d '{
    "merchant_transaction_id": "hs_orbital_3ds_001",
    "amount": { "minor_amount": 10000, "currency": "USD" },
    "payment_method": { "card": {
        "card_number": { "value": "<REDACTED>" },
        "card_exp_month": { "value": "06" },
        "card_exp_year": { "value": "2030" },
        "card_cvc": { "value": "<REDACTED>" },
        "card_network": "VISA" } },
    "capture_method": "MANUAL",
    "auth_type": "THREE_DS",
    "enrolled_for_3ds": true,
    "authentication_data": {
      "eci": "05",
      "cavv": "<REDACTED>",
      "threeds_server_transaction_id": "<REDACTED>",
      "message_version": "2.2.0",
      "ds_transaction_id": "f38e6948-5388-41a6-bca4-b49723c19437"
    }
  }' \
  localhost:8000 types.PaymentService/Authorize

Response:
{
  "merchantTransactionId": "MOCKORDER3DS",
  "connectorTransactionId": "6260A1F27C3E4AB9012D77EF9143221C8B770123",
  "status": "AUTHORIZED",
  "statusCode": 200,
  "connectorReferenceId": "MOCKORDER3DS"
}

--- (3) HTTP 200 issuer decline (approvalStatus "0") ---
{
  "connectorTransactionId": "625EC0E49LK88FCB0906BB8AAD435F42A2FC553E1",
  "status": "FAILURE",
  "error": { "connectorDetails": { "code": "05", "message": "Do Not Honor", "reason": "05" } },
  "statusCode": 200
}
Captured outbound HTTP requests — the key evidence (credentials redacted)
--- (1) NON-3DS ---
POST /gwapi/v4/gateway/payments HTTP/1.1
Host: <orbital-host>
content-type: application/json
merchantid: <REDACTED>
orbitalconnectionusername: <REDACTED>
orbitalconnectionpassword: <REDACTED>
via: HyperSwitch

{
  "version": "5.2",
  "transType": "AC",
  "merchant": { "bin": "<REDACTED>", "merchantID": "<REDACTED>", "terminalID": "<REDACTED>" },
  "paymentInstrument": { "card": { "ccAccountNum": "<REDACTED>", "ccExp": "203006" } },
  "order": { "orderID": "hs-orbital-non3ds-001", "amount": "10000", "industryType": "EC", "retryTrace": "5965648070021058" },
  "cardholderVerification": { "ccCardVerifyNum": "<REDACTED>", "ccCardVerifyPresenceInd": "1" }
}

--- (2) 3DS (Visa Secure) ---
POST /gwapi/v4/gateway/payments HTTP/1.1
Host: <orbital-host>
content-type: application/json
merchantid: <REDACTED>
orbitalconnectionusername: <REDACTED>
orbitalconnectionpassword: <REDACTED>
via: HyperSwitch

{
  "version": "5.2",
  "transType": "A",
  "merchant": { "bin": "<REDACTED>", "merchantID": "<REDACTED>", "terminalID": "<REDACTED>" },
  "paymentInstrument": { "card": { "ccAccountNum": "<REDACTED>", "ccExp": "203006" } },
  "order": { "orderID": "hs-orbital-3ds-001", "amount": "10000", "industryType": "EC", "retryTrace": "9294143268873549" },
  "cardholderVerification": { "ccCardVerifyNum": "<REDACTED>", "ccCardVerifyPresenceInd": "1" },
  "cryptogram": { "verifyByVisaCAVV": "<REDACTED>", "verifyByVisaXID": "<REDACTED>" },
  "additionalAuthInfo": { "authenticationECIInd": "5" }
}

--- (3) JPY 100 (zero-exponent) - proves the amount rule ---
"order": { "orderID": "hs-orbital-jpy-001", "amount": "10000", "industryType": "EC", "retryTrace": "3872083475311052" }
(minor_amount 100 + currency JPY -> "10000"; plain StringMinorUnit would have emitted "100")

--- (4) Mastercard 3DS with UCS eci "02" - proves the ECI scale inversion ---
"cryptogram": { "mcSecureCodeAAV": "<REDACTED>" },
"additionalAuthInfo": { "authenticationECIInd": "5", "ucafInd": "2", "mcProgramProtocol": "2",
                        "mcDirectoryTransID": "f38e6948-5388-41a6-bca4-b49723c19437" }
(UCS ECI "02" -> Orbital "5", not "2")

Validation checklist

  • cargo build passes on the full workspace with zero warnings from the new code
  • Authorize (non-3DS and 3DS) and PSync exercised end-to-end against a local mock Orbital server
  • No credentials, no localhost base URLs and no card numbers in the committed diff
  • The existing jpmorgan connector is untouched
  • Validated against JPMorgan's real VAR/sandbox environment — blocked on credentials
  • Production base URL confirmed with JPM Merchant Services

…ith Card Authorize (3DS + non-3DS)

Adds the `jpmorganorbital` connector (ConnectorEnum::JpmorganOrbital, display name
"JP Morgan Orbital") implementing Card Authorize for non-3DS and external-3DS
passthrough, plus PSync.

This is the JPMorgan Orbital Gateway JSON API v4
(orbitalvar1.chasepaymentech.com/gwapi/v4/gateway), a different product from the
existing `jpmorgan` connector (Payments API v2, api-ms.payments.jpmorgan.com).
The existing `jpmorgan` connector is untouched. The naming mirrors the existing
worldpay / worldpayvantiv sibling-suffix convention.

Notable details:
- order.amount uses two implied decimals for every currency including
  zero-exponent ones, so JPY 100 is sent as "10000". Currency is never sent; it
  is implied by the MID.
- Mastercard ECI scale inversion: UCS ECI "02" maps to Orbital
  authenticationECIInd "5" (Orbital's MC scale is 5/6/7), not "2".
- order.retryTrace is both the idempotency key and the PSync lookup key. It is
  numeric and <=16 chars, so it is derived deterministically (FNV-1a/64 folded
  into [1e15,1e16)) and also persisted to connector_metadata.
- txRefIdx is persisted alongside txRefNum for future Capture/Void.
- procStatus / respCode / avsRespCode / cvvRespCode are treated as opaque
  strings; no enum was invented.

Scope: card one-time payments only. Mandates/MIT, wallets, bank transfers and
BNPL are out of scope.
@shuklatushar226
shuklatushar226 requested review from a team as code owners August 27, 2026 10:29
@shuklatushar226
shuklatushar226 requested a review from a team as a code owner August 27, 2026 10:29
Auto-applied by CI:
- cargo +nightly fmt --all
- make -C sdk generate (if applicable)
- make docs (if applicable)

This commit was automatically generated by GitHub Actions.
Resolves the payment.proto conflict in ConnectorSpecificConfig: main claimed
Connector enum 140 and oneof field 150 for GOTYME_SANLAM, so JP Morgan Orbital
is renumbered to JPMORGAN_ORBITAL = 141 / jpmorgan_orbital = 151.
…ecs coverage

- clippy: use `len().is_multiple_of(4)` instead of `len() % 4 == 0` in the
  base64 shape check (manual_is_multiple_of, denied under -D warnings).
- typos: rename the Rust field `retry_attemp_count` -> `retry_attempt_count`
  and the `connector_metadata` JSON key `retry_attemp_count` ->
  `retry_attempt_count`. Both are ours, so the rename is safe. The wire field
  name stays `retryAttempCount` -- that is JPMorgan Orbital's own misspelling
  in the Orbital Gateway JSON API and correcting it would break response
  deserialization -- so it is allowlisted in .typos.toml instead. Also
  reworded a doc comment ("mis-mapping" -> "mismapping").
  NOTE: this changes the key persisted in `connector_metadata` from
  "retry_attemp_count" to "retry_attempt_count".
- specs: add crates/internal/integration-tests/src/connector_specs/
  jpmorganorbital/specs.json declaring only what the connector supports
  (PaymentService/Authorize and PaymentService/Get). This restores
  connector-list parity (108 integrations / 108 spec dirs) in
  check_connector_specs.
- merge fallout: populate the new `payment_account_reference` field on both
  `PaymentsResponseData::TransactionResponse` initializers.
Brings in the CI auto-fix commit (fmt + generated SDK/docs/examples).
Resolves the generated _generated_grpc_client.ts ConnectorSpecificConfig map
to carry both gotymeSanlam (from main) and jpmorganOrbital.
@shuklatushar226 shuklatushar226 self-assigned this Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant