Stripe Data Types in Swift
<!DOCTYPE html> Real World Example: Stripe Data Types in Swift

Real World Example: Stripe Data Types in Swift

Let’s practice using Swift to create data models for a real-world API.

One of the most important places to have correct code in your application is around areas of payment processing.

We can use our new-found knowledge of implementing custom types with Swift containers to model the Stripe API in Swift as safely as possible.

If you’re not familiar with Stripe, their product is an online payment API used by developers to easily accept credit cards online and create online peer-to-peer payment marketplaces.

The Stripe API

Let’s dive in to everybody’s favorite Stripe API object: charge.

The Charge Object

The charge object looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
  "id": "ch_104BZB2eZvKYlo2CdBjAUHDY",
  "object": "charge",
  "created": 1401724800,
  "livemode": false,
  "paid": true,
  "amount": 500,
  "currency": "usd",
  "refunded": false,
  "card": {
    "id": "card_104BZB2eZvKYlo2CRnmgsNrD",
    "object": "card",
    "last4": "4242",
    "type": "Visa",
    "exp_month": 1,
    "exp_year": 2050,
    "fingerprint": "Xt5EWLLDS7FJjR1c",
    "country": "US",
    "name": null,
    "address_line1": null,
    "address_line2": null,
    "address_city": null,
    "address_state": null,
    "address_zip": null,
    "address_country": null,
    "cvc_check": "pass",
    "address_line1_check": null,
    "address_zip_check": null,
    "customer": null
  },
  "captured": true,
  "refunds": [],
  "balance_transaction": "txn_10491X2eZvKYlo2C78fHq3sa",
  "failure_message": null,
  "failure_code": null,
  "amount_refunded": 0,
  "customer": null,
  "invoice": null,
  "description": null,
  "dispute": null,
  "metadata": {},
  "statement_description": null
}

Notice the charge object has an embedded card object and the embedded card object is using a super credit card with expiration date 34 years in the future. The charge object gives amount in units of cents for currency and includes various identifiers for referencing these objects in future Stripe API calls.

Also notice many fields are null which means we can use Swift optionals to represent the potential absence of values for those fields.

Every Stripe object has a unique object id and the type of the object is conveniently embedded as a prefix of the string object id. For example, the field card.id is prefixed with “card” and the field id for the entire card object is prefixed with “ch” letting us know the id refers to a charge object.

Let’s validate every id prefix, along with the very blatant object field, to make sure we’re reading the correct kinds of objects.

We’ll start by defining the types needed to create both the inner card object as well as the primary charge object.

Modeling the Charge Object

We see the card object has these types of things inside of it:

  • id for the charge itself
  • object name
  • created epoch timestamp
  • booleans about if this charge was test vs. live, whether the payment is complete, if this charge wasn’t a charge at all but rather a refund, and if the charge has gone through payment processing yet
  • an amount in cents in currency
  • three letter lower case ISO currency abbreviation
  • embedded card object with
    • credit card required details
    • credit card optional details (address informations mostly)
    • validity of credit card
    • many null’d out elements
  • list of potential partial refunds for this charge
  • …and a few more things.

Let’s be as specific as possible when creating our Swift-based StripeCard type and create all types the StripeCard type will reference.

Modeling IDs

First up, the charge id. We see the charge id is prefixed with ch, so we can create a custom StripeChargeId type to only store properly formatted charge ids.

1
2
3
4
5
6
7
8
9
10
struct StripeChargeId {
    let id: String
    init(id: String) {
        if id.hasPrefix("ch_") {
            self.id = id
        } else {
            self.id = ""
        }
    }
}

When we create a StripeChargeId, the value will either be a properly prefixed id or the empty string. Using an empty string for invalid input is a design decision and we could just as easily have defined id as String? to allow id to be nil. For now, let’s just populate using either a known good value or a known empty value.

We have a few different id-type objects to create though. Let’s make a Swift protocol to ensure all our types respond to the same requests for identification.

1
2
3
protocol StripeId {
    var id: String { get }
}

The StripeId protocol just says: anything implementing StripeId must have a property named id we can read.

Now let’s update StripeChargeId to use our new protocol:

1
2
3
4
5
6
7
8
9
10
struct StripeChargeId: StripeId {
    let id: String
    init(id: String) {
        if id.hasPrefix("ch_") {
            self.id = id
        } else {
            self.id = ""
        }
    }
}

That’s all it takes. Protocols look like type information or parent types of classes, but Swift knows what to do with each type of type in the type field.

Moving on, we have more id types to create:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct StripeCardId: StripeId {
    let id: String
    init(id: String) {
        if id.hasPrefix("card_") {
            self.id = id
        } else {
            self.id = ""
        }
    }
}

struct StripeBalanceTransactionId: StripeId {
    let id: String
    init(id: String) {
        if id.hasPrefix("txn_") {
            self.id = id
        } else {
            self.id = ""
        }
    }
}

This is starting to look a little too copy/pasty. All we’re changing in the structs are the type names and the prefix conditions.

Refactoring IDs

Can we centralize some of our assignment functions? Structs can’t inherit functionality, but classes can. Let’s use some inheritance!

First, let’s create a class to represent the concept of a Stripe id :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class StripeIdentifier: StripeId {
    let id: String
    init(prefixes: Array<String>, id: String) {
        /* Check if id starts with any prefix in prefixes */
        let matched = prefixes.filter { id.hasPrefix($0) }
        
        if !matched.isEmpty {
            /* If a prefix matched,
             * (meaning our result set isn't empty), set our id */
            self.id = id
        } else {
            /* If our prefix search failed, set empty id */
            self.id = ""
        }
    }
}

This generic StripeIdentifier class is more verbose than our previous individual id structs, but it still conforms to protocol StripeId.

Now we can implement individual id types by simply:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class StripeChargeId: StripeIdentifier {
    init(id: String) {
        super.init(prefixes: ["ch_"], id: id)
    }
}

class StripeCardId: StripeIdentifier {
    init(id: String) {
        super.init(prefixes: ["card_"], id: id)
    }
}

class StripeBalanceTransactionId: StripeIdentifier {
    init(id: String) {
        super.init(prefixes: ["txn_"], id: id)
    }
}

When instantiating a type, we pass in a list of valid prefixes for the type. If id doesn’t match any of our acceptable prefixes, we default id to the empty string.

Now let’s make even more types!

What’s left in the charge object? We have a timestamp, some booleans, some money values, a currency, an embedded card object, and a bunch of null values referencing other ids or types we didn’t get returned.

Let’s start with the timestamp:

1
2
3
struct StripeTimestamp {
    let epoch: UInt64
}

Simple enough. Since the timestamp isn’t an identifier, we’re not asking StripeTimestamp to conform to protocol StripeId.

Modeling Booleans

Next up, some raw booleans expanded to more meaningful types:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum StripeMode {
    case Live, Test
}

enum StripePaid {
    case Yes, No
}

enum StripeRefunded {
    case Yes, No
}

enum StripeCardCaptured {
    case Yes, No
}

How do we convert the card object’s livemode value of false into a value of StripeMode.Test?

We can create a custom initializer for the enum to convert the Stripe-provided boolean into our proper type:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum StripeMode {
    case Live, Test

    init(livemode: Bool) {
        if livemode {
            self = .Live
        } else {
            self = .Test
        }
    }
}

var mode = StripeMode(livemode: true)
mode == .Live  /* true */

Now, on to creating an initializer for StripePaid too:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

enum StripePaid {
    case Yes, No
    
    init(paid: Bool) {
        if paid {
            self = .Yes
        } else {
            self = .No
        }
    }
}

StripePaid(paid: true) == .Yes

Let’s finish these fields out by adding initializers to StripeRefunded and StripeCardCaptured:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
enum StripeRefunded {
    case Yes, No
    
    init(refunded: Bool) {
        if refunded {
            self = .Yes
        } else {
            self = .No
        }
    }
}

enum StripeCardCaptured {
    case Yes, No
    
    init(captured: Bool) {
        if captured {
            self = .Yes
        } else {
            self = .No
        }
    }
}

StripeRefunded(refunded: false) == .No
StripeCardCaptured(captured: true) == .Yes

It does look a little copy/pasty, but that’s okay for now. We have a limited number of these very specific boolean types.

Let’s move on to money values.

Modeling Money

Money, money, money. Money is better when it’s statically typed.

Stripe gives us amount as an integer number of cents. We can do better than a raw, typeless, number.

Let’s start by defining a protocol for Stripe money types:

1
2
3
protocol StripeMoney {
    var cents: Int { get }
}

The protocol just says any StripeMoney will have a cents property we can read.

Stripe has two kinds of money values:

  • prices of items you sell, which can range between 50 cents and $999,999.99
  • money amounts returned from Stripe representing either money in your account or some other Stripe-created money value with no limits

For price amounts, we want to restrict the range to between [50, 99999999] cents. For Stripe-given money amounts, we want to accept any non-negative integer.

Let’s define our Stripe money types as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct StripeAmount: StripeMoney {
    let cents: Int
    init(cents: Int) {
        if cents >= 0 {
            self.cents = cents
        }
        else {
            self.cents = 0
        }
    }
}

struct StripePrice: StripeMoney {
    let cents: Int
    init(cents: Int) {
        if cents >= 50 && cents <= 99999999 {
            self.cents = cents
        } else {
            self.cents = 0
        }
    }
}

Now we are guaranteed a StripePrice will never be invalid (though, we are setting to 0 upon invalid input; this would cause an error on the Stripe API, but a known, non-dangerous error). We are also guaranteed we will never have negative StripeAmount values to surprise our accounting math.

Implementation note: Stripe does support some currencies without any concept of “cents” (i.e. they don’t have fractional units of currency). For those currencies, you use whole units instead of fractional pieces.

Exercise: How would you fit a currency not having cents into our StripeMoney data model?

Modeling Worldwide Currencies

The sample card object we’re working from has a currency value of “usd”.

What other currencies are available? It turns out Stripe currently supports 139 currencies.

If you’re thinking ahead, yes, we’re going to implement a 139 property enum.

I’m not going to paste the entire implementation here since it’s close to 300 lines1, but here’s a taste of how our currency enum could work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct CurrencyDesc {
    let name: String
    let currency: StripeCurrency
}

enum StripeCurrency: String {
    case AED = "aed"
    case AFN = "afn"
    case ALL = "all"
    case AMD = "amd"
    case ANG = "ang"
    case AOA = "aoa"
    case ARS = "ars"
    case AUD = "aud"
    case AWG = "awg"
    case AZN = "azn"
    case BAM = "bam"
    case BBD = "bbd"
    case BDT = "bdt"
    case USD = "usd"

    func desc() -> CurrencyDesc {
        switch self {
        case AED: return CurrencyDesc(name: "United Arab Emirates Dirham",
            currency: self)
        case AFN: return CurrencyDesc(name: "Afghan Afghani*", currency: self)
        case ALL: return CurrencyDesc(name: "Albanian Lek", currency: self)
        case AMD: return CurrencyDesc(name: "Armenian Dram", currency: self)
        case ANG: return CurrencyDesc(name: "Netherlands Antillean Gulden",
            currency: self)
        case AOA: return CurrencyDesc(name: "Angolan Kwanza*", currency: self)
        case ARS: return CurrencyDesc(name: "Argentine Peso*", currency: self)
        case AUD: return CurrencyDesc(name: "Australian Dollar*", currency: self)
        case AWG: return CurrencyDesc(name: "Aruban Florin", currency: self)
        case AZN: return CurrencyDesc(name: "Azerbaijani Manat", currency: self)
        case BAM: return CurrencyDesc(name: "Bosnia & Herzegovina Convertible Mark",
            currency: self)
        case BBD: return CurrencyDesc(name: "Barbadian Dollar", currency: self)
        case BDT: return CurrencyDesc(name: "Bangladeshi Taka", currency: self)
        case USD: return CurrencyDesc(name: "United States Dollar", currency: self)
    }
}

Okay, what just happened there?

If you refer back to our charge object, currency is a string: three lower case letters denoting the currency.

We can use the Stripe-provided abbreviation to populate our correct StripeCurrency enum value:

1
let attemptedCurrency: StripeCurrency? = StripeCurrency.fromRaw("amd")

Remember from our enum introduction: fromRaw does not return the type directly, but rather it returns an optional. Swift has no way of knowing we will only pass in known-good values. Swift reserves the right to return nil if we pass in a non-matching raw string like “princess bubblegum is evil” when asking for StripeCurrency.

To use the returned currency value, we should check to make sure we did get a currency back and not just nil:

1
2
3
4
5
6
7
8
if attemptedCurrency {
   actualCurrency = attemptedCurrency!
} else {
    println("Incorrect currency conversion attempted!")
    /* ask for a new currency and make sure actualCurrency
     * exists before continuing */
}
`

Note how we unwrap the optional by using ! only after we are sure the optional is not nil.

Okay, so that covers the default raw strings on the enum. What’s up with the desc() function? The desc() function provides an in-enum helpful lookup table so we can get the actual currency name back again once we have an enum value:

1
attemptedCurrency!.desc().name == "Armenian Dram"  /* true */

Note also how desc().currency holds the same value as our original enum instance, but it is not the same enum instance:

1
attemptedCurrency!.desc().currency == attemptedCurrency!  /* true */

In Swift, == tests for equality of value while === tests if two instances refer to the exact same storage location. Since enums (and structs) are value types, they have no ability to be compared by === since nothing could ever be equal to their copy-on-assignment storage location.

Modeling Embedded Objects

As we saw previously, properties of any Swift struct or class can be defined to take on any type available in Swift.

charge contains an entire sub-object in the card field. Let’s create types required by the card object so we can then define a card type:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
enum StripeCheckResult: String {
    case Pass = "pass"
    case Fail = "fail"
    case Unchecked = "unchecked"
}

struct StripeFingerprint {
    let fingerprint: String
}

struct StripeLast4 {
    let digits: Int
    init(digits: String) {
        self.init(digits: digits.toInt()!)
    }
    
    init(digits: Int) {
        if digits > 0 || digits < 10000 {
            self.digits = digits
        } else {
            self.digits = 0
        }
    }
}

enum StripeCardBrand: String {
    case Visa = "Visa"
    case Amex = "American Express"
    case MasterCard = "MasterCard"
    case Discover = "Discover"
    case JCB = "JCB"
    case DinersClub = "Diners Club"
    case Unknown = "Unknown"
}

enum StripeObjectType {
    case Charge, Card, Customer, Invoice, InvoiceItem, Plan, Token
}

Note how in StripeCardBrand and StripeCheckResult we set default raw values on each enum property to be exactly the same string returned in the Stripe object. By using Stripe return values as our default enum values, we can use fromRaw() reliably to instantiate the correct enum value.

Now let’s define StripeCard in terms of our just-defined types (we’ll use raw Swift types for some values for expediency here):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
struct StripeCard {
    let id: StripeCardId
    let object = StripeObjectType.Card
    let last4: StripeLast4
    let type: StripeCardBrand
    let expirationMonth: Int
    let expirationYear: Int
    let fingerprint: StripeFingerprint
    let country: String
    let name: String?
    let addr1: String?
    let addr2: String?
    let addrCity: String?
    let addrState: String?
    let addrZip: String?
    let addrCountry: String?
    let cvcCheck: StripeCheckResult?
    let addr1Check: StripeCheckResult?
    let addrZipCheck: StripeCheckResult?
    let customer: String?
    
    init(id: String, last4: String, type: String,
        expMo: Int, expYr: Int, fingerprint: String,
        country: String) {
            
        if expMo >= 1 && expMo <= 12 {
            expirationMonth = expMo
        } else {
            expirationMonth = 0
        }

        if expYr >= 2014 && expYr <= 3000 {
            expirationYear = expYr
        } else {
            expirationYear = 0
        }
 
        self.id = StripeCardId(id: id)
        self.last4 = StripeLast4(digits: last4)
        self.type = StripeCardBrand.fromRaw(type)!
        self.fingerprint = StripeFingerprint(fingerprint: fingerprint)
        self.country = country
    }
}

Swift requires us to define every property at initialization time, but we can skip defining properties marked optional since it’s okay if they have a default value of nil.

If we give a property a default value (like object), we do not have to set the property in the initializer since it already has a default value.

Note how for these Stripe types, every property is marked let. These types are for representing objects returned from the Stripe API. Once the object is created, we don’t want to change any properties because every property represents state on the Stripe API. If we need updated values, we should re-fetch the object from the API.

Finally Creating the charge Object

By now we’ve defined enough sub-types of the charge object, so we can use all our clever types to create an unbreakable StripeCharge Swift type.

Now, finally, let’s create our StripeCharge type from mostly our custom types:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct StripeCharge {
    let id: StripeChargeId
    let object = StripeObjectType.Charge
    let created: StripeTimestamp
    let livemode: StripeMode
    let paid: StripePaid
    let amount: StripeAmount
    let currency: StripeCurrency
    let refunded: StripeRefunded
    let card: StripeCard
    let captured: StripeCardCaptured
    let refunds = Array<String>() /* would actually be array of Refund types */
    let balanceTxn: StripeBalanceTransactionId
    let failureMsg: String?
    let failureCode: String?
    let amountRefunded = StripeAmount(cents: 0)
    let customer: String?
    let invoice: String?
    let desc: String?
    let dispute: String? /* would actually be Dispute type */
    let metadata: String? /* actually, user-defined K-V metadata per charge */
    let statementDescription: String?
    
    init(id: String, created: UInt64, mode: Bool, paid: Bool, amount: Int,
        currency: String, refunded: Bool, card: StripeCard, captured: Bool,
        balanceTxnId: String) {
            self.id = StripeChargeId(id: id)
            self.created = StripeTimestamp(epoch: created)
            self.livemode = StripeMode(livemode: mode)
            self.paid = StripePaid(paid: paid)
            self.amount = StripeAmount(cents: amount)
            self.currency = StripeCurrency(abbv: currency)
            self.refunded = StripeRefunded(refunded: refunded)
            self.card = card
            self.captured = StripeCardCaptured(captured: captured)
            self.balanceTxn = StripeBalanceTransactionId(id: balanceTxnId)
    }
}

Now, with our StripeCard and our StripeCharge types, we can finally create an instance of StripeCharge from the original charge object data.

First, we create the inner card instance:

1
2
3
4
let card = StripeCard(id: "card_104BZB2eZvKYlo2CRnmgsNrD",
                      last4: "4242", type: "Visa",
                      expMo: 1, expYr: 2050,
                      fingerprint: "Xt5EWLLDS7FJjR1c", country: "US")

Then, we can create our charge instance:

1
2
3
4
5
6
let charge = StripeCharge(id: "ch_104BZB2eZvKYlo2CdBjAUHDY",
                          created: 1402256002,
                          mode: false, paid: true, amount: 500,
                          currency: "usd", refunded: false, card: card,
                          captured: true,
                          balanceTxnId: "txn_10491X2eZvKYlo2C78fHq3sa")

And that, dear reader, is how you can strictly model important data types inside Swift.

For the StripeCharge object we did take some shortcuts. We didn’t expose every property to init. There are a few types we skipped over defining such as the Dispute and Refund types. Since we didn’t expose every property as parameters to our init method, any unset (or default) property will always be fixed to their default values. For a proper production implementation, your initialization must take every property as a parameter.

Extra Types

For more practice, we can define a few more Stripe objects almost completely from the types we’ve already implemented.

Here’s an attempt at a partial StripeCustomer with new StripeEmail and StripeCustomerId types:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct StripeEmail {
    let email: String
    init(email: String) {
        var isPotentialEmail = false
        for char in email {
            if char == "@" {
                isPotentialEmail = true
                break
            }
        }
        if isPotentialEmail {
            self.email = email
        } else {
            self.email = ""
        }
    }
}

class StripeCustomerId: StripeIdentifier {
    init(id: String) {
        super.init(prefixes: ["cu_", "cus_"], id: id)
    }
}

struct StripeCustomer {
    let id: StripeCustomerId
    let created: StripeTimestamp
    let desc: String?
    let mode: StripeMode
    let email: StripeEmail
    let delinquent: Bool
    let accountBalance: StripeAmount
}

Note how in this case StripeCustomerId has two potential prefixes, so we add both to the prefixes array.

The Remaining 15 Types

Don’t worry, we’re not going to implement the remaining 15 Stripe API object types.

If you want more practice thinking about and implementing very specific types in Swift, implement Stripe’s Subscription and Invoice objects as types inside Swift too.


  1. The full implementation is in the Xcode Swift Playground for this chapter.