Getting Started with Xamarin

Learn how to set up your Xamarin app to use QuantumPay to process payment transactions.

Table of contents
  1. Getting Started with Xamarin
    1. Requirements
    2. Project setup
      1. Installing dependancies
      2. Adding the QuantumPay SDKs
      3. Add MFi protocols to Info.plist
      4. Add privacy entries to Info.plist
    3. Processing a payment
      1. Initialize the SDKs
      2. Create Payment Device
      3. Create payment engine
      4. Setup handlers
      5. Connect to payment device
      6. Create an invoice
      7. Create a transaction
      8. Start transaction
      9. Transaction receipt
    4. Disconnect payment device
    5. Connect bluetooth peripheral to iOS device using the camera
      1. Add permisions to Info.plist
      2. Implement scanner page
    6. Scan product barcodes
      1. Scanning a Barcode

Requirements

  • SDKs
    • QuantumPay.Client.dll
    • QuantumPay.Mobile.dll
    • QuantumPay.Peripherals.InfinitePeripherals.iOS.dll
    • QuantumSDK.iOS.dll
  • Xcode 11+ / iOS 13+
  • Infinite Peripherals payment device
  • Infinite Peripherals developer key for your app bundle ID
  • Payment related credentials: username/email, password, service name and tenant key

If you are missing any of these items, please contact Infinite Peripherals


Project setup

Before we jump into the code we need to make sure your project is properly configured to use the QuantumPay frameworks. Follow the steps below to get you set up.

Installing dependancies

Please install the following dependancies.

System.Buffers: https://www.nuget.org/packages/System.Buffers/

Adding the QuantumPay SDKs

  1. Open Visual Studio and create a new folder to put the frameworks in. If you have a place for frameworks already you can skip this.

  2. Import the QuantumPay frameworks into your folder.

  1. Import the QuantumSDK.iOS.resources folder provided by IPC Peripherals.

Add MFi protocols to Info.plist

Go to your project’s Info.plist file and add a new entry for “Supported external accessory protocols” using the following values. Note: in Xcode 13.0+ this has been moved to the “Info” tab in your project’s settings.

com.datecs.pengine
com.datecs.linea.pro.msr
com.datecs.linea.pro.bar
com.datecs.printer.escpos
com.datecs.iserial.communication
com.datecs.printer.label.zpl
com.datecs.label.zpl
com.datecs.pinpad

Add privacy entries to Info.plist

Also in your project’s Info.plist file we need to add the four (4) privacy tags listed below. You can enter any string value you want or copy what we have below. Note: in Xcode 13.0+ this has been moved to the “Info” tab in your project’s settings.

"Privacy - Bluetooth Peripheral Usage Description"
"Privacy - Location Always Usage Description" 
"Privacy - Location When In Use Usage Description" 
"Privacy - Location Usage Description"

We also need to add the “NSBluetoothAlwaysUsageDescription”

"NSBluetoothAlwaysUsageDescription"

Finally we need to add the “Required Background Modes” array with three (3) string entries.

"Required background modes" : "App registers for location updates"
                              "App communicates with an accessory"
                              "App downloads content from the network"


Processing a payment

At this point, your Visual Studio project should be configured and ready to use the QuantumPay libraries. This next section will take you through some initial setup all the way to processing a payment.

Initialize the SDKs

The SDKs need to be initialized with the correct keys provided by Infinite Peripherals. This step is important and should be the first code to run before using other functions from the SDKs. Create tenant in FInishedLaunching function in AppDelegate.cs

// These keys are jsut examples and will not work. Please ask IPC to supply you with the correct keys
var hostKey = "US";
var tenant = "IPC";
var developerKey = "0000-0000-0000-0000-0000";

// Create tenant
QuantumPay.Client.Tenant tenant = new QuantumPay.Client.Tenant(hostKey, tenantKey);

// Initialize QuantumPay
InfinitePeripherals.Init(developerKey, tenant);

Create Payment Device

Now initialize a payment device that matches the hardware you are using. The current supported payment devices are: QPC150, QPC250, QPP400, QPP450, QPR250, QPR300. Note that this step is different for payment devices that are connected with Bluetooth LE.

Initialize QPC150, QPC250 (Lightning connector)

var paymentDevice = new Qpr250();

Initialize QPP400, QPP450, QPR250, QPR300 (Bluetooth LE) by supplying its serial number so the PaymentEngine can search for and connect to it. On first connection, the app will prompt you to pair the device. Be sure to press “OK” when the pop-up is shown. To complete the pairing, if using a QPR device, press the small button on top of the device opposite the power button. If using a QPP device, press the green check mark button on the bottom right of the keypad. // The device serial number is found on the label on the device.

var paymentDevice = new Qpr250("2320900026");

Create payment engine

The payment engine is the main object that you will interact with to send transactions and receive callbacks.

// 
var username = "test@testuser.com";
var password = "P@ssword";

var testPosId = "TestPosId"; // this should be a unique value for your device instance. The POS ID is a customer generated unique value for the instance of the app installation. It can be any string value of the customer's choice. It will be used to identify the app/device instance when processing transactions and is visible in the portal.

var paymentEngine = await PaymentEngine.Builder
                                       .AssignLocationsToTransactions() // optional - use precise tracking for assigning locations
                                       .RegistrationCredentials(username, password) // optional - only used to register the device, not required if the device is already registered with the server
                                       .PosId(testPosId) // required - the unique POS ID for your system
                                       .TransactionTimeout(TimeSpan.FromSeconds(30)) // optional - specify the duration that the peripheral will wait for the customer to complete the payment
                                       .AddPeripheral(paymentDevice, autoConnect: false) // required - add your peripheral
                                       .BuildAsync();

Setup handlers

Once the PaymentEngine is created, you can use it’s handlers to track the operation. The PaymentEngine handlers will get called throughout the payment process and will return you the current state of the transaction. You can set these handlers in the completion block of the previous step.

TransactionStateHandler will assign the delegate to use for handling transaction state changes.

TransactionResultHandler will assign the delegate to use for handling transaction results.

PeripheralStateHandler will get called when the state of the peripheral changes during the transaction process. The PeripheralState represents the current state of the peripheral as reported by the peripheral device itself. These include “idle”, “ready”, “contactCardInserted” etc.

PeripheralMessageHandler will get called when there is new message about the transaction throughout the process. The peripheral message tells you when to present the card, if the card read is successful or failed, etc. This usually indicates something that should be displayed in the user interface.

paymentEngine.SetTransactionStateHandler((peripheral, transaction, transactionState) =>
{
    var scanLabel = $"Transaction State = {transactionState}";

    if (transactionState == TransactionState.CardReadSuccess)
    {
        var dataLabel = transaction.Properties?.MaskedPan;
    }

    if (transactionState.IsFinalState())
    {
       // handle final state of transaction here
    }
});

paymentEngine.SetTransactionResultHandler((transactionResult) =>
{
    var dataLabel = $"{transactionResult.Status} {transactionResult.ServerResponse?.GatewayResult} {transactionResult.Reason}";
});

PaymentEngine.SetPeripheralStateHandler((peripheral, peripheralState) =>
{
    MainThread.BeginInvokeOnMainThread(() => { Console.WriteLine(peripheralState); });
});

PaymentEngine.SetPeripheralMessageHandler((peripheral, message) =>
{
    MainThread.BeginInvokeOnMainThread(() => { Console.WriteLine(message); });
});

Connect to payment device

Now that your payment engine is configured and your handlers are set up, lets connect to the payment device. Please make sure the device is attached and turned on. We need to connect to the payment device prior to starting a transaction. The connection state will be returned to the ConnectionStateHandler that we set up previously. If you didn’t set autoConnect when creating the payment engine, you will need to call Connect() before starting a transaction.

ConnectionStateHandler will get called when the connection state of the payment device changes between connecting, connected, and disconnected. It is important to make sure your device is connected before attempting to start a transaction.

// assign connection state and transaction state handlers
paymentEngine.SetConnectionStateHandler((peripheral, connectionState) =>
{
    switch (connectionState)
    {
        case ConnectionState.Disconnected:
            Console.WriteLine("Peripheral disconnected");
            // update your UI code here
            break;

        case ConnectionState.Connecting:
            Console.WriteLine("Peripheral connecting...");
            // update your UI code here
            break;

        case ConnectionState.Connected:
            Console.WriteLine("Peripheral connected");
            // update your UI code here
            break;
    }
});

  // connect to the peripheral - must be called before any further interaction with the peripheral
  paymentEngine.Connect();

Create an invoice

Time to create an invoice. This invoice object holds information about a purchase order and the items in the order.

var orderNum = 1;

var invoice = paymentEngine.BuildInvoice(orderNum.ToString())
                           .CompanyName("ACME SUPPLIES INC.")
                           .PurchaseOrderReference("PO1234")
                           .AddItem("SKU1", "Discount Voucher for Return Visit", 0M)
                           .AddItem(item => item.ProductCode("SKU2")
                              .Description("In Store Item")
                              .SaleCode(SaleCode.Sale)
                              .Price(amount)
                              .Quantity(4)
                              .UnitOfMeasure(UnitOfMeasure.Each)) // defines the association between quantity and price
                           .CalculateTotals()
                           .Build();
                 

Create a transaction

The transaction object holds information about the invoice, the total amount for the transaction and the type of the transaction (e.g.: sale, auth, refund, etc.)

var reference = "3423-2234-222";
var service = "TestService";
var amount = 5.00M; // $5.00 USD

var txn = paymentEngine.BuildTransaction(invoice)
                       .Sale() // other optons: refund(), auth(), capture(), void()
                       .Amount(amount, Currency.USD)
                       .Reference(reference) // required - unique transaction reference, such as your application order number
                       .Service(service) // optional - allow customer to control the merchant account that will process the transaction in business that have multiple services / legal entities
                       .MetaData(new Dictionary<string, string> { { "OrderNumber", orderNum.ToString() }, { "Delivered", "Y" } }) // optional - store data object to associate with the transaction
                       .Build();

Start transaction

Now that everything is ready we can start the transaction and take payment. Watch the handler messages and status updates to track the transaction throughout the process.

return await paymentEngine.StartTransactionAsync(txn);

Transaction receipt

Once the transaction is completed and approved, the receipt is sent to the TransactionResultHandler callback.

// The url for customer receipt
transactionResult.receipt?.customerReceiptUrl;

// The url for merchant receipt
transactionResult.receipt?.merchantReceiptUrl;

Disconnect payment device

Now that the transaction is complete you are free to disconnect the payment device if you wish. Please note that this should not be called before or during the transaction process.

paymentEngine.Disconnect()

Connect bluetooth peripheral to iOS device using the camera

It is possible to connect any of our bluetooth devices to your app using the camera to scan the peripheral barcode.

Add permisions to Info.plist

First you will need to allow the app to use the camera and also enable bluetooth, do this by adding the following code to the info.plist

<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app communicates with an external peripheral via Bluetooth to enable functionality such as reading cards and scanning barcodes.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app communicates with an external peripheral via Bluetooth to enable functionality such as reading cards and scanning barcodes.</string>
<key>NSCameraUsageDescription</key>
<string>Please allow the camera to be used for scanning barcodes</string>

Implement scanner page

The simplist way to achieve this is to use a package, for this example you can use the zxing scanner ‘https://www.nuget.org/packages/ZXing.Net.Mobile.Forms/’. You can however create your own scanner implementation if you so wish.

ScanPage.xaml

<zxing:ZXingScannerView x:Name="ScanView"
                        OnScanResult="Handle_OnScanResult" 
                        IsScanning="true" />
<zxing:ZXingDefaultOverlay/>

ScanPage.xaml.cs

public void Handle_OnScanResult(Result result)
{
    Device.BeginInvokeOnMainThread(async() =>
    {
         DeviceSerialNumber = result.Text;
    });
}

Scan product barcodes

Scanning a Barcode

Some of our payment devices also support barcode scanning (e.g., QPC150, QPC250). In order to receive the barcode data, you will need to set your class to conform to the protocol IPCDTDeviceDelegate, to do this set an instance of the IPCDTDeviceDelegateEvents:

// Gets the Infinite Peripherals object
private IPCDTDevices Peripheral { get; } = IPCDTDevices.Instance;

private IPCDTDeviceDelegateEvents PeripheralEvents { get; } = new IPCDTDeviceDelegateEvents();

PeripheralEvents.BarcodeNSDataType += OnBarcodeScanned;

// register the peripheral events delegate - must be set before connecting to the peripheral
Peripheral.AddDelegate(PeripheralEvents);

Next, implement the function that handles the scan and the dat returned

private void OnBarcodeScanned(object sender, BarcodeNSDataTypeEventArgs e)
{
    Console.WriteLine($"Barcode scanned: {e.Barcode} ({e.Type})");
    //update UI code here 
}