PoyntOS architecture provides an extensible way to add new payment methods (custom tenders). This document describes how a developer can build a custom transaction processor by implemeting IPoyntTransactionService interface to create a gift card app for Poynt. While the scope of this document is to address integration with gift card providers this interface can be used to implement Loyalty, Discount or any other type of service that can create a custom tender transaction.
PoyntSamples app has a sample implementation of a custom transaction processor.
On a traditional terminal, a merchant would need to select a gift card option before swiping a gift card. Poynt Payment Fragment automatically determines whether or not the swiped card is a gift card based on it’s BIN range and routes the transaction to the custom transaction processor. In addition, based on the configuration of the custom transaction processor you can add a button in the payment options screen of the payment fragment to process a non-swipe input (e.g. scan QR code, manually input gift card number, coupon code, etc).
Specify configuration in giftcard_transaction_capabilities.xml
Updating card reader configuration on the terminal.
Note: By default Poynt card reader encrypts all track data irrespective of card BIN range. To turn off encrypting track data, please refer to this code example. Please note that this setting will not apply to cards that start with BIN ranges reserved by payment card brands.
Transaction Capabilities Configuration
This configuration file specifies entry methods and the BIN range of the payment cards supported by your application.
This config file should be located in res/xml directory of your app and referenced from your app’s manifest:
Note: The value of <provider> element shows in payment options:
Implementing IPoyntTransactionService interface
IPoyntTransactionService has a number of methods. The only ones that need to be implemented are in bold below:
Create a Service class that implements IPoyntTransactionService.Stub class and returns it from onBind
Override processTransaction() to handle SALE, and REFUND (referenced and non-referenced) requests. Please note that the Transaction object you receive here will carry unencrypted track data.
Override captureTransaction() to capture an AUTH. If your backend API does not follow auth/capture paradigm, this method does not need to be implemented.
Override voidTransaction() to void an AUTH. If your backend API does not follow auth/capture paradigm, this method does not need to be implemented.
Override reverseTransaction() to void/reverse any transaction (SALE, CAPTURE, REFUND or even an AUTH). Note: reverseTransaction() on an AUTH is the same as voidTransaction() on an AUTH. A reverseTransaction() gets called when the Poynt Payment Fragment/Card Reader is unable to complete the transaction. This could happen:
when an Online Authorization request (processTransaction) has timed out
merchant hit CANCEL button in the payment fragment before transaction got processed
Override updateTransaction() to adjust an AUTH or SALE transactions (e.g. to add tip or even adjust the base amount). If your service does not support auth/sale adjustment, return a PoyntError.
Override getTransaction() to return the details about the transactions
captureEMVData() is not used.
checkCard() should not be used.
captureAllTransactions() is currently not used in the Terminal but will be used in the future to request capturing all previously authorized (and still valid) transactions.
createTransaction() is not currently used
saveTransaction() is not currently used
Please note that all API calls MUST respond with the corresponding callbacks - not doing so could cause bad user experiences with Payment Fragment waiting for a response from your processor for ever
IPoyntTransactionServiceListener callbacks:
onResponse(Transaction, RequestId, PoyntError) - processed transaction object otherwise an error indicating why the transaction couldn’t be processed. See below for information on what must be loaded in the processed Transaction object. Note that a processed transaction could be approved or declined.
onLoginRequired() - if your transaction processor determines that the merchant session has timed out. This usually happens when your JWT expires.
onLaunchActivity(Intent, requestId) - use this callback when you need to collect additional information that is not collected by Poynt Payment Fragments - eg. zip code or cvv. The Intent must carry whatever information you would need to handle UI/UX based on your needs. This intent will be launched as an Activity with result. So it is important that you return a result with 3 Parcelable extras containing “transaction”, “payment” and “error”. The “transaction” object contains the processed transaction as you would otherwise return in onResponse() callback, “payment” object if you’ve update the payment object based on additional data that you’ve collected, and “error” if the transaction has failed. Use this callback to implement support for manual entry.
Tip: Setting Transaction.setSignatureCaptured(false) will skip the signature screen if you don't need to collect signature.
Handling SALE Request
When a gift card is swiped on a Poynt terminal, PoyntOS will call processTransaction() of your transaction service and pass a Transaction object like the one below:
Here’s the logic you should use to determine if a merchant swiped a card:
Returning the response to Payment Fragment:
Implementing Partial Approval
Implementing partial approval requires only changing a few lines of code:
Handling Refund Request
To determine if the merchant has performed a refund action you need to perform the following check:
Here’s an example Transaction object passed as an argument to processTransaction in a refund use case.
Note that this is not the original sale transaction, but rather a Transaction object that needs to be updated by your transaction service once the refund is processed. Poynt transaction id of the original sale is referenced as the value of parentId in the Transaction object.
As you can see, the Refund transaction object contains all references (i.e. processorTransactionId) set during processing the SALE transaction. You can use that id to look up which transaction needs to be refunded on your backend.
After you perform the refund by calling your backend, you need to create ProcessorResponse, updating the Transaction object and return it using listener.onResponse callback.
Linking Refund request to Original Sale
If you need to store a reference id (e.g. processor transaction id, invoice id, etc.) to facilitate linking the refund REFUND to the original SALE you can do that by adding your own reference id to the Transaction object when processing the SALE:
Partial Refund
Payment Fragment UI allows merchant to specify a partial refund amount.
If your gift card service does not support partial refunds and you determine that the amount passed in the refund request does not match the sale amount, you will need to return a PoyntError to the listener.
Manual Entry
In addition, to a card swipe entry method, you may need to support other entry methods like scanning QR code or entering card number manually. This can be accomplished by adding “CUSTOM” entry method in the capabilities configuration file (please refer to the Transaction Capabilities Configuration section of this document). Adding this entry method will add a new payment option button in the Payment Fragment payment options menu.
When that button is pressed, Payment Fragment will call processTransaction of your transaction service and pass a Transaction object:
Note that the Transaction object does not have a nested Card object inside it's FundingSource.
Here’s how you can determine if the merchant initiated the transaction by pressing your custom button in payment options menu of the Payment Fragment:
Once your activity finished collecting additional information it should call your transaction service to process the request and get the updated Transaction object back. Your Activity should finish by creating the following intent:
This sequence diagram explains the manual entry flow from start to finish:
Activating, Reloading, Checking Balance of a Card
In order to be able to activate, reload or check balance of a gift card your application needs to be able to read card’s track data. This can be accomplished by launching the Payment Fragment with a readCardData only flag.
You need to include the following code in your Activity:
Note that when Payment Fragment comes up and it has no amount and operation type is displayed as READ CARD.
Transaction Object returned after readCardOnly operation:
Error Handling
In case of a failure other than processor decline, your transaction should return a PoyntError to the listener. Below is a list of applicable error codes that can be set by your application.
Why is my custom tender transaction not showing up in Transaction list?
Please check the logcat when you create the transaction. The most likely reason is that you are not setting a required field in the Transaction object in which case you would see a 400 error in the logcat with the message explaining which field should be set.
When I perform a refund the Payment Fragment shows status Refunded but after Transaction list refreshes the SALE transaction is still showing as refundable
Please check the logcat to see if PUT call to record the refund in the Poynt cloud has failed. This could happen if your transaction processor did not set all of the required fields in the Transaction object. The developerMessage in the error response will indicate which required field is not set.