Skip to content

iOS Pay

This object exposes APIs to interact with the Pay side. This includes authentication, instrument handling and executing and listing payments.

Setup

The recommended way is to have a singleton instance of VipasoPay in your code and pass an observer (delegate/listener) instance that will receive all authentication state change events.

Configuration

When you create the instance, you can pass a VipasoPayDelegate (iOS), instance, that will receive notifications when the authentication state changes.

swift
VipasoPay(
    config: VipasoConfiguration(
        paymentBasePath: Config.basePath,
        bleServiceUUID: Config.bleServiceUUID
    ),
    delegate: delegate
)

Once the parameters are ready, you can create VipasoPay instance anywhere. You can also use Dependency Injection to handle the instance creation and can be injected anywhere in your project.

Observing SDK events

By implementing theVipasoPayDelegate interface and passing it to the SDK, the client-side developer can subscribe to authentication state change events. You can pass the delegate at init. Setting it automatically fires an event with the latest authentication state of the user. As the user signs up, logs in or out, an event will arrive and the app can be updated accordingly.

swift
extension RootCoordinator: VipasoPayDelegate {
    func onAuthenticationStateChange(vipaso: VipasoPayProtocol, authenticated: Bool) {
        if authenticated {
            displayMainCoordinator()
        } else {
            displayAuthenticationCoordinator()
        }
    }

    func onEventTracked(name: String, parameters: [String: String]?) {
        // analytics calls
    }
}

Managed Authentication

Signup

In the managed authentication flow the Vipaso SDK provides an out of the box authentication procedure. This means that there is no need on the client side to maintain the API side user administration.

The flow has three steps, each is an actual networking call.

Creating a new registration flow

First, you need to create a new registration flow with a phone number, this will send an OTP code to the phone number.

swift
let request = StartSignupRequest(phoneNumber: phoneNumber)
vipasoPay.managedAuth.startSignupFlow(request: request) { result in
    switch result {
    case .success(let response):
        print("success: \(response.flowID)")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}

Verify phone number

Next, you need to verify the phone number by sending the flow id and the OTP code you received to the backend.

swift
let request = VerifyPhoneNumberRequest(flowID: flowID, otp: otp)
vipasoPay.managedAuth.verifyPhoneNumber(request: request) { result in
    switch result {
    case .success(let response):
        print("success: \(response.flowID)")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}

Completing signup flow

Lastly, you need to finish the signup request by sending all the user data you collected through the flow. The SDK will store your session token securely and the observer will be notified.

swift
let request = FinishSignupRequest(
    flowID: flowID,
    password: password,
    email: email,
    firstName: firstName,
    lastName: lastName,
    phoneNumber: phoneNumber
)
vipasoPay.managedAuth.finishSignupFlow(request: request) { result in
    switch result {
    case .success(let response):
        print("success: \(response.flowID)")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}

Login

Once you have an account, logging in is very straightforward. You pass the phone number as identifier with a password, the SDK will store your session token securely and the observer will be notified.

swift
let request = LoginRequest(identifier: phoneNumber, password: password)
vipasoPay.managedAuth.login(request: request) { result in
    switch result {
    case .success:
        print("success")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}

Logging out

Simply calling the exposed logout function, the SDK will remove the stored session token and the observer will be notified.

PIN Recovery

Recovering the PIN has three steps:

  1. Use startRecoveryFlow with a phone number to receive a flow id first.
swift
let request = StartRecoveryRequest(phoneNumber: "+431234567")
vipasoPay.managedAuth.startRecoveryFlow(request: request) { result in
    switch result {
    case .success(let response):
      	print("success")
      	// save flow id from response.flowID for the upcoming step
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}
  1. This will send an OTP code to the phone number you set. Send the code and the flow id with sendRecoveryOTP to the backend.
swift
let request = RecoveryOTPRequest(
    code: code, // the received OTP code
    flowID: flowID // the flowID from the previous step
)

vipasoPay.managedAuth.sendRecoveryOTP(request: request) { result in
    switch result {
    case .success(let response):
        print("success")
        // save the new settingsFlowID from response.settingsFlowID for the upcoming step
        // save the new sessionToken from response.sessionToken for the upcoming step
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}
  1. With the settings flow id and the session token and the new PIN the user selected, you can update the PIN on the backend with finishRecoveryFlow and with that you are done.
swift
let request = FinishRecoveryRequest(
    settingsFlowID: settingsFlowID, // the settingsFlowID from the previous step (IMPORTANT: not the one from the first step)
    sessionToken: sessionToken, // the sessionToken from the previous step
    password: password // the new password entered by the user
)

vipasoPay.managedAuth.finishRecoveryFlow(request: request) { result in
    switch result {
    case .success(let response):
        print("success")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}

Delegated Authentication

With delegated authentication Vipaso provides the opportunity to authenticate a self hosted user management with the Vipaso SDK. This means that Vipaso does not provide user management but allows your user management to be integrated with the SDK.

Establishing authentication

With this method Vipaso supports authenticating users that are not managed by Vipaso. The host app is expected to call establishAuthentication on each app start, after login.

NOTE: This method is using async/await in order as the first step to modernize our classic closure based SDK.

swift
try await vipasoPay.delegatedAuth.establishAuthentication(
  userIdentifier: userIDString,
  connect: { [weak self] jwk in
  	// Use received JWK here to return encrypted init token
	}
)

User data

Fetching user info

To get all info about the customer, use fetchUser.

swift
vipasoPay.user.fetchUser { result in
    switch result {
    case .success(let response):
        print("success")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}

This will return a user object with the following data.

swift
public struct VipasoUser {
    public let id: String?
    public let phone: String?
    public let name: String?
    public let firstName: String?
    public let lastName: String?
    public let email: String?
}

Fetching feature flags

We support adding custom feature flags so you can remote control what your users have access to in their apps. The SDK fetches them under the hood and exposes an API to read them on the client-side. The API prefers using cached flags from the database.

coffeescript
vipasoPay.user.fetchFeatureFlags { [weak self] result in
    guard let self = self else { return }
    switch result {
    case .success(let response):
        print("success: \(response.featureFlags)")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}

Instruments

Fetch instrument

You can fetch a single instrument with the following API.

swift
let request = FetchInstrumentRequest(id: UUID())
vipasoPay.instrument.fetchInstrument(request: request) { [weak self] result in
    guard let self = self else { return }
    switch result {
    case .success(let response):
        print("success: \(response.instrument)")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}

List all instruments

Listing your instruments is a simple fetch call without any parameters.

swift
vipasoPay.instrument.fetchInstruments { result in
    switch result {
    case .success(let response):
        print("success: \(response.instruments)")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}

Remove an instrument

You can remove a single instrument with the following API.

swift
let request = RemoveInstrumentRequest(id: UUID())
vipasoPay.instrument.removeInstrument(request: request) { [weak self] result in
    guard let self = self else { return }
    switch result {
    case .success:
        print("success:")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}

Onboarding a new Card Instrument

This creates a card instrument which results in a CreateCardInstrumentResponseobject

swift
let request = CreateCardInstrumentRequest(
    lang: .en,
    customerName: customerName,
    creditCardNumber: creditCardNumber,
    expiryMonth: expiryMonth,
    expiryYear: expiryYear,
    cvv: cvv,
    nameOnCard: customerName
)

vipasoPay.instrument.createCardInstrument(request: request) { result in
    switch result {
    case .success(let response):
        print("success")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}

Result:

Text
public struct CreateCardInstrumentResponse {
    public let id: String
    public let status: VipasoInstrumentStatus?
}

Onboarding a new MNO instrument

You can create a mobile money instrument with the following API.

Text
let request = CreateMNOInstrumentRequest(
    phone: phoneNumber,
    customerName: name,
    deviceName: deviceName,
    lang: .en
)

vipasoPay.createMNOInstrument(request: request) { result in
    switch result {
    case .success(let response):
        promise(.success(response.id))
    case .failure(let error):
        promise(.failure(.generic(error: error)))
    }
}

Creating an instrument with a third-party id

You can create an instrument with an existing third-party id from your own system.

coffeescript
let request = CreateThirdPartyInstrumentRequest(
    thirdPartyID: "<instrument-id>",
    instrumentType: .card,
    lang: .en
)
vipasoPay.instrument.createThirdPartyInstrument { result in
    switch result {
    case .success(let response):
        // handle success
    case .failure(let error):
        // handle error
    }
}

Withdrawal

Withdrawal allows the funds to be transferred to a supplied phone number. The response contains an id and a status. The possible values of the status areinitiated, inProgress, completed, failed, cancelled.

swift
let request = WithdrawalRequest(
    instrumentID: instrumentID,
    amount: amount,
    currency: currency,
    phone: phoneNumber
)
vipasoPay.instrument.withdrawFromInstrument(request: request) { result in
    switch result {
    case .success(let response):
        promise(.success(response))
    case .failure(let error):
        promise(.failure(.generic(error: error)))
    }
}

Top-up an instrument

Top-up allows the funds to be transferred from a mobile phone number to an instrument. This results in a response that contains an URL which points to the web flow start page for the top-up.

swift
let request = TopUpRequest(
    instrumentID: instrumentID,
    amount: amount,
    currency: currency
)
vipasoPay.instrument.topUpInstrument(request: request) { result in
    switch result {
    case .success(let response):
        promise(.success(response.webFlowURL))
    case .failure(let error):
        promise(.failure(.generic(error: error)))
    }
}

Pre-authorize amount for an instrument - !!! EXPERIMENTAL !!!

Certain instruments can be pre-authorized with an amount of money to enable the users to pay in offline mode. Please note that this feature is currently experimental.

swift
let request = PreAuthorizationRequest(
    instrumentID: instrumentID,
    amount: amount,
    currency: currency
)

vipasoPay.instrument.preAuthorizeInstrument(request: request) { result in
    switch result {
    case .success(let response):
        promise(.success(response.authorization))
    case .failure(let error):
        promise(.failure(.generic(error: error)))
    }
}

Clearing expired pre-authorizations - !!! EXPERIMENTAL !!!

The following API clears expired pre-authorizations from the instruments. Please note that this feature is currently experimental.

Text
do {
    try vipasoPay.instrument.clearExpiredPreAuthorizations()
} catch {
    print("error: \(error.localizedDescription)")
}

Payments

List all payments

You can list all your payment history by the following fetch call. You can specify a page and a status filter.

swift
vipasoPay.payment.fetchPayments(page: 3, status: .completed) { result in
    switch result {
    case .success(let response):
        print("success: \(response.payments)")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}

Listen for BLE payments

To receive payments and payment events from Vipaso POS Terminals the Pay app has to listen for payment events.

swift
vipasoPay.payment.listenForBLEPayments { paymentEvent in
    switch paymentEvent {
    case .receivedPayment:
        // send payment data for user to accept
    case .finishedPayment, .error, .cancelled, .otherPaid, .disconnected:
        // handle status change
    }
}

The possible payment events are:

Text
public enum VipasoPaymentEvent: Equatable {
    case receivedPayment(VipasoPaymentRequest) // an incoming payment request to display for the user
    case finishedPayment(VipasoOfflinePaymentResult) // the payment has been successfully finished
    case error(VipasoError) // an error occured during the payment
    case cancelled // the payment has been cancelled
    case otherPaid // another wallet has paid the currently presented payment
    case disconnected // disconnected
}

Accepting a payment

When an incoming payment request arrives to the BLE receiver, users can decide if they accept it. In case the user accepts the payment the following method sends the acceptance to the PoS Terminal.

Responding to an incoming payment request

swift
let paymentUserResponse = VipasoPayPaymentResponse(
    paymentID: paymentID,
    amount: amount,
    tip: tip,
    currency: currency,
    instrumentID: instrumentID,
    createdAt: Date().iso8601String
)
vipasoPay.payment.acceptPayment(response: paymentUserResponse) { result in
    switch result {
    case .success(let response):
        promise(.success(response))
    case .failure(let error):
        promise(.failure(.generic(error: error)))
    }
}

Cancelling a payment

In case the user doesn't accept a payment the following method has to be called to notify the POS Terminal that the payment is not accepted.

swift
vipasoPay.payment.cancelPayment(paymentID: paymentID) { result in
    switch result {
    case .success(let response):
        promise(.success(response))
    case .failure(let error):
        promise(.failure(.generic(error: error)))
    }
}

Fetching payment details

Fetching the details of a payment is a simple fetch call, you only need to set the payment id.

swift
let request = FetchPaymentDetailsRequest(paymentID: paymentID)
vipasoPay.fetchPaymentDetails(request: request) { result in
    switch result {
    case .success(let response):
        print("success: \(response.payment)")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}

Stopping offline payment BLE

Disconnects the offline BLE stack.

swift
vipasoPay.stopBLE()

Sync offline payments - !!! EXPERIMENTAL !!!

Synchronizes the offline payments stored in the mobile app. Please note that this feature is currently experimental.

swift
vipasoPay.payment.syncPayments { result in
    switch result {
    case .success(let response):
        print("success: \(response)")
    case .failure(let error):
        print("completion success/error")
    }
}

NOTE: Should be called when the device comes online. The iOS SDK does not do synchronization unless this method is called.

Fetch offline payments - !!! EXPERIMENTAL !!!

You can fetch all offline payments that will be synchronized by syncPayments using the following api. Please note that this feature is currently experimental.

Text
let offlinePayments = vipasoPay.payment.fetchOfflinePayments()
print("offline payments: \(offlinePayments)")