VipasoPay
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 some configuration parameters (also Context
on Android) to update the base path and the BLE settings. You can also pass a VipasoPayDelegate
(iOS), VipasoPayListener
(Android), instance, that will receive notifications when the authentication state changes.
iOS
VipasoPay(
config: VipasoConfiguration(
paymentBasePath: Config.basePath,
bleServiceUUID: Config.bleServiceUUID,
bleStatusUUID: Config.bleStatusUUID,
bleWriteUUID: Config.bleWriteUUID
),
delegate: delegate
)
Android
data class VipasoConfiguration(
val paymentBasePath: String,
val bleServiceUuid: String,
val bleStatusUuid: String,
val bleWriteUuid: String,
val bleReadUUID: String
)
val vipasoConfiguration = VipasoConfiguration(
paymentBasePath = "https://your.base.path",
bleServiceUuid = "your service UUID",
bleWriteUuid = "your Bluetooth Write UUID",
bleStatusUuid = "your Bluetooth Status UUID",
bleReadUUID = "your Bluetooth Read UUID"
)
)
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, as follow :
// Instance Creation
val vipasoPay : VipasoPay = VipasoPayImpl(context = context, vipasoConfiguration = vipasoConfiguration, listener = listener)
// Instance Creation using Dependency Injection Dagger Hilt
@Module
@InstallIn(SingletonComponent::class)
class VipasoPayModule {
@Provides
@Singleton
fun provideVipasoPay(
@ApplicationContext context: Context,
configuration: VipasoConfiguration,
listener: VipasoPayListener?
): VipasoPay {
return VipasoPayImpl(configuration, context, listener = listener)
}
@Provides
fun provideVipasoConfig(@ApplicationContext context: Context): VipasoConfiguration {
return VipasoConfiguration(
paymentBasePath = "https://your.base.path",
bleServiceUuid = "your service UUID",
bleWriteUuid = "your Bluetooth Write UUID",
bleStatusUuid = "your Bluetooth Status UUID",
bleReadUUID = "your Bluetooth Read UUID"
)
}
}
Observing SDK events
By implementing the VipasoPayListener
(Android) or VipasoPayDelegate
(iOS) interface and passing it to the SDK, the client-side developer can subscribe to authentication state change events. You can pass the listener/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.
iOS example
extension RootCoordinator: VipasoPayDelegate {
func onAuthenticationStateChange(vipaso: VipasoPayProtocol, authenticated: Bool) {
if authenticated {
displayMainCoordinator()
} else {
displayAuthenticationCoordinator()
}
}
func onEventTracked(name: String, parameters: [String: String]?) {
// analytics calls
}
}
Android example
override fun onAuthenticationStateChange(authenticated: Boolean) {
isAuthenticated.value = authenticated
}
Authentication
Signup
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.
iOS
let request = StartSignupRequest(phoneNumber: phoneNumber)
vipasoPay.user.startSignupFlow(request: request) { result in
switch result {
case .success(let response):
print("success: \(response.flowID)")
case .failure(let error):
print("error: \(error.localizedDescription)")
}
}
Android
val phoneNumber : String = "+4312345678"
val request = StartSignupRequest(phoneNumber)
val response = vipasoPay.startSignupFlow(request)
val data = response.data
if (data != null) {
print(data.flowId)
} else {
print(response.error?.message)
}
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.
iOS
let request = VerifyPhoneNumberRequest(flowID: flowID, otp: otp)
vipasoPay.user.verifyPhoneNumber(request: request) { result in
switch result {
case .success(let response):
print("success: \(response.flowID)")
case .failure(let error):
print("error: \(error.localizedDescription)")
}
}
Android
val flowId : String = "flow Id"
val otp : String = "otp code"
val request = VerifyPhoneNumberRequest(flowId, otp)
val response = vipasoPay.verifyPhoneNumber(request)
val data = response.data
if (data != null) {
print(data.flowId)
} else {
print(response.error?.message)
}
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.
iOS
let request = FinishSignupRequest(
flowID: flowID,
password: password,
email: email,
firstName: firstName,
lastName: lastName,
phoneNumber: phoneNumber
)
vipasoPay.user.finishSignupFlow(request: request) { result in
switch result {
case .success(let response):
print("success: \(response.flowID)")
case .failure(let error):
print("error: \(error.localizedDescription)")
}
}
Android
val flowId : String = "flowId"
val password : String = "password"
val email : String = "[email protected]"
val name : String = "John Doe"
val phoneNumber : String = "+431234567"
val request = FinishSignupRequest(flowId, password, email, name, phoneNumber)
val response = vipasoPay.finishSignupFlow(request)
val data = response.data
if (data != null) {
print(data.flowId)
} else {
print(response.error?.message)
}
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.
iOS
let request = LoginRequest(identifier: phoneNumber, password: password)
vipasoPay.user.login(request: request) { result in
switch result {
case .success:
print("success")
case .failure(let error):
print("error: \(error.localizedDescription)")
}
}
Android
val phoneNumber : String = "+431234567"
val password : String = "password"
val request = LoginRequest(phoneNumber, password)
val response = vipasoPay.login(request)
if (response.data != null) {
print("success")
} else {
print(response.error?.message)
}
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:
- Use
startRecoveryFlow
with a phone number to receive a flow id first.
var phoneNumber : String = "+431234567"
let request = StartRecoveryRequest(phoneNumber: phoneNumber)
vipasoPay.user.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)")
}
}
- This will send an OTP code to the phone number you set. Send the code and the flow id with
sendRecoveryOTP
to the backend.
let request = RecoveryOTPRequest(
code: code, // the received OTP code
flowID: flowID // the flowID from the previous step
)
vipasoPay.user.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)")
}
}
- 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.
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.user.finishRecoveryFlow(request: request) { result in
switch result {
case .success(let response):
print("success")
case .failure(let error):
print("error: \(error.localizedDescription)")
}
}
User data
Fetching user info
To get all info about the customer, use fetchUser
.
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.
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?
}
Instruments
List instruments
Listing your instruments is a simple fetch call without any parameters.
iOS
vipasoPay.instrument.fetchInstruments { result in
switch result {
case .success(let response):
print("success: \(response.instruments)")
case .failure(let error):
print("error: \(error.localizedDescription)")
}
}
Android
val response = vipasoPay.fetchInstruments()
if (response.data != null) {
print (response.data?.instrumentList)
} else {
print(response.error?.message)
}
Onboarding a new Card Instrument
iOS
This creates a card instrument which results in a CreateCardInstrumentResponse
object
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:
public struct CreateCardInstrumentResponse {
public let id: String
public let status: VipasoInstrumentStatus?
}
Android
Onboarding a new MNO instrument
You can create a mobile money instrument with the following api.
iOS
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)))
}
}
Android
val request = CreateMnoInstrumentRequest(
phone = phoneNumber,
customerName = name,
deviceName = deviceName,
lang = VipasoLang.from(Locale.getDefault())
)
val response = vipasoWrapper.vipasoPay?.createMnoInstrument(request = request)
return if (response?.data != null) {
val id = response.let { it.data?.id }
VipasoResponseWrapper(vipasoResponse = VipasoResponse((id)))
} else {
VipasoResponseWrapper(VipasoResponse(error = response?.error))
}
Creating an instrument with a third-party id
You can create an instrument with an existing third-party id from your own system.
iOS
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
}
}
Android
vipasoPay.createThirdPartyInstrument(
CreateThirdPartyInstrumentRequest(
thirdPartyId = "<instrument-id>",
instrumentType = VipasoInstrumentType.MNO,
lang = VipasoLang.EN
)
)
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
.
iOS
let request = WithdrawalRequest(
instrumentID: instrumentID,
amount: amount,
currency: currency,
phone: phoneNumber
)
vipasoPay.withdrawal(request: request) { result in
switch result {
case .success(let response):
promise(.success(response))
case .failure(let error):
promise(.failure(.generic(error: error)))
}
}
Android
Top up
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.
iOS
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)))
}
}
Android
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.
Be advised that the offline mode is an EXPERIMENTAL functionality as of this moment.
iOS
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)))
}
}
Android
Payments
List transactions
Listing your payment transaction history is a simple fetch call without any parameters.
iOS
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)")
}
}
Android
val response = vipasoPay.fetchPayments(page: 3, status: VipasoPaymentPagaStatus.COMPLETED)
if (response.data != null) {
print(response.data?.payments)
} else {
print(response.error?.message)
}
Listen for BLE payments
To receive payments and payment events from Vipaso PoS Terminals the Pay app has to listen for payment events.
iOS
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
}
}
On iOS the possible payment events are:
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
}
Android
val response = vipasoPay.startBLE()
if (response.data != null) {
print(response)
//inform payment that payment was accepted/rejected
response.data.onComplete(true)
} else {
print(response.error?.message)
}
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
iOS
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)))
}
}
Android
val request = ExecutePaymentRequest(
paymentID: "paymentId",
instrumentID: "instrumentID",
lang: "EN",
confirmationTimestamp: "timeStamp now"
)
val response = vipasoPay.executePayment(request)
if (response.data != null) {
print(response.payment)
} else {
print(response.error?.message)
}
Notifying PoS about the accepted payment
vipasoPay.sendBLEResponse(paymentID, true)
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.
iOS
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)))
}
}
Android
Fetching payment details
Fetching the details of a payment is a simple fetch call, you only need to set the payment id.
iOS
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)")
}
}
Android
val paymentId : String = "paymentId"
val request = FetchPaymentDetailsRequest(paymentId)
val response = vipasoPay.fetchPaymentDetails(request)
return if (response.data != null) {
print(response.payment)
} else {
print(response.error?.message)
}
Stopping Offline payment BLE
Disconnects the offline BLE stack.
iOS
vipasoPay.stopBLE()
Android
Sync Offline Payments !!! EXPERIMENTAL !!!
Synchronizes the Offline Payments stored in the mobile app.
iOS
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.
Android
Feature flags
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.
iOS
vipasoPay.user.fetchFeatureFlags { result in
switch result {
case .success(let response):
promise(.success(response.featureFlags))
case .failure(let error):
promise(.failure(.generic(error: error)))
}
}
Android
suspend fun fetchFeatureFlags(): VipasoResponse<FetchFeatureFlagsResponse>
Updated 3 days ago