Initiate a Sale or Refund

This article covers both sale and refund transactions

Initializing the Reader for ChargingAnchorIcon

When you're ready to charge a customer, you should create an In-Store Context by requesting a charge from a card reader. In this step, your application will specify, at minimum, the readerId , merchantAccountIdand transaction.amount details to initialize the reader for payment acceptance. Note that the merchantAccountIdis not validated against in the Sandbox environment; however, this is a required value in the production environment for all interactions with the card reader.

Charging is similar to authorizing, except that if the authorization is successful, Braintree will automatically capture the transaction. This is also known as creating a Sale Transaction.

To provide idempotency on the request charge mutation, you must also include the HTTP header Idempotency-Key with a unique value. UUIDv4 is recommended. For example, if using curl to make the request, you would include

note

Using Idempotency-Key is an important way of preventing duplicate charges to your customer in the event of an API communication failure. For example, if you request a charge from the reader, the customer completes the charge on the reader, but for some reason, the POS does not get back the transaction result before timing out. In this scenario, the POS could recover the original transaction using the same Idempotency-Key without accidentally creating a duplicate charge.

--header 'Idempotency-Key: 94c9ea8b-31b0-488e-a231-57e32f0bfd70'
  1. GraphQL Mutation
  2. GraphQL Variables
  3. Sample API Response
mutation RequestChargeFromInStoreReader($input: RequestChargeFromInStoreReaderInput!) {
                requestChargeFromInStoreReader(input: $input) {
                    clientMutationId
                    id
                    status
                    reader {
                        id
                        name                
                        status
                    }
                }
            }

Checking the Reader Charge StatusAnchorIcon

After the reader is initialized for payment, your application must wait for the customer to interact with it (i.e. insert a test card) and for the payment attempt to be processed. Your application should use the node query to poll on a 2-second interval between responses for the current status of the payment using the RequestChargeInStoreContext.id received at charge initialization and monitor the RequestChargeInStoreContext.statusfield as the transaction goes through the lifecycle.

When the RequestChargeInStoreContext.status changes to "COMPLETE", you will also receive a transaction object RequestChargeInStoreContext.transactionin the response. Save the completed RequestChargeInStoreContext.transaction.id in your database for referencing the transaction in subsequent operations.

All successful test transactions should have a transaction amount value below $2,000 for testing. For more info on using amounts to simulate various transaction outcomes, look at Testing Your Integration.


Testing the Unhappy Paths in Sandbox:


  1. GraphQL Query
  2. PENDING Response
  3. COMPLETE Response
  4. FAILED Response : Decline
  5. FAILED Response : Network Error
  6. FAILED Response: Gateway Reject
{
                node(id: "{{last_braintree_instore_context}}") {
                 ... on RequestChargeInStoreContext {
                  id
                  status
                  statusReason
                  reader {
                   id
                   name
                   status
                  }
                  transaction {
                   id
                   orderId
                   status
                   statusHistory {
                    ... on PaymentStatusEvent {
                     status
                     timestamp
                     terminal
                     ... on AuthorizedEvent {
                      processorResponse {
                       authorizationId
                       emvData
                       message
                       legacyCode
                       retrievalReferenceNumber
                      }
                     }
                     ... on GatewayRejectedEvent {
                      gatewayRejectionReason
                     }
                     ... on FailedEvent {
                      processorResponse {
                       retrievalReferenceNumber
                       emvData
                       message
                       legacyCode
                      }
                      networkResponse {
                       message
                       code
                      }
                     }
                     ... on ProcessorDeclinedEvent {
                      processorResponse {
                       legacyCode
                       message
                       authorizationId
                       additionalInformation
                       retrievalReferenceNumber
                       emvData
                      }
                      declineType
                      networkResponse {
                       code
                       message
                      }
                     }
                    }
                   }
                   merchantAddress {
                    company
                    streetAddress
                    addressLine1
                    extendedAddress
                    addressLine2
                    locality
                    adminArea2
                    region
                    adminArea1
                    postalCode
                    countryCode
                    phoneNumber
                   }
                   amount {
                    value
                    currencyIsoCode
                   }
                   merchantAccountId
                   merchantName
                   createdAt
                   channel
                   customFields {
                    name
                    value
                   }
                   paymentMethodSnapshot {
                    ... on CreditCardDetails {
                     origin {
                      details {
                       ... on EmvCardOriginDetails {
                        applicationPreferredName
                        applicationIdentifier
                        terminalId
                        inputMode
                        pinVerified
                       }
                      }
                     }
                     brandCode
                     last4
                     bin
                     expirationMonth
                     expirationYear
                     cardholderName
                     binData {
                      issuingBank
                      countryOfIssuance
                      prepaid
                      healthcare
                      debit
                      commercial
                     }
                    }
                   }
                  }
                 }
                }
               }

Charge Flow Example Sequence DiagramAnchorIcon

The following high-level sequence diagram depicts an example charge flow; however, this flow can vary depending on whether you are vaulting, and polling logic may also vary by application. This is just an example flow.

Knowing that every application will have different flows and architectural constraints the following recommended timeout logic is just a suggestion based on best practices, but every integration may be different.

Generally, the card reader has a built-in timeout logic of 180 seconds for a “maximum checkout time,” that is, from the moment you request a charge from the card reader, it will hang for 180 seconds if no payment instrument is presented. However, it is recommended for the POS application to have its own timeout logic to make sure the POS stays in sync with the card reader and provides a seamless experience to the customer.

Step 1

Generate unique Idempotency key (UUID) to be added in your request header

Step 2

Use an HTTP timeout window of 10 seconds if you do not get back a response

Step 3

Send the request again using the same Idempotency key, this allows you to recover the request session and avoid sending a duplicate request

Step 4

Once you get an InstoreContext.Id start polling against it to retrieve the transaction.Id and final transaction status

Step 5

Use an HTTP timeout window of 3 seconds if you do not get a response while polling against the InStoreContext.Id

Step 6

Send the polling request every 2-3 seconds until you get the final transaction.status, transaction.Id

Cancelling a ChargeAnchorIcon

If you need to stop a transaction from being paid while the reader is activated and requesting payment, you can cancel the InStoreContext. Cancelling an InStoreContext places the reader back into idle mode and returns you to the PayPal-branded screensaver. It is important to note that canceling a charge is only possible when a payment instrument has not yet been presented to the card reader. After the card reader has been presented with payment, the only way to stop that would be with a reversal or refund of payment.

Note
Note that if you request to cancel the InStoreContext and the API returns a context status of "COMPLETE" that would indicate that the charge has already been processed. In this scenario, you should check the reader charge status again to get the transactionId and perform a reversal or refund to cancel the charge.

*** As of August 2022, we have introduced a new API error code 96716, which occurs when you attempt to cancel a context ID that is already in a "PROCESSING" or "COMPLETE" state
  1. GraphQL Mutation
  2. GraphQL Variables
  3. Sample API Response
mutation RequestCancelFromInStoreReader(
                $input: RequestCancelFromInStoreReaderInput!
            ) {
                requestCancelFromInStoreReader(input: $input) {
                    id
                    status
                    reader {
                        id
                        name
                        status
                        location {
                            id
                            name
                        }
                    }
                }
            }

Partial AuthorizationsAnchorIcon

note

The Partial Auth feature has undergone a rebuild as of October 2023. The information below represents the new behavior and integration path.

Partial Authorizations refer to transactions where a card with limited available funds is used to complete a purchase, and the available funds are less than the amount to be collected. This can occur with a debit card or a prepaid gift card. For example, if a merchant wants to charge $100, and the customer presents a card with only $50 available. With a partial authorization, the available $50 would be authorized or charged, while the POS system would need to recognize that there is a remaining balance to tender of $50. The POS should generally handle this like a split tender once it is determined that a partial authorization has occurred.

The way for the POS to know that a partial authorization has occurred would be to parse out the statusReason from the context ID node query, which would show as PARTIALLY_AUTHORIZED. The context status will be returned as COMPLETE.

In the Sandbox environment, you may test partial authorizations by using the "amount":"1004" in your request.

Partial Authorization EnablementAnchorIcon

There are 2 ways to enable partial authorization on your account. You may have your PayPal Solutions Engineer or Integration Engineer enable this feature on your account. Alternatively, you may pass an API flag to determine whether you want to allow partial authorizations on the particular transaction using the acceptPartialAuthorization object. This API flag is a boolean ("true/false") variable that will override the Braintree account-level configuration. We recommend using this API flag if you would like to allow for partial authorization on some transactions and not others, or if you want more control of the enablement of this feature.

note

The Partial Authorization feature does need to be enabled on the Braintree gateway account if you are not passing the acceptPartialAuthorization API flag. Please consult with your Solutions Engineer or Integration Engineer to enable it.

Refunding Your CustomerAnchorIcon

There are 3 ways of refunding your customer once a charge has been completed.

1) Performing a Referenced Refund:AnchorIcon

To perform a referenced refund, you must have the transactionId from the authorization that you are attempting to refund against. This results in a refund that is linked to the original authorization. Typically, this is the preferred refund method as it allows for easy accounting. It is important to note that referenced refunds can either be in full or of a partial amount of the original authorization; however, the refund amount can not exceed the full authorized amount.

  1. GraphQL Mutation
  2. GraphQL Variables
  3. Sample API Response
mutation refundTransaction($input: RefundTransactionInput!) {
                refundTransaction(input: $input) {
                  refund {
                    id
                    amount {
                      value
                    }
                    orderId
                    status
                    refundedTransaction {
                      id
                      amount {
                        value
                      }
                      orderId
                      status
                    }
                  }
                }
              }

Referenced Refund Example Sequence Diagram:AnchorIcon

2) Using the Reversal API callAnchorIcon

Sometimes, the POS might not know whether the original authorization has already been settled or not. In this scenario, it might be best to use a reversal call, which lets Braintree determine whether the transaction can be Voided or Refunded depending on the settlement status. For more details on how this works, follow this link and see the example below:

  1. GraphQL Mutation
  2. GraphQL Variables
  3. Sample API Response
mutation reverseTransaction($input: ReverseTransactionInput!) {
                reverseTransaction(input: $input) {
                  reversal {
                      __typename
                    ... on Transaction {
                      id
                      legacyId
                      orderId
                      status
                      statusHistory {
                        status
                        terminal
                      }
                    }
                    ... on Refund {
                      id
                      legacyId
                      orderId
                      status
                      amount {
                        value
                      }
                      refundedTransaction {
                        id
                        amount {
                          value
                        }
                        orderId
                        status
                      }
                    }
                  }
                }
              }

Reversal API Call Example Sequence Diagram:AnchorIcon

3) Performing an Unreferenced Refund or "Blind Credit":AnchorIcon

To perform an unreferenced refund, your customer must be present at the time of refund, and a payment instrument must be presented to the card reader. The resulting refund will not be linked to the original authorization, so this is typically not the preferred way of refunding due to accounting reasons. Here are some of the common use cases below:

  • Refunds to customers without a receipt (when original order cannot be found)

  • Refunds to customers whose original authorization occurred while on your previous payment solution

  • Refunds to an alternate payment instrument or tender type

note

Note that this feature requires approval to be enabled in the production environment and needs to be requested to be enabled by your Solutions Engineer or Integration Engineer in the sandbox environment

  1. GraphQL Mutation
  2. GraphQL Variables
  3. Sample API response
mutation RequestRefundFromInStoreReader($input: RequestRefundFromInStoreReaderInput!) {
                requestRefundFromInStoreReader(input: $input) {
                    clientMutationId
                    inStoreContext {
                        id
                        status
                        reader {
                            id
                            name                
                            status
                        }
                    }
                }
            }

Once the refund is complete, you'll need to check the status of the context ID, similar to how this is done for a charge request. A status of "COMPLETE" indicates a successful refund.

  1. GraphQL Mutation
  2. GraphQL Variables
  3. Sample API Response
query ID($inStoreContextId: ID!) {
                node(id: $inStoreContextId) {
                    ... on InStoreContext {
                        id
                        status
                        reader {
                            id
                            name
                            status
                        }
                        transaction {
                            id
                                    }
                                    refund {
                                        id
                                        orderId
                                        legacyId
                                        status
                                        paymentMethodSnapshot {
                                            __typename
                                            ... on CreditCardDetails {
                                                brandCode
                                                bin
                                                last4
                                                expirationMonth
                                                expirationYear
                                                }
                                            }
                                        }
                                    }
                                }
                            }

Unreferenced Refund Example Sequence Diagram:AnchorIcon

4) Reversing a RefundAnchorIcon

If a refund was performed and you need to reverse that refund within the acceptable reversal window, you can use the reverseRefund mutation. Keep in mind that you would need to have saved the refundId from the API response of the original refund attempt. See below example of a refund reversal. You will notice that the refundId is a required variable input to perform the reversal.

  1. GraphQL Mutation
  2. GraphQL Variables
  3. Sample API Response
mutation reverseRefund($input: ReverseRefundInput!) {
                reverseRefund(input: $input) {
                  refund {
                    ... on Refund {
                      id
                      status
                      statusHistory {
                        status
                        terminal
                      }
                    }
                    ... on Refund {
                      id
                      amount {
                        value
                      }
                      orderId
                      status
                      refundedTransaction {
                        id
                        amount {
                          value
                        }
                        orderId
                        status
                      }
                    }
                  }
                }
              }

Refund Reversal Example Sequence Diagram:AnchorIcon

5) Using Auth Adjustments for partial reversalsAnchorIcon

There may be some use cases where you want to perform a partial reversal rather than fully reverse a payment. The example scenario is if a customer wants to return a single item that was part of a larger purchase, and the transaction may be in an authorized state (which would make a partial refund not possible). In this scenario, you may use the updateTransaction API mutation to adjust the auth amount to the total purchase amount minus the partial reversal amount.

Receipt Data HandlingAnchorIcon

To maintain EMV compliance you may need to add additional data elements to be displayed on the receipt you offer your customers. The table below highlights the data elements that are mandatory for EMV compliance and other data elements that are optional. Be sure to include the mandatory data elements listed in the "GraphQL API Field" column when you check the reader charge status so that you can parse them from the API response.

note

EMV-compliant receipts are not enforced by PayPal/Braintree. We make all necessary data for compliance available through our API and highly suggest following EMV compliance rules. For more information about EMV-compliant receipts, see the EMVco website.

This is just an example of an EMV compliant omni-cart receipt. Merchants should format their receipts in a way that make sense for their business while incorporating the below EMV elements listed in the table.

note

Please take a look at our EMV-compliant receipt reference page, including an explanation with an EMV-compliant data element as an overlay.

Data Element

Description

Mandatory vs. Optional

GraphQL API Field

Merchant Name

The name of the merchant.

M

Transaction.merchantName

Merchant Address Details

The address details of the merchant.

M

Transaction.merchantAddress

Transaction Date and Time

The date and time of the transaction.

M

Transaction.createdAt

Merchant ID (MID)

The unique reference of the merchant.

O

Transaction.merchantId

Terminal ID (TID)

The unique reference of the terminal.

O

Transaction.paymentMethodSnapshot .origin.details.terminalId

Transaction Number

The unique reference number of the transaction.

M

Transaction.legacyId

Invoice Number

The unique reference number of the receipt.

M

Transaction.legacyId

Card Type

The name of the card issuer.

M

Transaction.paymentMethodSnapshot. creditCard.brand

Account Number

The account number printed on the card. All digits except for last 4 must be truncated.

M

Transaction.paymentMethodSnapshot. creditCard.last4

Application Preferred Name

The name of the application used to process the transaction (e.g. MasterCard).

M

Transaction.paymentMethodSnapshot .origin.details.applicationPreferredName

Application Identifier

The unique reference number of the EMV application used for the transaction.

M

Transaction.paymentMethodSnapshot .origin.details.applicationIdentifier

Approval Code

The authorization code received from the processor for the transaction.

M

Transaction.statusHistory[…]. processorResponse.authorizationId

Authorization Mode

The authorizing entity (e.g. Issuer).

M

Transaction.paymentMethodSnapshot .origin.details.authorizationMode

Transaction Kind

The type of the transaction (e.g. Sale/Refund).

M

Transaction.kind

Card Entry Method

The method used to read the detail of the card (e.g. Chip).

M

Transaction.paymentMethodSnapshot .origin.details.inputMode

Purchase Details

A brief description of the purchased goods or services along with their prices.

M

Transaction.lineItems* * - If not provided in the authorize/charge request, then these details must be provided by the merchant.

Transaction Amount

The grand total amount for the transaction.

M

Transaction.amount.value

Currency

If specified, the currency that the transaction was processed in. If no currency is identified on the receipt, the transaction is deemed to have taken place in the currency that is legal tender at the point of sale.

O

Transaction.amount.currencyCode

PIN Verify Statement

The cardholder’s PIN verification status specific to the processed transaction (e.g. Verified).

M

Transaction.paymentMethodSnapshot .origin.details.pinVerified

Return and Refund Policies

The terms and conditions for return and refund set forth by the merchant.

M

Merchant or POS generated message that indicates return policy

The following are data elements that are mandatory for transactions that were declined:

Decline Code

The system generated decline code for the declined transaction.

M

Transaction.statusHistory[…] .processorResponse.legacyCode

Decline Message

The merchant generated message for the respective decline code.

M

Merchant or POS generated message that reflects the decline code

The following are data elements that are mandatory for transactions that were EMV chip declined:

Terminal Verification Results

A series of bits set by the terminal upon reading EMV card data, where each bit represents information about the transaction. This value is contained in the response EMV data under tag 95.

M

Transaction.statusHistory[…] .processorResponse.emvData

Issuer Authentication Data

Issuer provided data received in response to an online authorization request to be delivered to the EMV chip card. This value is contained in the response EMV data under tag 91.

M

Transaction.statusHistory[…] .processorResponse.emvData

Authorization Response Code

The 4-character code generated by the Issuer in response to an online authorization request that represents the authorization status of the transaction. This value is contained in the response EMV data under tag 8A.

M

Transaction.statusHistory[…] .processorResponse.emvData

Manual Key Entry TransactionsAnchorIcon

There are some use cases in which you may want to support the processing of a transaction without using the card reader by entering in the card details into another application UI. This could be for over-the-phone orders (MOTO) or as a manual key entry (MKE) fallback for when the card is not readable by the card reader, in which case you may want to enter the card details into the POS UI. To facilitate this while keeping your tech stack outside of PCI scope, we have a couple of solutions that can work for you.

1) Braintree Hosted FieldsAnchorIcon

The Braintree Hosted Fields enables you to render Braintree encrypted input fields in your application user interface. This allows for the manual key entry of card details directly into Braintree encrypted input fields. See how the Braintree Hosted Fields solution works.

note

The Braintree Hosted Fields solution gives you more control over the checkout flow and the flexibility to blend the encrypted fields into your POS UI

2) Braintree Drop-in UIAnchorIcon

The Braintree drop-in UI allows you to render Braintree encrypted forms in your application user interface. This would allow for the manual key entry of the card details directly into the Braintree Encrypted fields. The drop-in UI is a simple integration leveraging pre-built dynamic Braintree containers. See how the Braintree Drop-in UI solution works.

note

The drop in UI allows for a simpler integration but reduced control over the look and feel of the UI and checkout flow experience

Important Tips for building out your charge/refund flowsAnchorIcon

  1. Always make sure you are passing your Braintree Merchant Account ID (MAID) in your charge and refund requests. This should be added as an API variable, for example:

  2. Use Idempotency Keys to prevent duplicate charges to your customers

  3. Align your application with our recommended timeout logic

  4. Make sure you are parsing key EMV receipt data from your API response to populate on your customer receipts for EMV compliance

  5. Utilize the orderId API field to pass important data for reporting and reconciliation