Adapting an Objective-C API to Swift 3: Taming Wirecard’s PaymentSDK

Marin Benčević
COBE
Published in
7 min readSep 19, 2016

--

Swift and Objective-C differ in more than just syntax. As has been discussed (and discussed, and discussed…), they have fundamental differences in their philosophy.

While Objective-C is powerful and dynamic, Swift is safer, more explicit and prefers knowing things at compile time. Swift strives for immutability, with the use of more powerful structs and enums. Those enums also allow for clearer and a more concise API than is often the case in Objective-C.

Though I prefer Swift, I would not say one is objectively (pun not intended) better than the other. But the fact is they are very different.

Sometimes, however, we don’t have the opportunity to take full advantage of those differences. This is especially the case when using an Objective-C library in Swift. While calling Objective-C methods from Swift is easy, the API often doesn’t fit the look and feel of Swift. In a word — it’s not Swifty.

Recently we at COBE encountered this problem. We were working on our first production Swift project — mydays, a cool app that provides gift ideas for special people in your life. The app features a payment system, and we used Wirecard’s PaymentSDK for processing the payment.

While the SDK is fairly simple to use and flexible, it’s written in Objective-C and fits within the Objective-C world. As an example of what I mean, let’s take a look at what making a credit card payment looks like with PaymentSDK.

let amount = NSDecimalNumber(value: 13.99 as Float)

let payment = WDCardPayment(
amount: amount,
amountCurrency: .EUR,
transactionType: .authorization)!

First we need to create a WDCardPayment object. This object holds information about our payment, like an amount or currency. It also includes super secret keys that we fetch from the backend, like our merchant ID, signature, timestamp and request ID.

payment.requestID = "request-id"
payment.requestTimestamp = Date()
payment.requestSignature = "super-secret-string"
payment.merchantAccountID = "merchant-id"

Once we have our payment, we need to actually pay. We’ll have to instantiate a WDClient that handles the actual payment for us.

let client = WDClient(environment: .TEST)!

We provide it a completion block that will handle the response. As is common in Objective C world, the completion block takes a response and an error as parameters. Obviously, both of these can be nil since if an error occurs, there is no response, and vice-versa.

client.make(payment) { (response, error) in

guard error == nil else {
print("Error! \(error)")
return
}

guard let response = response else {
print("A really weird error!")
return
}

print("Payment completed: \(response)")
}

So what exactly is wrong with this? If we were living in Objective C world, I would say nothing. However, we write Swift, and we’re happiest when all of the API we use is a good Swift citizen.

First of all, in Swift we prefer to represent our data via immutable structs. They are thread safe and much easier to debug and think about. Thus, instead of using the WDCardPayment object, why not use a WDCardPaymentData struct?

struct WDCardPaymentData {let amount: NSDecimalNumber
let currency: WDCurrency
let type: WDTransactionType
}

Making a payment without a signature and the merchant ID doesn’t make sense. Since in Swift only optionals can be nil, we can make sure the users of our API don’t forget to set these properties by making them non-optional.

struct WDCardPaymentData {let amount: NSDecimalNumber
let currency: WDCurrency
let type: WDTransactionType
//These properties shouldn't be optional!
let requestID: String
let requestTimestamp: Date
let requestSignature: String
let merchantAccountID: String
}

We can then extend the WDCardPayment class to work with our struct.

extension WDCardPayment {

convenience init(_ data: WDCardPaymentData) {

self.init(
amount: data.amount,
amountCurrency: data.currency,
transactionType: data.type)!

self.requestID = data.requestID
self.requestTimestamp = data.requestTimestamp
self.requestSignature = data.requestSignature
self.merchantAccountID = data.merchantAccountID
}

}

Note that this is only a subset of all of the WDCardPayment’s properties. There are other optional properties that we can add to our struct as optionals.

I’m also guessing the consumers of our API would prefer to use a less complex type than NSDecimalNumber. While it’s very powerful, it’s too complex for our use. Instead, we’ll replacing the NSDecimalNumber with a Money struct.

Since we’re dealing with money, we need to be extra careful to avoid floating-point weirdness like having 19.99999 when we want $20.00. We can easily avoid this by using two integers (representing our whole part, and the number of cents) instead of a single Float or Double.

struct Money {

let whole: Int
let cents: Int
let currency: WDCurrency

init(whole: Int, cents: Int, currency: WDCurrency) {
self.whole = whole
self.cents = cents
self.currency = currency
}
}

And now our WDCardPaymentData struct works with Money instead of an NSDecimalNumber.

struct WDCardPaymentData {

let price: Money
let type: WDTransactionType

let requestID: String
let requestTimestamp: Date
let requestSignature: String
let merchantAccountID: String
}

PaymentSDK still expects an NSDecimalNumber, which is a smart object-oriented representation of floating point numbers. We can add a computed variable that will return an NSDecimalNumber from our Money struct.

extension Money {

var value: NSDecimalNumber {

let mantissa = UInt64(whole * 100 + cents)
let exponent = Int16(-2)

return NSDecimalNumber(
mantissa: mantissa,
exponent: exponent,
isNegative: false)
}
}

First we calculate the number of cents we want to pay by adding up the whole part and the cents. Then, we divide the number of cents by 100 to get a precise decimal number in Euro. E.g. if we wanted to pay $13.99, that means we want to pay 1399 cents, (the mantissa) which, when multiplied by 10^-2 (the exponent) gives us 13.99.

let price = Money(whole: 13, cents: 99, currency: .EUR)
print(price.value) //13.99

Note: Not all currencies have the smallest denomination at 1/100 of the standard denomination. Some don’t have a smaller denomination, and some have 1/1000 of the standard. But mydays is aimed for a German-speaking market and thus supports prices in Euro only.

We can now modify our convenience initialiser for WDCardPayment to accept our new Money struct.

extension WDCardPayment {

convenience init(_ data: WDCardPaymentData) {

self.init(
amount: data.price.value,
amountCurrency: data.price.currency,
transactionType: data.type)!
//...}}

Creating a WDCardPayment object is much easier now.

let price = Money(whole: 13, cents: 99, currency: .EUR)

let payment = WDCardPayment(WDCardPaymentData(
price: price,
type: .Authorization,
requestID: "request-id",
requestTimestamp: NSDate(),
requestSignature: "super-secret-string",
merchantAccountID: "merchant-id"))

Now that creating the payment is more swifty, let’s take a look at handling the response.

One of the idiomatic uses of Swift’s awesome enums with associated values is as a result enum. A result enum represents the result of some operation that can either be successful or fail somewhere along the way, exactly like our payment.

enum WDResult {
case success(WDPaymentResponse)
case failure(WDError)
}

The PaymentSDK includes some nice constants for the different kinds of errors that can occur, so that’s what we’ll use if we can get it. If, however, the WDClient isn’t sure about which error it got, we can just as well forward the error to the caller.

enum WDError {

case wirecard(WDErrorCode)
case unknown(NSError)

init(error: NSError) {

if let wirecardError = WDErrorCode(rawValue: error.code) {
self = .wirecard(wirecardError)
} else {
self = .unknown(error)
}

}

Now we can add onto our extension to WDClient and convert the completion block into one that takes our WDResult as an argument.

extension WDClient {

func make(_ payment: WDPayment, completion: @escaping (WDResult)-> Void) {

self.makePayment(payment) { (response, error) in

guard error == nil else {
let error = error as! NSErrorif let wirecardError = WDErrorCode(rawValue: error.code) {
completion(.failure(.wirecard(wirecardError)))
} else {
completion(.failure(.unknown(error)))
}
return
}

guard let response = response else {
completion(.failure(.wirecard(.General)))
return
}

completion(.success(response))
}
}

}

This way, we got rid of those pesky optionals and provided a clearer, more concise, and more swifty way of handling the payment response.

Let’s take a look at a before and after:

What we got by adhering to Swift’s API guidelines and using native Swift patterns:

  1. A clearer (and more concise) way to express the result of our payment via a result-enum
  2. A new initialiser for WDCardPayment that makes all the requirements clear so the users of the API won’t forget to set any required properties
  3. A simpler way to represent money, without floating-point number weirdness
  4. Less optionals
  5. A more descriptive way of error handling, where errors are converted to WDErrorCode (when possible) and can be inside a switch case

Extending older classes to fit within newer API guidelines is a good exercise in API design. It allows you to think and see the good and bad sides of both APIs, and let’s you see which one is better for you.

Marin is an iOS developer at COBE, a blogger at marinbenc.com, and a Computer Science student at FERIT, Osijek. He likes to program, learn about stuff and then write about them, ride bicycles and drink coffee. Mostly, though, he just causes SourceKit crashes. He has a chubby cat called Amigo. He totally didn’t write this bio himself.

--

--