Skip to content

Commit

Permalink
feat: adds maskSatisified, setValueRef, and errors to iOS docs (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
brigonzalez authored Jan 10, 2023
1 parent 2a4fb14 commit 7b334e3
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 25 deletions.
57 changes: 47 additions & 10 deletions docs/sdks/mobile/ios/events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,55 @@ sidebar_label: Events

# Events

## ElementEvent

iOS Element events are triggered whenever a user types into an element text field. An `ElementEvent` is the struct that gets passed into the [subject](/docs/sdks/mobile/ios#modified-and-additional-fields)’s `receiveValue` function. The following are properties on the `ElementEvent` struct:

| Property | Description |
|----------|------------------------------------------------------------------------------------------------------------|
| type | The event type for the `ElementEvent`. |
| complete | Whether the input is `valid` and the `mask` is satisfied. |
| valid | Whether the input is `valid` according to `validation` for each element. |
| details | An array of [ElementEventDetail](#elementeventdetail) describing more information about the element event. |
| Property | Description |
| ------------- |------------------------------------------------------------------------------------------------------------|
| type | The event [type](#elementevent-types) for the `ElementEvent`. |
| complete | Whether the input `valid` and `maskSatisfied` properties are `true`. |
| valid | Whether the input is `valid` according to `validation` for each element. |
| maskSatisfied | Whether the input satisfies the `mask` length requirements. |
| details | An array of [ElementEventDetail](#elementeventdetail) describing more information about the element event. |

### ElementEvent Types

The following are the available element event types and their descriptions.

| Type | Description |
|--------------|--- |
| textChange | All elements emit this event when a user changes an element's value. |
| maskChange | `CardVerificationCodeUITextField` elements emit this event when its mask has changed. |

### ElementEventDetail

| Property | Description |
|----------|---------------------------------------------------|
| type | A `String` describing the type of detail. |
| message | A `String` containing the message for the detail. |
| Property | Description |
|----------|------------------------------------------------------------------------|
| type | A `String` describing the [type](#elementeventdetail-types) of detail. |
| message | A `String` containing the message for the detail. |

#### ElementEventDetail Types

The following are the available element event detail types and their descriptions.

| Type | Description |
| --- |--- |
| cardBrand | `CardNumberUITextField` elements emit this event when a card number can be identified. |
| cardLast4 | `CardNumberUITextField` elements emit the last 4 digits of a card number when the input is considered `complete`. |
| cardBin | `CardNumberUITextField` elements emit the first 6 digits of a card number when the input is considered `complete`. |

## Usage

You can observe element events through the [PassThroughSubject](https://developer.apple.com/documentation/combine/passthroughsubject) [subject](/docs/sdks/mobile/ios#modified-and-additional-fields) field on every element.

```swift showLineNumbers
private var cancellables = Set<AnyCancellable>()
...

myTextField.subject.sink { completion in
...
} receiveValue: { elementEvent in
...
}.store(in: &cancellables)
```
9 changes: 5 additions & 4 deletions docs/sdks/mobile/ios/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Here's how we make this possible:
- [Install](#installation) our iOS SDK into your mobile application
- Build forms using our **[Element](/docs/sdks/mobile/ios/types)** input components
- [Interact](/docs/sdks/mobile/ios/services#tokenize) with the Basis Theory API using **Element** references, not plaintext data
- Own your UI/UX by fully customizing how **Elements** are styled
- Own your UI/UX by fully customizing how **Elements** are [styled](#styling-elements)

## Collect Sensitive Data

Expand Down Expand Up @@ -93,7 +93,7 @@ Below is an example of a UITextField set as `TextElementUITextField`.

![](/img/iOS/ios-initialization.jpg)

## Customizing Elements
## Styling Elements

All of our elements have been designed to take advantage of the pre-existing native properties and customizations built directly into the [UIKit framework](https://developer.apple.com/documentation/uikit). Utilizing this framework means you’re able to customize the styling of your elements as you would with any other `UITextField`. For example:

Expand All @@ -110,8 +110,9 @@ myTextElement.backgroundColor = UIColor( red: 240/255, green: 240/255, blue: 240

`BasisTheoryElements` package’s elements are a simple wrapper for the native [UIKit](https://developer.apple.com/documentation/uikit) UITextField. Due to this developers can take full advantage of existing native customization, but we restrict and enable access to the underlying data in the following ways:

| Fields | Description |
| Field | Description |
| --- | --- |
| text | We restrict the getter for this value; it always returns nil. The setter works as is. |
| subject | An instance of PassThroughSubject that allows you to subscribe to [ElementEvents](/docs/sdks/mobile/ios/events). |
| subject | An instance of [PassThroughSubject](https://developer.apple.com/documentation/combine/passthroughsubject) that allows you to subscribe to [ElementEvents](/docs/sdks/mobile/ios/events). |
| setValue | A function that recieves an `ElementValueReference` parameter to set the value of the element. Note: `ElementValueReference` instances can only be retrieved from [proxy](/docs/sdks/mobile/ios/services#proxy) responses. |
| setValueRef | Binds the provided [TextElementUITextField](/docs/sdks/mobile/ios/types#textelementuitextfield) instance as a value source for this element, keeping the value of this element in sync with any changes made to the other element. This makes the caller element read-only. Any iOS element type can be provided as an instance into this function. |
3 changes: 2 additions & 1 deletion docs/sdks/mobile/ios/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ For example, the mask for a US based phone number shown below will have the foll
let regexDigit = try! NSRegularExpression(pattern: "\\d")

let phoneMask = [ //(123)456-7890
"(",
"(",
regexDigit,
regexDigit,
regexDigit,
Expand Down Expand Up @@ -70,6 +70,7 @@ let phoneMask = [ //(123)456-7890
regexDigit
] as [Any]

let transformMatcher = try! NSRegularExpression(pattern: "[()-]") //Regex to remove parentheses & dashes
textElementUITextField.setConfig(options: TextElementOptions(mask: phoneMask, transform: ElementTransform(matcher: transformMatcher))
```

Expand Down
54 changes: 47 additions & 7 deletions docs/sdks/mobile/ios/services.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
sidebar_label: Services
---

import { Alert } from "@site/src/components/shared/Alert";

# Services

## tokenize
## Methods

### tokenize

Elements' values can be securely tokenized utilizing our [tokenize](/docs/api/tokens/tokenize) services. To accomplish this, simply pass the Element instance in the payload.

Expand All @@ -19,12 +23,19 @@ let body: [String: Any] = [
]

BasisTheoryElements.tokenize(body: body, apiKey: "<YOUR PUBLIC API KEY>")
{ data, error in ... }
{ data, error in ... }
```

<Alert>
Note that <code>tokenize</code> requires the use of a public API key during initialization
(an API key issued to a <code>public</code> <a href="/docs/concepts/access-controls#what-are-applications">Application</a>).
Click <a href="https://portal.basistheory.com/applications/create?permissions=token%3Acreate&type=public">here </a>
to create one in the Basis Theory portal.
</Alert>

The callback provided calls your function with a `data` of type `AnyCodable`, and an `error` of type `Error`.

## createToken
### createToken

Elements' values can be securely tokenized utilizing our [createToken](/docs/api/tokens/tokenize#create-token) services. To accomplish this, simply pass the Element instance in the payload.

Expand All @@ -35,12 +46,19 @@ let body: CreateToken = CreateToken(type: "token", data: [
], searchIndexes: ["{{ data.property }}"])

BasisTheoryElements.createToken(body: body, apiKey: "<YOUR PUBLIC API KEY>")
{ data, error in ... }
{ data, error in ... }
```

<Alert>
Note that <code>createToken</code> requires the use of a public API key during initialization
(an API key issued to a <code>public</code> <a href="/docs/concepts/access-controls#what-are-applications">Application</a>).
Click <a href="https://portal.basistheory.com/applications/create?permissions=token%3Acreate&type=public">here </a>
to create one in the Basis Theory portal.
</Alert>

The callback provided calls your function with a `data` of type `CreateTokenResponse`, and an `error` of type `Error`.

## proxy
### proxy

Proxy provides a simple way to retrieve data back into an element utilizing our [proxy](/docs/api/proxies/invoke-proxy) service. To accomplish this, simply construct your proxy request like this:

Expand All @@ -55,7 +73,7 @@ let proxyHttpRequest = ProxyHttpRequest(method: .post, body: [
])

BasisTheoryElements.proxy(
apiKey: "<YOUR EXPIRING API KEY>",
apiKey: "<YOUR BT API KEY>",
proxyKey: "<YOUR PROXY KEY>",
proxyHttpRequest: proxyHttpRequest)
{ response, data, error in ... }
Expand All @@ -71,7 +89,7 @@ The callback provided calls your function with a:
...

BasisTheoryElements.proxy(
apiKey: "<YOUR EXPIRING API KEY>",
apiKey: "<YOUR BT API KEY>",
proxyKey: "<YOUR PROXY KEY>",
proxyHttpRequest: proxyHttpRequest)
{ response, data, error in
Expand All @@ -84,3 +102,25 @@ BasisTheoryElements.proxy(
{ data, error in print(data) }
}
```

## Errors

| Error | Description |
| --- |--- |
| TokenizingError.applicationTypeNotPublic | The [Application](/docs/api/applications) API key used is not of type `public`. Create a `public` API key through this [link](https://portal.basistheory.com/applications/create?permissions=token%3Acreate&type=public). |
| TokenizingError.invalidInput | An element instance used in a tokenization request is invalid. Check the element events on each element to determine which one is invalid. |
| ProxyError.invalidRequest | The [proxy](#proxy) request is malformed. Revise the [proxy](#proxy) request being attempted. |
| ErrorResponse.error | An instance of [ErrorResponse enum](#errorresponse-enum) gets returned when there's an error from the BasisTheory API. |

### ErrorResponse enum

| Order | Associated Value Name | Description |
| --- | --- |--- |
| 1 | status | An `Int` describing the response status code |
| 2 | data | A `Data?` instance describing the response body |
| 3 | urlResponse | The raw `UrlResponse?` instance |
| 4 | error | The raw `Error` instance |

<Alert>
The <code>ErrorResponse</code> enum can be imported from the BasisTheory Swift SDK through <code>import BasisTheory</code>.
</Alert>
100 changes: 97 additions & 3 deletions docs/sdks/mobile/ios/types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ sidebar_label: Types
---

import { Alert } from "@site/src/components/shared/Alert";
import CardBrands from "@site/docs/sdks/_card-brands.mdx";

# iOS Element Types

Expand Down Expand Up @@ -44,6 +45,12 @@ class TokenizeName: UIViewController {
}
```

| Configuration | Defaults |
| --- | --- |
| validation | No default validation. Always valid in [ElementEvent](/docs/sdks/mobile/ios/events#elementevent). |
| mask | No default [mask](/docs/sdks/mobile/ios/options#mask). [Mask](/docs/sdks/mobile/ios/options#mask) can be overridden. |
| transform | No default [transform](/docs/sdks/mobile/ios/options#transform). [Transform](/docs/sdks/mobile/ios/options#transform) can be overriden. |

## CardNumberUITextField

The `CardNumberUITextField` element type renders a card number input featuring automatic brand detection, input validation, and masking. The input must be Luhn valid and be an acceptable length for the card brand.
Expand Down Expand Up @@ -82,6 +89,21 @@ class TokenizeCardNumber: UIViewController {
}
```

| Configuration | Defaults |
| --- | --- |
| validation | Input must be Luhn valid and be an acceptable length for the card brand. |
| mask | The [mask](/docs/sdks/mobile/ios/options#mask) changes depending on the card brand identified for this input according to the [card brand](#card-brands). |
| transform | The [transform](/docs/sdks/mobile/ios/options#transform) removes all spaces set by the [mask](/docs/sdks/mobile/ios/options#mask) before tokenization. |

### Card Brands

The first several digits of the card number are analyzed as the user is typing to determine the card brand.
The brand is used to automatically set a [mask](/docs/sdks/mobile/ios/options#mask) to a brand-specific format.
If the [CardNumberUITextField](#cardnumberuitextfield) is bound to a [CardVerificationCodeUITextField](#cardverificationcodeuitextfield),
a `mask` is also automatically set on the `CardVerificationCodeElement` based on the brand's CVC length requirements.

<CardBrands />

## CardExpirationDateUITextField

The `CardExpirationDateUITextField` element type features a month and year formatted input with validation. The input must be the current month and year or later.
Expand Down Expand Up @@ -121,6 +143,28 @@ class TokenizeCardExpirationDate: UIViewController {
}
```

| Configuration | Defaults |
| --- | --- |
| validation | Input must be the current month and year or later. |
| mask | The [mask](/docs/sdks/mobile/ios/options#mask) is two digits followed by a forward slash followed by two more digits (eg. `MM/YY` ). |
| transform | No default [transform](/docs/sdks/mobile/ios/options#transform). |

#### Month and Year

Both the month and year values need to be retrieved from a `CardExpirationDateUITextField` with the `month()` and `year()` functions, respectively. Below is an example:

```swift
let body: [String: Any] = [
"data": [
"number": self.cardNumberTextField,
"expiration_month": self.expirationDateTextField.month(),
"expiration_year": self.expirationDateTextField.year(),
"cvc": self.cvcTextField
],
"type": "card"
]
```

## CardVerificationCodeUITextField

The `CardVerificationCodeUITextField` element type is used to collect the card verification code.
Expand Down Expand Up @@ -159,7 +203,28 @@ class TokenizeCVC: UIViewController {
}
```

See below for an example that uses all of the card-related mobile elements, `CardNumberUITextField`, `CardExpirationDateUITextField`, and `CardVerificationCodeUITextField`, together.
| Configuration | Defaults |
| --- | --- |
| validation | No default validation. Always valid in [ElementEvent](/docs/sdks/mobile/ios/events#elementevent). |
| mask | If not associated with a `CardNumberUITextField`, the [mask](/docs/sdks/mobile/ios/options#mask) is a 4 digit number. If it is, the [mask](/docs/sdks/mobile/ios/options#mask) changes depending on the [card brand](#card-brands) identified by the `CardNumberUITextField`. Refer to the [section below](#associating-a-cardnumberuitextfield). |
| transform | No default [transform](/docs/sdks/mobile/ios/options#transform). |

### Associating a CardNumberUITextField

Associating a `CardNumberUITextField` with a `CardVerificationCodeUITextField` will enhance masking capabilities of the CVC element. By default, a `CardVerificationCodeUITextField` `mask` is a 4-digit number.
But when associated with a `CardNumberUITextField`, the `mask` will change to match the [card brand](#card-brands) identified by the `CardNumberUITextField`. Below is an example of how to make that association:

```swift
cvcTextField.setConfig(
options: CardVerificationCodeOptions(
cardNumberUITextField: cardNumberTextField
)
)
```

## Collecting Card Data Example

See below for an example that uses all the card-related mobile elements, `CardNumberUITextField`, `CardExpirationDateUITextField`, and `CardVerificationCodeUITextField` together.

<Alert>
Note that when these card-related elements are grouped together in an array, as shown in the value of <code>data</code> below, the token <code>type</code> of <code>card</code> should be used.
Expand All @@ -171,13 +236,14 @@ import UIKit
import BasisTheoryElements
import Combine

class TokenizeBillingInformation: UIViewController {

class TokenizeBillingInformationViewController: UIViewController {
@IBOutlet weak var cardNumberTextField: CardNumberUITextField!
@IBOutlet weak var expirationDateTextField: CardExpirationDateUITextField!
@IBOutlet weak var cvcTextField: CardVerificationCodeUITextField!
@IBOutlet weak var output: UITextView!

private var cancellables = Set<AnyCancellable>()

@IBAction func tokenize(_ sender: Any) {
let body: [String: Any] = [
"data": [
Expand All @@ -202,5 +268,33 @@ class TokenizeBillingInformation: UIViewController {
print(stringifiedData)
}
}

override func viewDidLoad() {
super.viewDidLoad()

let cvcOptions = CardVerificationCodeOptions(cardNumberUITextField: cardNumberTextField)
cvcTextField.setConfig(options: cvcOptions)

cardNumberTextField.subject.sink { completion in
print(completion)
} receiveValue: { message in
print("cardNumberTextField:")
print(message)
}.store(in: &cancellables)

expirationDateTextField.subject.sink { completion in
print(completion)
} receiveValue: { message in
print("expirationDateTextField:")
print(message)
}.store(in: &cancellables)

cvcTextField.subject.sink { completion in
print(completion)
} receiveValue: { message in
print("cvcTextField:")
print(message)
}.store(in: &cancellables)
}
}
```

1 comment on commit 7b334e3

@vercel
Copy link

@vercel vercel bot commented on 7b334e3 Jan 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.