Offline Transactions via Deferred Authorizations
To accept card payments, your merchants point-of-sale (POS) systems must be connected to the internet for transaction processing and authorization. However, what options do merchants have to avoid business interruption when internet connectivity is an issue?
Processing offline transactions via deferred authorization enables your merchants to accept payments
at their own risk
when an internet connection is not available. Transactions are securely queued locally and after the internet connection is restored, the merchant can submit the stored their offline transactions for deferred authorization.
Before implementing this feature please talk to your account manager, to understand whether the risk introduced by processing offline transactions is acceptable for the type of merchants you serve.
To use the Offline Transactions feature, your POS must be extended for running and submitting offline transactions and your processes must ensure that offline transactions are reconciled later on, specifically that there is a verification process in place for which transactions were approved or declined.
Installing Additional Dependencies
In order to fully support Offline Transactions in the SDK, please make sure you are updating your Cocoapod dependencies to reflect offline mode in there. Your
Podfile
needs to reference the
payworks/offline
dependency in addition to your normal ones:
source 'https://github.com/CocoaPods/Specs.git' source 'https://github.com/visa/mpos.sdk.ios.pods.git' use_frameworks! target :"<your-app-target>" do pod 'payworks', '2.61.0' pod 'payworks/offline', '2.61.0' pod 'payworks.paybutton', '2.61.0' end
Running and Submitting Offline Transactions
All the offline operations are syntactically similar to the standard transaction operations and are exposed via the new offline module.
To get started, just create the
TransactionProvider
,
TransactionParameters
and
AccessoryParameters
as normal:
MPTransactionProvider *transactionProvider = [MPMpos transactionProviderForMode:MPProviderModeTEST merchantIdentifier:@"MERCHANT_IDENTIFIER" merchantSecretKey:@"MERCHANT_SECRET_KEY"]; MPTransactionParameters *transactionParameters = [MPTransactionParameters chargeWithAmount:[NSDecimalNumber decimalNumberWithString:@"10"] currency:MPCurrencyEUR optionals:^(id _Nonnull optionals) { [optionals setSubject:@"my first transaction"]; [optionals setCustomIdentifier:@"yourReferenceForTheTransaction"]; }]; /* When using the Bluetooth Miura, use the following parameters: MPAccessoryParameters *accessoryParameters = [MPAccessoryParameters externalAccessoryParametersWithFamily:MPAccessoryFamilyMiuraMPI protocol:@"com.miura.shuttle" optionals:nil]; */ /* When using Verifone readers via WiFi or Ethernet, use the following parameters: MPAccessoryParameters *accessoryParameters = [MPAccessoryParameters tcpAccessoryParametersWithFamily:MPAccessoryFamilyVerifoneVIPA remote:@"192.168.254.123" port:16107 optionals:nil]; */
Starting Offline Transaction
Running an offline transaction is just as simple as:
[transactionProvider.offlineModule startTransactionWithParameters:transactionParameters accessoryParameters:accessoryParameters processParameters:nil registered:^(MPTransactionProcess * transactionProcess, MPTransaction * transaction) { NSLog(@"Transaction identifier is %@. Store it in your backed so that you can always query its status", transaction.identifier); } statusChanged:^(MPTransactionProcess * transactionProcess, MPTransaction *transaction, MPTransactionProcessDetails * details) { NSLog(@"Status changed %@", details.information); } actionRequired:^(MPTransactionProcess * transactionProcess, MPTransaction * transaction, MPTransactionAction action, MPTransactionActionSupport *support) { NSLog(@"Action is required!"); switch (action) { case MPTransactionActionCustomerSignature: // in a live app, this image comes from your signature screen [transactionProcess continueWithCustomerSignature:[UIImage new] verified:YES]; break; case MPTransactionActionCustomerIdentification: // always return false here [transactionProcess continueWithCustomerIdentityVerified:NO]; break; case MPTransactionActionApplicationSelection: { // This happens only for readers that don't support application selection on their screen MPTransactionActionApplicationSelectionSupportWrapper *sw = [MPTransactionActionApplicationSelectionSupportWrapper wrapAround:support]; [transactionProcess continueWithSelectedApplication:sw.applications.firstObject]; } break; case MPTransactionActionCreditDebitSelection: [transactionProcess continueCreditDebitSelectionWithDebit]; break; case MPTransactionActionNone: //NOOP break; } } completed:^(MPTransactionProcess * transactionProcess, MPTransaction *transaction, MPTransactionProcessDetails * details) { if (details.state == MPTransactionProcessDetailsStateAccepted) { // Make sure to store the transactionIdentifier for later reconciliation NSLog(@"transactionIdentifer for reconciliation: %@", transaction.identifier); // print the merchant receipt MPReceipt *merchantReceipt = transaction.merchantReceipt; // print a signature line if required if (merchantReceipt.printSignatureLine) { NSLog(@"\n\n\n------ PLEASE SIGN HERE ------"); } // ask the merchant, whether the shopper wants to have a receipt MPReceipt *customerReceipt = transaction.customerReceipt; // and close the checkout UI } else { // Allow your merchant to try another transaction // Make clear to merchant NOT to hand out the goods } }];
But only when the
TransactionProcessDetailsState
equals
ACCEPTED
, the Offline Transaction was successfully stored for later submission (and even later authorization). For any other status, make clear to your merchant that they should not hand out the goods.
Make sure to store the
transactionIdentifier
for later reconciliation (see below) and to provide compliant merchant and shopper receipts as documented here.
Refunding Offline Transactions
Refunding a previous Offline Transaction is just as simple as:
MPTransactionParameters *refundTransactionParameters = [MPTransactionParameters refundForCustomIdentifier:@"transactionIdentifier" optionals:nil]; [transactionProvider.offlineModule amendTransactionWithParameters:refundTransactionParameters statusChanged:^(MPTransactionProcess *transactionProcess, MPTransaction *transaction, MPTransactionProcessDetails *details) { //intermediary update } completed:^(MPTransactionProcess *transactionProcess, MPTransaction *transaction, MPTransactionProcessDetails *details) { if (transaction.status == MPTransactionStatusAccepted) { //logic for successful offline transaction refund } }];
Information on creating your own receipts can be found here.
Note that partial refunds are not supported for Offline Transactions.
Submitting Offline Transactions for Deferred Authorization
Once internet connection is restored, you need to make sure that the merchant submits the stored Offline Transactions for Deferred Authorization: We suggest to train your merchants to do this as part of their daily end-of-day/store closing routines.
If the Offline Transactions are not submitted, they will not be processed, hence they remain stored locally and the merchant will not receive the actual money. Do not forget to ask your merchants to submit them as soon as possible!
Submitting the offline transactions will send all stored Offline Transactions to the gateway, which then asynchronously will attempt to run a Deferred Authorization for each of the submitted transactions.
Offline Transactions submission can be achieved as follows:
[transactionProvider.offlineModule submitOfflineTransactionsBatchWithCompleted:^(MPOfflineBatchUploadProcess *uploadProcess, MPSubmitBatchResponse * response) { if (response.error == nil) { // The Offline Transactions have been submitted successfully // for later Deferred Authorization. // You can find the submitted transactions by NSArray *submittedTransactions = response.transactions; // You can mark them as submitted in your POS as submitted, but not yet APPROVED/DECLINED } else { // error indicates that there was a problem refunding the transaction // check response.error.type // and response.error.info for more information // there could be submitted transaction present even if there are errors // as the error may have occured during submission process( Eg: Internet connectivity issues) // response.transactions; // Make your merchants aware that they need to attempt to submit // the Offline Transactions again, until successful } }];
In case an error occurs, please make your merchants aware that they need to attempt to submit the Offline Transactions again, until successful.
If the error indicates
ErrorType.SERVER_OFFLINE_BATCH_MALFORMED
then you will find some submitted transactions to be pending manual review. This indicates that these submitted transactions will be reviewed manually by us because there was some discrepancy with the transaction data.
Lookup and Querying Offline Transactions
A transaction lookup for any stored Offline Transaction can achieved as follows:
[transactionProvider.offlineModule lookupTransactionWithTransactionIdentifier:@"transactionIdentifier" completed:^(MPTransaction *transaction, NSError *error) { if (error == nil) { //apply logic for the found transaction } else { //handle error } }];
Querying the stored Offline Transactions is achieved as follows:
boolean includeReceipts = false; int offset = 0; int page = 10; [transactionProvider.offlineModule queryTransactionsWithRange:NSMakeRange(100, 0) includeReceipts:YES completed:^(NSArray *transactions, NSError *error) { if (error == nil) { //apply logic for the found transaction } else { //handle error } }];
The respective response messages in either case contain the stored Offline Transactions which were not yet submitted to the gateway.
Reconciling Offline Transactions
After submitting Offline Transactions it's important that you verify the final transaction outcome for each
ACCEPTED
transaction.
To achieve this, your POS or Backend needs to query each
ACCEPTED
transaction and then act according to the status:
  • PENDING
    means that the
    ACCEPTED
    transaction is still queued for Deferred Authorization, which will result in either
    APPROVED
    or
    DECLINED
    eventually. You need to check the status again later.
  • APPROVED
    means that the
    ACCEPTED
    transaction has successfully been approved and that the merchant will receive the money. No further actions are required for you and your merchant.
  • DECLINED
    means that the merchant will NOT receive money for the
    ACCEPTED
    transaction. The merchant should either contact the shopper for an alternative method of payment or write off the loss, depending on the payment risk approach of the merchant.
You can get the status of each
ACCEPTED
transaction via either:
Querying the
transactionIdentifier
of the
ACCEPTED
transaction from your POS:
[transactionProvider.transactionModule lookupTransactionWithTransactionIdentifier:@"transactionIdentifierOfAcceptedTransaction" completed:^(MPTransaction * _Nullable transaction, NSError * _Nullable error) { if (error == nil) { if (transaction.status == MPTransactionStatusApproved) { // Merchant will receive the money. No further actions are required. NSLog(@"Final state: APPROVED"); } else if (transaction.status == MPTransactionStatusDeclined) { // Merchant will NOT receive money. NSLog(@"Final state: DECLINED"); } else if (transaction.status == MPTransactionStatusPending) { // Transaction is still queued for Deferred Authorization, try again later NSLog(@"Still waiting for Deferred Authorization"); } else { // handle error and try again } } else { // handle error and try again } }];
  • Querying the
    transactionIdentifier
    of the
    ACCEPTED
    transaction from your Backend.
  • Receiving a WebHook on your Backend with the final status (
    APPROVED
    or
    DECLINED
    ) of the transaction. For this you need to match the previously stored
    transactionIdentifier
    of the
    ACCEPTED
    transaction with the
    transactionIdentifier
    of the WebHook.
Offline Transactions via Deferred Authorizations
To accept card payments, your merchant's POS must be connected to the internet for processing and authorizing transactions online. But what happens if the internet connection or the acquirer is down? Usually the merchant is blocked and cannot accept card payments temporarily. This leads not only to customer dissatisfaction, but also to a real business challenge.
Offline Transactions allow your merchants to accept payments
at their own risk
when an internet connection is not available. Transactions are queued securely locally and once the internet connection is restored, the merchant can submit the stored Offline Transactions for Deferred Authorization.
Before implementing this feature please contact your account manager, to understand whether the risk introduced by Offline Transactions is acceptable for the kind of merchants you aim to serve.
To use Offline Transactions, your POS must be extended for running and submitting offline transactions and your processes must ensure that Offline Transactions are reconciled later on, i.e. that it's verified which transactions got approved or declined in the end.
Running and Submitting Offline Transactions
All the offline operations are syntactically similar to the standard transaction operations and are exposed via the new offline module.
To get started, just create the
TransactionProvider
,
TransactionParameters
and
AccessoryParameters
as normal:
TransactionProvider transactionProvider = Mpos.createTransactionProvider(MyActivity.this, ProviderMode.TEST, "MERCHANT_IDENTIFIER", "MERCHANT_SECRET_KEY"); TransactionParameters transactionParameters = new TransactionParameters.Builder() .charge(new BigDecimal("10"), Currency.EUR) .subject("my first transaction") .customIdentifier("yourReferenceForTheTransaction") .build(); /* When using the Bluetooth Miura, use the following parameters: AccessoryParameters accessoryParameters = new AccessoryParameters.Builder(AccessoryFamily.MIURA_MPI) .bluetooth() .build(); */ /* When using Verifone readers via WiFi or Ethernet, use the following parameters: AccessoryParameters accessoryParameters = new AccessoryParameters.Builder(AccessoryFamily.VERIFONE_VIPA) .tcp("192.168.254.123", 16107) .build(); */
Starting Offline Transaction
Running an offline transaction is just as simple as:
[transactionProvider.offlineModule startTransactionWithParameters:transactionParameters accessoryParameters:accessoryParameters processParameters:nil registered:^(MPTransactionProcess * transactionProcess, MPTransaction * transaction) { NSLog(@"Transaction identifier is %@. Store it in your backed so that you can always query its status", transaction.identifier); } statusChanged:^(MPTransactionProcess * transactionProcess, MPTransaction *transaction, MPTransactionProcessDetails * details) { NSLog(@"Status changed %@", details.information); } actionRequired:^(MPTransactionProcess * transactionProcess, MPTransaction * transaction, MPTransactionAction action, MPTransactionActionSupport *support) { NSLog(@"Action is required!"); switch (action) { case MPTransactionActionCustomerSignature: // in a live app, this image comes from your signature screen [transactionProcess continueWithCustomerSignature:[UIImage new] verified:YES]; break; case MPTransactionActionCustomerIdentification: // always return false here [transactionProcess continueWithCustomerIdentityVerified:NO]; break; case MPTransactionActionApplicationSelection: { // This happens only for readers that don't support application selection on their screen MPTransactionActionApplicationSelectionSupportWrapper *sw = [MPTransactionActionApplicationSelectionSupportWrapper wrapAround:support]; [transactionProcess continueWithSelectedApplication:sw.applications.firstObject]; } break; case MPTransactionActionCreditDebitSelection: [transactionProcess continueCreditDebitSelectionWithDebit]; break; case MPTransactionActionNone: //NOOP break; } } completed:^(MPTransactionProcess * transactionProcess, MPTransaction *transaction, MPTransactionProcessDetails * details) { if (details.state == MPTransactionProcessDetailsStateAccepted) { // Make sure to store the transactionIdentifier for later reconciliation NSLog(@"transactionIdentifer for reconciliation: %@", transaction.identifier); // print the merchant receipt MPReceipt *merchantReceipt = transaction.merchantReceipt; // print a signature line if required if (merchantReceipt.printSignatureLine) { NSLog(@"\n\n\n------ PLEASE SIGN HERE ------"); } // ask the merchant, whether the shopper wants to have a receipt MPReceipt *customerReceipt = transaction.customerReceipt; // and close the checkout UI } else { // Allow your merchant to try another transaction // Make clear to merchant NOT to hand out the goods } }];
But only when the
TransactionProcessDetailsState
equals
ACCEPTED
>, the Offline Transaction was successfully stored for later submission (and even later authorization). For any other status, make clear to your merchant that they should not hand out the goods.
Make sure to store the
transactionIdentifier
for later reconciliation (see below) and to provide compliant merchant and shopper receipts as documented here.
Refunding Offline Transactions
Refunding a previous Offline Transaction is just as simple as:
TransactionParameters refundTransactionParameters = new TransactionParameters.Builder() .refund("transactionIdentifer") .build(); transactionProvider.getOfflineModule().amendTransaction(refundTransactionParameters, new BasicTransactionProcessListener() { @Override public void onStatusChanged(TransactionProcess transactionProcess, Transaction transaction, TransactionProcessDetails transactionProcessDetails) { //intermediary update } @Override public void onCompleted(TransactionProcess transactionProcess, Transaction transaction, TransactionProcessDetails transactionProcessDetails) { if (transaction.getStatus().equals(TransactionStatus.ACCEPTED)) { //logic for successful offline transaction refund } } });
Information on creating your own receipts can be found here
Note that partial refunds are not supported for Offline Transactions.
Submitting Offline Transactions for Deferred Authorization
Once internet connection is restored, you need to make sure that the merchant submits the stored Offline Transactions for Deferred Authorization: We suggest to train your merchants to do this as part of their daily end-of-day/store closing routines.
If the Offfline Transactions are not submitted, they will not be processed, hence they remain stored locally and the merchant will not receive the actual money. Do not forget to ask your merchants to submit them as soon as possible!
Submitting the offline transactions will send all stored Offline Transactions to the gateway, which then asynchronously will attempt to run a Deferred Authorization for each of the submitted transactions.
Offline Transactions submission can be achieved as follows:
transactionProvider.getOfflineModule().submitTransactionsBatch(new SubmitTransactionsBatchProcessListener(){ @Override public void onCompleted(SubmitTransactionsBatchProcessDetails submitTransactionsBatchProcessDetails) { if (submitTransactionsBatchProcessDetails.getError() == null) { // The Offline Transactions have been submitted successfully // for later Deferred Authorization. // You can find the submitted transactions by List<SubmittedTransaction> submittedTransactions = submitTransactionsBatchProcessDetails.getAllTransactions(); // You can mark them as submitted in your POS as submitted, but not yet APPROVED/DECLINED } else { // error indicates that there was a problem refunding the transaction // check submitTransactionsBatchProcessDetails.getError().getErrorType() // and submitTransactionsBatchProcessDetails.getError().getInfo() for more information // there could be submitted transaction present even if there are errors // as the error may have occured during submission process( Eg: Internet connectivity issues) // details.getAllTransactions(); // Make your merchants aware that they need to attempt to submit // the Offline Transactions again, until successful } } });
In case an error occurs, please make your merchants aware that they need to attempt to submit the Offline Transactions again, until successful.
the error indicates
ErrorType.SERVER_OFFLINE_BATCH_MALFORMED
then you will find some submitted transactions to be pending manual review. This indicates that these submitted transactions will be reviewed manually by us because there was some discrepancy with the transaction data.
Lookup and Querying Offline Transactions
A transaction lookup for any stored Offline Transaction can achieved as follows:
transactionProvider.getOfflineModule().lookupTransaction("add your transaction id here", new LookupTransactionListener(){ @Override public void onCompleted(String transactionIdentifier, Transaction transaction, MposError mposError) { if (mposError == null) { //apply logic for the found transaction } else { //handle error } } });
Querying the stored Offline Transactions is achieved as follows:
boolean includeReceipts = false; int offset = 0; int page = 10; FilterParameters filterParameters = new FilterParameters.Builder().build(); transactionProvider.getOfflineModule().queryTransactions(filterParameters, includeReceipts, offset, page, new QueryTransactionsListener() { @Override public void onCompleted(FilterParameters filterParameters, boolean includeReceipts, int offset, int limit, List transactions, MposError error) { if (error == null) { //apply logic for the found transactions } else { //handle error } } });
The respective response messages in either case contain the stored Offline Transactions which were not yet submitted to the gateway.
Reconciling Offline Transactions
After submitting Offline Transactions it's important that you verify the final transaction outcome for each
ACCEPTED
transaction.
To achieve this, your POS or Backend needs to query each
ACCEPTED
transaction and then act according to the status:
  • PENDING
    means that the
    ACCEPTED
    transaction is still queued for Deferred Authorization, which will result in either
    APPROVED
    or
    DECLINED
    eventually. You need to check the status again later.
  • APPROVED
    means that the
    ACCEPTED
    transaction has successfully been approved and that the merchant will receive the money. No further actions are required for you and your merchant.
  • DECLINED
    means that the merchant will NOT receive money for the
    ACCEPTED
    transaction. The merchant should either contact the shopper for an alternative method of payment or write off the loss, depending on the payment risk approach of the merchant.
You can get the status of each
ACCEPTED
transaction via either:
Querying the
transactionIdentifier
of the
ACCEPTED
transaction from your POS:
transactionProvider.getTransactionModule().lookupTransaction("transactionIdentifierOfAcceptedTransaction", new LookupTransactionListener(){ @Override public void onCompleted(String transactionIdentifier, Transaction transaction, MposError mposError) { if (mposError == null) { if(transaction.getStatus() == TransactionStatus.APPROVED) { // Merchant will receive the money. No further actions are required. Log.d("mpos", "Final state: APPROVED"); } else if(transaction.getStatus() == TransactionStatus.DECLINED) { // Merchant will NOT receive money. Log.d("mpos", "Final state: DECLINED"); } else if(transaction.getStatus() == TransactionStatus.PENDING) { // Transaction is still queued for Deferred Authorization, try again later Log.d("mpos", "Still waiting for Deferred Authorization"); } else { // handle error and try again } } else { // handle error and try again } } }); }
  • Querying the
    transactionIdentifier
    of the
    ACCEPTED
    transaction from your Backend.
  • Receiving a WebHook on your Backend with the final status (
    APPROVED
    or
    DECLINED
    ) of the transaction. For this you need to match the previously stored
    transactionIdentifier
    of the
    ACCEPTED
    transaction with the
    transactionIdentifier
    of the WebHook.