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:
- Concatenate
{public_key}:{private_key}, Base64-encode the result, and set Authorization: Basic {encoded} on every request.
- Also set
Braintree-Version: 2019-01-01 and Content-Type: application/json on every request.
- 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:
- Concatenate
{public_key}:{private_key}, Base64-encode the result, and set Authorization: Basic {encoded} on every request.
- Also set
Braintree-Version: 2019-01-01 and Content-Type: application/json on every request.
- No token rotation, OAuth, or expiry — credentials are static per environment.
- 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_code ← errors[0].extensions.legacyCode (falls back to NO_ERROR_CODE)
error_message ← errors[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
Braintree — ZeroDollarAuth
Complexity: low
Generated by: Grace pipeline run
run-2026-05-13T15-18-07-758Z-b92d20Summary
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_idfor 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) andvariables.API Version:
2019-01-01(required viaBraintree-Versionrequest header).Hosts:
https://payments.braintree-api.com/graphqlhttps://payments.sandbox.braintree-api.com/graphqlThe URL is stored in the
base_urlconnector config field and is environment-specific; there are no regional sub-endpoints.2. Authentication
public_keyprivate_keymerchant_account_idtransaction.merchantAccountIdin every mutationmerchant_config_currency{public_key}:{private_key}, Base64-encode the result, and setAuthorization: Basic {encoded}on every request.Braintree-Version: 2019-01-01andContent-Type: application/jsonon every request.Out of Scope
Not specified in techspec
Technical Constraints
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_idfor 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) andvariables.API Version:
2019-01-01(required viaBraintree-Versionrequest header).Hosts:
https://payments.braintree-api.com/graphqlhttps://payments.sandbox.braintree-api.com/graphqlThe URL is stored in the
base_urlconnector config field and is environment-specific; there are no regional sub-endpoints.2. Authentication
public_keyprivate_keymerchant_account_idtransaction.merchantAccountIdin every mutationmerchant_config_currency{public_key}:{private_key}, Base64-encode the result, and setAuthorization: Basic {encoded}on every request.Braintree-Version: 2019-01-01andContent-Type: application/jsonon every request.Braintree::get_auth_header(braintree.rs) usingBASE64_ENGINE.encode(format!("{public_key}:{private_key}")).3. Supported Flows
/graphqlauthorizeCreditCardmutation withamount: "0.00"+vaultPaymentMethodAfterTransacting: {when: ALWAYS};transaction.paymentMethod.idin response becomesconnector_mandate_id/graphqlauthorizeCreditCardorchargeCreditCardmutation depending oncapture_method/graphqlauthorizePaymentMethod/chargePaymentMethodfor ApplePay, GooglePay, PayPal SDK tokens/graphqlcaptureTransactionmutation; requirestransactionId+amount/graphqlreverseTransactionmutation/graphqlrefundTransactionmutation/graphqlsearch { transactions(input: {id: {is: ...}}) }query/graphqlsearch { refunds(input: ..., first: 1) }query/graphqltokenizeCreditCardmutation; prerequisite step to obtainpaymentMethodIdfor ZeroDollarAuth/graphqlcreateClientTokenmutation; seeds 3DS / Drop-in UI / SDK sessionsbt-payload4. 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 —
authorizeCreditCardwith 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:
queryAUTHORIZE_AND_VAULT_CREDIT_CARD_MUTATION)variables.input.paymentMethodIdSecret<String>tokenizeCreditCardcallvariables.input.transaction.amountStringMajorUnit"0.00"for zero-dollar verification; Braintree uses string decimal (NOT minor integer units)variables.input.transaction.merchantAccountIdSecret<String>BraintreeAuthType.merchant_account_idvariables.input.transaction.channel"HyperSwitchBT_Ecom"(constants::CHANNEL_CODE)variables.input.transaction.orderIdconnector_request_reference_idfromResourceCommonDatavariables.input.transaction.vaultPaymentMethodAfterTransacting{"when": "ALWAYS"}— causes Braintree to persist the card and returnpaymentMethod.idvariables.input.transaction.customerDetails.emailoptions.storeInVaultOnSuccessIdempotency: No dedicated idempotency header in Braintree GraphQL. Use
orderIdfor deduplication; a duplicateorderIdwithin the same merchant account will surface aDUPLICATE_TRANSACTIONgateway rejection.Currency Validation: The UCS connector validates that
request.currencymatchesmerchant_config_currencyfrom auth config before building the request (validate_currencycall).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=" } } } } }data.authorizeCreditCard.transaction.idConnectorTransactionIddata.authorizeCreditCard.transaction.statusdata.authorizeCreditCard.transaction.createdAtdata.authorizeCreditCard.transaction.paymentMethod.idSecret<String>vaultPaymentMethodAfterTransacting.when = ALWAYS; becomesMandateReference.connector_mandate_idfor future MIT calls5b. Transaction Status Values
AttemptStatusAUTHORIZEDAuthorizedAUTHORIZINGAuthorizingAUTHORIZED_EXPIREDAuthorizationFailedSUBMITTED_FOR_SETTLEMENTChargedSETTLINGChargedSETTLEDChargedSETTLEMENT_CONFIRMEDChargedSETTLEMENT_PENDINGChargedFAILEDFailurePROCESSOR_DECLINEDFailureGATEWAY_REJECTEDFailureSETTLEMENT_DECLINEDFailureVOIDEDVoided5c. Mandate Reference Extraction
The
paymentMethod.idis the vaulted card token. Future MIT flows (RepeatPayment) pass this aspaymentMethodIdin thechargeCreditCard/authorizeCreditCardmutation.6. Error Handling
Braintree returns HTTP 200 for most logical/validation errors; errors appear in the top-level
errorsGraphQL 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
extensions.legacyCodeerrorClass915078150181528817039156491570816048160591002PROCESSOR_DECLINEDstatus in transaction — issuer declinedGATEWAY_REJECTEDstatus — Braintree fraud/AVS/CVV rulesUCS Error Extraction Logic (
build_error_response):error_code←errors[0].extensions.legacyCode(falls back toNO_ERROR_CODE)error_message←errors[0].message(falls back toNO_ERROR_MESSAGE)reason← concatenation of all error messages7. Webhooks / Async Notifications
bt-signature={sig}&bt-payload={base64_xml_payload}(URL-encoded form data).bt-payloadas Base64-decoded XML; verifybt-signature= HMAC-SHA1(private_key,bt-payload). Reject events that fail verification.transaction_settledtransaction_settlement_declinedchecknotification.kind+notification.timestamp+ merchant account ID.setup_mandateorverify_payment_methodwebhook event type; the result of a ZeroDollarAuth is determined synchronously from theauthorizeCreditCardresponse.data/field_probe/braintree.json), thesetup_recurring,proxy_setup_recurring, andtoken_setup_recurringflows all reportnot_implemented— this spec targets their initial implementation.verifyPaymentMethodmutation for card verification without a transaction; the zero-dollarauthorizeCreditCardwith vault is the canonical approach.8. References
crates/integrations/connector-integration/src/connectors/braintree/crates/integrations/connector-integration/src/connectors/braintree/transformers.rsgrace/rulesbook/codegen/guides/patterns/pattern_setup_mandate.mddata/field_probe/braintree.jsondocs-generated/connectors/braintree.md