Chirp + PayPal

This aim of this tutorial is to create an iOS app that can make peer-to-peer payments to anyone nearby using PayPal and Chirp.

Chirp uses sound to transfer data over the air, which creates a seamless interaction between unpaired devices. Combining this with one of the largest payment providers in the world allows money to be transferred securely to anyone in your vicinity. The unique affordances of data-over-sound also mean that a payment request could be broadcast to many people simultaneously.

Since PayPal acquired Braintree in 2013, developers are now guided towards their SDKs to create transactions. Although the Braintree SDKs are great to use, they are more catered for merchant transactions and not peer-to-peer payments. However this code snippet from PayPal Engineering explains how P2P payments can be made using the PayPal REST API, and this will form the basis for this tutorial.

You can view the source code for this app here. To go straight to the API client code, go here. A getting started guide for Chirp with Swift can be found here.


A PayPal payment is completed in three steps.

  1. The requestor first creates a payment with the payee’s email address and the requested amount.
  2. This payment ID is sent to the payer who authorises the payment using PayPal’s OAuth web flow, generating a unique payer ID.
  3. The payer ID is then sent back to the requestor to execute the payment.

First of all though, we need to generate an access token to authorise our API requests. This requires a client ID and secret which can be generated from the PayPal developer console. This is just for authenticating API requests, the payment authorisation all happens in the web flow.

let clientId = "PAYPAL_CLIENT_ID"
let clientSecret = "PAYPAL_CLIENT_SECRET"
let apiClient = PayPalAPIClient()
apiClient.send(PayPalAuthenticate(clientId: clientId, clientSecret: clientSecret)) { response in
    switch response {
    case .success(let data):
        guard let data = data as? [String:Any] else {return}
        guard let accessToken = data["access_token"] as? String else {return}
    }
}

Now the requestor can use this access token to create a payment, and send the payment ID using Chirp.

apiClient.send(PayPalPayment(
    accessToken: accessToken,
    payeeEmail: "payee@email.com",
    amount: "10",
    currency: "GBP",
    returnUrl: "https://mysite.com/process",
    cancelUrl: "http://mysite.com/cancel",
    description: "P2P Payment"
)) { response in
    switch response {
    case .success(let data):
        guard let data = data as? [String:Any] else {return}
        guard let paymentId = data["id"] as? String else {return}
        guard let links = data["links"] as? [[String: String]] else {return}
        for link in links {
            if link["method"] == "REDIRECT" {
                guard let href = link["href"] else {return}
                let authUrl = URL(string: href)
                let paymentToken = (url?.queryParameters!["token"])!

                let appDelegate = UIApplication.shared.delegate as! AppDelegate
                if let connect = appDelegate.connect {
                    let payload = paymentToken.data(using: .utf8, allowLossyConversion: false)
                    connect.send(payload: payload!)
                }
            }
        }
    }
}

Once the payment has been created, the payment ID can be saved for later, as the requestor will also be executing the payment. Inside the response there is also a redirect URL that the payer should visit to authorise the payment.

Since most of this URL is constant, we just extract the token from the URL and send this to the payer to keep the transmission as short as possible. To send the entire URL with Chirp [standard protocol] would take roughly 10secs, whereas the token alone is more like 2secs.

After the payer has completed the payment authorisation, the web browser is redirected to the returnUrl that was specified in the initial payment request, that contains the generated Payer ID as a query parameter. This can be extracted from the URL and sent back to the requestor to execute.

apiClient.send(PayPalExecute(
    accessToken: accessToken,
    paymentId: paymentId,
    payerId: payerId
)) { response in
    switch response {
    case .success(let data):
        print("Payment successful")
    }
}

Like most apps, we need to create different view controllers for each view in our app, and these controllers will likely need to access one single instance of something, ie. Chirp Connect. Rather than creating a global Chirp instance it is considered best practice to create a service which interfaces the SDK, and delegate control of this to the designated view controller. To do this we need to create a protocol to manage the data flow in and out of the service.

protocol ChirpDelegate {
    func onReceived(data: Data?)
}

class ChirpService {
    var delegate: ChirpDelegate?
    var sdk: ChirpConnect?

    public init() {
        sdk = ChirpConnect(appKey: APP_KEY, andSecret: APP_SECRET)
        if let sdk = sdk {
            sdk.setLicenceString(LICENCE)
            sdk.start()

            sdk.receivedBlock = {
                (data: Data?) -> () in
                if let delegate = self.delegate {
                    delegate.onReceived(data: data)
                }
                return;
            }
        }
    }
}

This service is now simply instantiated once in the app delegate, and control is passed on to the relevant view controller when necessary.

So to send a payment ID to a receiving device, we can just use the connect instance directly from the app delegate, converting the string data to the required NSData type.

let appDelegate = UIApplication.shared.delegate as! AppDelegate
if let connect = appDelegate.connect {
    let payload = paymentId.data(using: .utf8, allowLossyConversion: false)
    connect.send(payload: payload!)
}

On the receiving side, we can delegate control of the service to the receive view controller to process the received payer ID.

class ReceiveController: UIViewController, ChirpDelegate {

    override func viewDidLoad() {
        if let connect = appDelegate.connect {
            connect.delegate = self
        }
    }
    func onReceived(data: Data?) {
        if let data = data {
            let payerId = String(data: data, encoding: .ascii)!
            print(String(format: "Received Payer ID: %@", payerId))
        } else {
            print("Decode failed");
        }
    }
}

Please note that these code snippets serve as reference material, for a full working example app head over to the Chirp + PayPal repository.


Chirp have now released a new pricing model which grants free usage to personal and commercial projects with less than 10k monthly active users. So to get started developing your own app with Chirp, sign up to download SDKs for iOS/Android/Python and more at the admin centre. Further documentation can be found at the developer portal.

Made something cool with Chirp SDKs? Tweet it to @chirp