Initiate a Sale or Refund
This article covers both sale and refund transactions
Initializing the Reader for Charging
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
,
merchantAccountId
and
transaction.amount
details to initialize the reader for payment acceptance. Note that the
merchantAccountId
is 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
--header 'Idempotency-Key: 94c9ea8b-31b0-488e-a231-57e32f0bfd70'
- GraphQL Mutation
- GraphQL Variables
- Sample API Response
mutation RequestChargeFromInStoreReader($input: RequestChargeFromInStoreReaderInput!) {
requestChargeFromInStoreReader(input: $input) {
clientMutationId
id
status
reader {
id
name
status
}
}
}
Checking the Reader Charge Status
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.status
field as the transaction
goes through the lifecycle.
When the
RequestChargeInStoreContext.status
changes to "COMPLETE", you will also receive a transaction object
RequestChargeInStoreContext.transaction
in 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:
- GraphQL Query
- PENDING Response
- COMPLETE Response
- FAILED Response : Decline
- FAILED Response : Network Error
- 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 Diagram
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.
Recommended Timeout Logic
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.
Recommended sequence for initializing card reader and polling for transaction result:
Step 1 |
Generate unique
|
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
|
Step 4 |
Once you get an
|
Step 5 |
Use an HTTP timeout window of 3 seconds if you do not get a
response while polling against the
|
Step 6 |
Send the
polling request
every 2-3 seconds until you get the final
transaction.status,
|
Cancelling a Charge
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.
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
- GraphQL Mutation
- GraphQL Variables
- Sample API Response
mutation RequestCancelFromInStoreReader(
$input: RequestCancelFromInStoreReaderInput!
) {
requestCancelFromInStoreReader(input: $input) {
id
status
reader {
id
name
status
location {
id
name
}
}
}
}
Partial Authorizations
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 Enablement
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.
Refunding Your Customer
There are 3 ways of refunding your customer once a charge has been completed.
1) Performing a Referenced Refund:
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.
- GraphQL Mutation
- GraphQL Variables
- 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:
2) Using the Reversal API call
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:
- GraphQL Mutation
- GraphQL Variables
- 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:
3) Performing an Unreferenced Refund or "Blind Credit":
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
- GraphQL Mutation
- GraphQL Variables
- 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.
- GraphQL Mutation
- GraphQL Variables
- 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:
4) Reversing a Refund
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.
- GraphQL Mutation
- GraphQL Variables
- 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:
5) Using Auth Adjustments for partial reversals
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 Handling
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.
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 Transactions
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 Fields
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.
2) Braintree Drop-in UI
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.
Important Tips for building out your charge/refund flows
-
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:
-
Make sure you are parsing key EMV receipt data from your API response to populate on your customer receipts for EMV compliance