Skip to content

[Tech Spec] Braintree — ZeroDollarAuth #155

Description

@shuklatushar226

Braintree — ZeroDollarAuth

Complexity: low
Generated by: Grace pipeline run run-2026-05-13T15-18-07-758Z-b92d20

Summary

Implement ZeroDollarAuth payment method for Braintree connector. Technical Specification: Braintree — ZeroDollarAuth (SetupMandate)

Scope

Technical Specification: Braintree — ZeroDollarAuth (SetupMandate)

1. Connector Profile

  • Connector Name: Braintree (a PayPal service)

  • Primary Flow Scope: ZeroDollarAuth (SetupMandate) — card-on-file mandate establishment via zero-amount authorization with automatic vaulting; used as the Customer-Initiated Transaction (CIT) that seeds a connector_mandate_id for future Merchant-Initiated Transactions (MIT / RepeatPayment).

  • API Family: GraphQL over HTTPS — single endpoint for all operations; all requests use HTTP POST with a JSON body containing query (GraphQL mutation/query string) and variables.

  • API Version: 2019-01-01 (required via Braintree-Version request header).

  • Hosts:

    Environment Base URL
    Production https://payments.braintree-api.com/graphql
    Sandbox https://payments.sandbox.braintree-api.com/graphql

    The URL is stored in the base_url connector config field and is environment-specific; there are no regional sub-endpoints.


2. Authentication

  • Scheme: HTTP Basic Authentication (RFC 7617).
  • Credentials Required:
    Field Config Key Notes
    API Public Key public_key Used as Basic-auth username
    API Private Key private_key Used as Basic-auth password
    Merchant Account ID merchant_account_id Identifies the sub-account receiving funds; sent in transaction.merchantAccountId in every mutation
    Merchant Config Currency merchant_config_currency Currency the merchant account is configured for; validated at request time against the payment currency
  • Implementation Notes:
    1. Concatenate {public_key}:{private_key}, Base64-encode the result, and set Authorization: Basic {encoded} on every request.
    2. Also set Braintree-Version: 2019-01-01 and Content-Type: application/json on every request.
    3. No token rotation, OAuth, or expiry — credentials are static

Out of Scope

Not specified in techspec

Technical Constraints

  • Follow existing connector patterns in the codebase

Full Tech Spec

Technical Specification: Braintree — ZeroDollarAuth (SetupMandate)

1. Connector Profile

  • Connector Name: Braintree (a PayPal service)

  • Primary Flow Scope: ZeroDollarAuth (SetupMandate) — card-on-file mandate establishment via zero-amount authorization with automatic vaulting; used as the Customer-Initiated Transaction (CIT) that seeds a connector_mandate_id for future Merchant-Initiated Transactions (MIT / RepeatPayment).

  • API Family: GraphQL over HTTPS — single endpoint for all operations; all requests use HTTP POST with a JSON body containing query (GraphQL mutation/query string) and variables.

  • API Version: 2019-01-01 (required via Braintree-Version request header).

  • Hosts:

    Environment Base URL
    Production https://payments.braintree-api.com/graphql
    Sandbox https://payments.sandbox.braintree-api.com/graphql

    The URL is stored in the base_url connector config field and is environment-specific; there are no regional sub-endpoints.


2. Authentication

  • Scheme: HTTP Basic Authentication (RFC 7617).
  • Credentials Required:
    Field Config Key Notes
    API Public Key public_key Used as Basic-auth username
    API Private Key private_key Used as Basic-auth password
    Merchant Account ID merchant_account_id Identifies the sub-account receiving funds; sent in transaction.merchantAccountId in every mutation
    Merchant Config Currency merchant_config_currency Currency the merchant account is configured for; validated at request time against the payment currency
  • Implementation Notes:
    1. Concatenate {public_key}:{private_key}, Base64-encode the result, and set Authorization: Basic {encoded} on every request.
    2. Also set Braintree-Version: 2019-01-01 and Content-Type: application/json on every request.
    3. No token rotation, OAuth, or expiry — credentials are static per environment.
    4. The UCS implementation builds the auth header in Braintree::get_auth_header (braintree.rs) using BASE64_ENGINE.encode(format!("{public_key}:{private_key}")).

3. Supported Flows

Flow HTTP Path Notes
ZeroDollarAuth (SetupMandate) POST /graphql authorizeCreditCard mutation with amount: "0.00" + vaultPaymentMethodAfterTransacting: {when: ALWAYS}; transaction.paymentMethod.id in response becomes connector_mandate_id
Authorize (Card) POST /graphql authorizeCreditCard or chargeCreditCard mutation depending on capture_method
Authorize (Wallet) POST /graphql authorizePaymentMethod / chargePaymentMethod for ApplePay, GooglePay, PayPal SDK tokens
Capture POST /graphql captureTransaction mutation; requires transactionId + amount
Void POST /graphql reverseTransaction mutation
Refund POST /graphql refundTransaction mutation
PSync POST /graphql search { transactions(input: {id: {is: ...}}) } query
RSync POST /graphql search { refunds(input: ..., first: 1) } query
Tokenize (PaymentMethodToken) POST /graphql tokenizeCreditCard mutation; prerequisite step to obtain paymentMethodId for ZeroDollarAuth
ClientAuthenticationToken POST /graphql createClientToken mutation; seeds 3DS / Drop-in UI / SDK sessions
Webhooks (inbound) POST merchant endpoint Braintree pushes signed events; verified via HMAC of bt-payload

4. Request Schema Highlights

4a. Prerequisite — Tokenize Card (tokenizeCreditCard)

Before ZeroDollarAuth, the raw card must be tokenized to produce a paymentMethodId.

{
  "query": "mutation tokenizeCreditCard($input: TokenizeCreditCardInput!) { tokenizeCreditCard(input: $input) { clientMutationId paymentMethod { id } } }",
  "variables": {
    "input": {
      "creditCard": {
        "number": "4111111111111111",
        "expirationYear": "2030",
        "expirationMonth": "03",
        "cvv": "737",
        "cardholderName": "Jane Doe"
      }
    }
  }
}

4b. ZeroDollarAuth — authorizeCreditCard with Vault

{
  "query": "mutation authorizeCreditCard($input: AuthorizeCreditCardInput!) { authorizeCreditCard(input: $input) { transaction { id status createdAt paymentMethod { id } } } }",
  "variables": {
    "input": {
      "paymentMethodId": "<vaulted_card_pm_id_from_tokenize>",
      "transaction": {
        "amount": "0.00",
        "merchantAccountId": "<merchant_account_id>",
        "channel": "HyperSwitchBT_Ecom",
        "orderId": "<connector_request_reference_id>",
        "vaultPaymentMethodAfterTransacting": { "when": "ALWAYS" }
      }
    }
  }
}

Field Reference:

Field Type Required Notes
query String GraphQL mutation string (hardcoded constant AUTHORIZE_AND_VAULT_CREDIT_CARD_MUTATION)
variables.input.paymentMethodId Secret<String> Card token from prior tokenizeCreditCard call
variables.input.transaction.amount StringMajorUnit "0.00" for zero-dollar verification; Braintree uses string decimal (NOT minor integer units)
variables.input.transaction.merchantAccountId Secret<String> From BraintreeAuthType.merchant_account_id
variables.input.transaction.channel String Always "HyperSwitchBT_Ecom" (constants::CHANNEL_CODE)
variables.input.transaction.orderId String connector_request_reference_id from ResourceCommonData
variables.input.transaction.vaultPaymentMethodAfterTransacting Object ✓ for mandate {"when": "ALWAYS"} — causes Braintree to persist the card and return paymentMethod.id
variables.input.transaction.customerDetails.email String Optional Billing email; binds vault entry to a customer record
options.storeInVaultOnSuccess Boolean Optional Legacy REST flag; not used in GraphQL path

Idempotency: No dedicated idempotency header in Braintree GraphQL. Use orderId for deduplication; a duplicate orderId within the same merchant account will surface a DUPLICATE_TRANSACTION gateway rejection.

Currency Validation: The UCS connector validates that request.currency matches merchant_config_currency from auth config before building the request (validate_currency call).


5. Response Schema Highlights

5a. Successful ZeroDollarAuth Response

{
  "data": {
    "authorizeCreditCard": {
      "transaction": {
        "id": "dHJhbnNhY3Rpb246dHJhbnNhY3Rpb246QUJDREVGR0g=",
        "status": "AUTHORIZED",
        "createdAt": "2024-07-20T14:22:05.000Z",
        "paymentMethod": {
          "id": "cGF5bWVudG1ldGhvZDpwYXltZW50bWV0aG9kOlhZWlo="
        }
      }
    }
  }
}
Field Type Notes
data.authorizeCreditCard.transaction.id String Connector transaction ID (opaque Base64 global ID); stored as ConnectorTransactionId
data.authorizeCreditCard.transaction.status Enum See §5b status table
data.authorizeCreditCard.transaction.createdAt ISO 8601 datetime Transaction creation timestamp
data.authorizeCreditCard.transaction.paymentMethod.id Secret<String> Critical — returned only when vaultPaymentMethodAfterTransacting.when = ALWAYS; becomes MandateReference.connector_mandate_id for future MIT calls

5b. Transaction Status Values

Braintree Status UCS AttemptStatus Meaning
AUTHORIZED Authorized Auth hold placed; card valid
AUTHORIZING Authorizing Authorization in progress
AUTHORIZED_EXPIRED AuthorizationFailed Auth hold expired (7–30 days depending on card network)
SUBMITTED_FOR_SETTLEMENT Charged Capture queued
SETTLING Charged Settlement in progress
SETTLED Charged Funds transferred
SETTLEMENT_CONFIRMED Charged Settlement confirmed
SETTLEMENT_PENDING Charged Settlement delayed (e.g., bank holiday)
FAILED Failure Generic failure
PROCESSOR_DECLINED Failure Issuer / processor declined
GATEWAY_REJECTED Failure Braintree risk/fraud rules triggered
SETTLEMENT_DECLINED Failure Settlement refused by processor
VOIDED Voided Transaction reversed before settlement

5c. Mandate Reference Extraction

mandate_reference: transaction_data.payment_method.as_ref().map(|pm| {
    Box::new(MandateReference {
        connector_mandate_id: Some(pm.id.clone().expose()),
        payment_method_id: None,
        connector_mandate_request_reference_id: None,
    })
})

The paymentMethod.id is the vaulted card token. Future MIT flows (RepeatPayment) pass this as paymentMethodId in the chargeCreditCard / authorizeCreditCard mutation.


6. Error Handling

Braintree returns HTTP 200 for most logical/validation errors; errors appear in the top-level errors GraphQL array. HTTP non-200 codes indicate infrastructure or authentication failures.

GraphQL Error Response Shape

{
  "errors": [
    {
      "message": "Amount is required.",
      "extensions": {
        "legacyCode": "91507",
        "errorClass": "VALIDATION",
        "inputPath": ["input", "transaction", "amount"]
      }
    }
  ]
}

Error Table

HTTP extensions.legacyCode errorClass Cause
200 91507 VALIDATION Amount is required
200 81501 VALIDATION Amount cannot be negative
200 81528 VALIDATION Card number is invalid
200 81703 VALIDATION Credit card type not accepted by this merchant account
200 91564 VALIDATION Merchant account does not support zero-amount transactions
200 91570 VALIDATION Merchant account currency mismatch
200 81604 VALIDATION Card expiration month is invalid
200 81605 VALIDATION Card expiration year is invalid
200 91002 AUTHORIZATION Merchant account not found or not authorized
200 (none) PROCESSOR_DECLINED status in transaction — issuer declined
200 (none) GATEWAY_REJECTED status — Braintree fraud/AVS/CVV rules
401 Invalid API credentials (public_key/private_key)
403 Insufficient permissions for the requested operation
422 Malformed GraphQL query or missing required variable
429 Rate limit exceeded
500 Braintree internal server error

UCS Error Extraction Logic (build_error_response):

  • error_codeerrors[0].extensions.legacyCode (falls back to NO_ERROR_CODE)
  • error_messageerrors[0].message (falls back to NO_ERROR_MESSAGE)
  • reason ← concatenation of all error messages

7. Webhooks / Async Notifications

  • Subscription: Configured in the Braintree Control Panel under Settings → Webhooks. Sandbox supports test pings only; production allows full event subscriptions.
  • Delivery Format: HTTP POST to the merchant-configured URL with body bt-signature={sig}&bt-payload={base64_xml_payload} (URL-encoded form data).
  • Verification: Parse bt-payload as Base64-decoded XML; verify bt-signature = HMAC-SHA1(private_key, bt-payload). Reject events that fail verification.
  • Event Types Relevant to ZeroDollarAuth / SetupMandate:
    Event Meaning
    transaction_settled Transaction moved to SETTLED
    transaction_settlement_declined Settlement was declined
    check Test delivery (Braintree health-check ping)
  • Retry Policy: Braintree retries on non-2xx responses with exponential backoff; up to ~50 attempts over 3 days. Idempotency key: notification.kind + notification.timestamp + merchant account ID.
  • Documented Gaps:
    • There is no dedicated setup_mandate or verify_payment_method webhook event type; the result of a ZeroDollarAuth is determined synchronously from the authorizeCreditCard response.
    • In UCS field probe (data/field_probe/braintree.json), the setup_recurring, proxy_setup_recurring, and token_setup_recurring flows all report not_implemented — this spec targets their initial implementation.
    • Braintree's GraphQL API does not support a standalone verifyPaymentMethod mutation for card verification without a transaction; the zero-dollar authorizeCreditCard with vault is the canonical approach.

8. References

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions