# Collect and Send Data

### Collect and Send Data

With the VGS Collect SDK, you can tokenize data via the **Vault API** or securely send it to your server using the **VGS Proxy**.

### Send Data to Your Server via VGS Proxy

Using the `sendData(_:)` method, sensitive data from `VGSTextField` will be collected, stored in your organization's Vault, and sent to the Upstream URL specified in the Inbound Route.

```swift
/**
  Send data from VGSTextFields to your organization vault.
  
  - Parameters:
    - path: Inbound rout path for your organization vault.
    - method: HTTPMethod, default is `.post`.
    - routeId: id of VGS Proxy Route, default is `nil`.
    - extraData: Any data you want to send together with data from VGSTextFields , default is `nil`.
    - requestOptions: `VGSCollectRequestOptions` object, holds additional request options. Default options are `.nestedJSON`.
    - completion: response completion block, returns `VGSResponse`.
  
  - Note:
    Errors can be returned in the `NSURLErrorDomain` and `VGSCollectSDKErrorDomain`.
*/
public func sendData(path: String,
                    method: HTTPMethod = .post,
                  routeId: String? = nil,
                extraData: [String: Any]? = nil,
            requestOptions: VGSCollectRequestOptions = VGSCollectRequestOptions(),
          completion block: @escaping (VGSResponse) -> Void)

/**
  Asynchronously send data from VGSTextFields to your organization vault.
*/
public func sendData(path: String,
                    method: HTTPMethod = .post,
                  routeId: String? = nil,
                extraData: [String: Any]? = nil,
            requestOptions: VGSCollectRequestOptions = VGSCollectRequestOptions()) async throws -> VGSResponse

/**
  Send data from VGSTextFields to your organization vault using the Combine framework.
*/
public func sendDataPublisher(path: String,
                    method: HTTPMethod = .post,
                  routeId: String? = nil,
                extraData: [String: Any]? = nil,
            requestOptions: VGSCollectRequestOptions = VGSCollectRequestOptions()) -> Future<VGSResponse, Never> 
```

The send data request returns `VGSResponse.failure(_:)` in the following cases:\
• The response status code is invalid (i.e., not in the range **\[200..<300]**).\
• The request is not possible due to iOS system conditions (e.g., no internet connection).\
• The data being sent is invalid according to `VGSConfiguration`.\\

Errors can be returned in the `NSURLErrorDomain` and `VGSCollectSDKErrorDomain`.\
For more details, check the [SDK Reference Docs](https://verygoodsecurity.github.io/vgs-collect-ios/Errors.html).

> • Data will be collected from all text fields that registered to current `VGSCollect`instance and send to your organization vault id.\
> • By default `aliases` are not a part of response data for `sendData(_:)` request. It's your responsibility to configure a response for the client after the data goes through the Inbound Proxy to your backend. However, if you test `sendData(_:)` request with our echo server, you will get a response with the same body as in the request, where the raw data will be redacted to aliases.

#### Code example

*Send data to your organization vault:*

```swift
func sendData(_ sender: UIButton) {

    /// extra information can be sent together with all sensitive data from VGSTextFields
    var extraData = [String: Any]()
    extraData["cardHolderName"] = "Joe Business"

    /// send data to your Vault
    vgsCollect.sendData(path: "/post", method: .post, extraData: extraData) { [weak self](../../response) in
      switch response {
        case .success(let code, let data, let response):
          // parse data
        case .failure(let code, let data, let response, let error):
          // handle failed request
          switch code {
            // handle error codes
          }
      }
    }
}
```

*Asynchronously send data from VGSTextFields to your organization vault:*

```swift
func sendData() async {
    do {
      let response = try await vgsCollect.sendData(path: "/post", method: .post)
      switch respose {
        case .success(let code, let data, let response):
          // parse data
        case .failure(let code, let data, let response, let error):
          // handle failed request
          switch code {
            // handle error codes
          }
      }
    } catch {
      // handle error
    }
}
```

*Send data from VGSTextFields to your organization vault using the Combine framework:*

```swift
  vgsCollect.sendDataPublisher(path: "/post", method: .post)
    .sink { completion in
      switch completion {
        case .finished:
          // handle finished
        case .failure(let error):
          // handle error
      }
    } receiveValue: { response in
      // parse data
    }
    .store(in: &cancellables)
```

> • If you need to send files data, check **VGSFilePickerController**.

### Sending custom data

To send additional custom data, include it in the **extraData** parameter of the `sendData(_:`)\` request.

#### Code example

```swift
// ...

/// extra information can be sent together with all sensitive data from VGSTextFields
var extraData = [String: Any]()
extraData["cardHolderName"] = "Joe Business"

/// send data to your Vault
vgsCollect.sendData(path: "/post", extraData: extraData) { [weak self](../../response) in
  switch response {
    // ...
  }
}
```

### Setting Custom API Headers

To include custom headers in your `sendData(_:)` request, assign them to:

```swift
var customHeaders: [String: String]?
```

#### Code example

```swift
// ...

/// set custom headers
vgsCollect.customHeaders = [
    "custom header": "custom data"
]
```

### VGSResponse

Represents the result of a VGS SDK request.

#### Declaration

```swift
@frozen enum VGSResponse

    /// Success response case
    case success(_ code:Int, _ data:Data?, _ response: URLResponse?)

    /// Failed response case
    case failure(_ code:Int, _ data:Data?, _ response: URLResponse?, _ error:Error?)
```

See [SDK Reference Docs](https://verygoodsecurity.github.io/vgs-collect-ios/Enums/VGSResponse.html) for more information.

## Advanced Settings

### Setting Custom JSON Structure

Use dot **(.)** notation in the `VGSConfiguration.fieldName` to define nested JSON structures. Each dot **(.)** in a **fiealdName** will create a new level of nesting.

#### Code example

```swift
/// Define field names
let userId = "userId"
let cardNumberFieldKey = "card_data.card_number"
let cardCVCFieldKey = "card_data.cvc"

/// Example of transformed JSON structure on sendData(_:)
{
  "userId": "id12345",
  “card_data: {
     “card_number”:  411111111111111”,
     “cvc”: “123”
  }”
}
```

> • If there are two fieldNames with equal names - they will be overwritten.\
> • If object structure and field name in **extraData** are same as defined in **VGSConfiguration** fieldName, value in **extraData** will be overwritten by data collected from **VGSCollect** elements with same name on **sendData(\_:)**.

Dot **(.)** notation does not apply to **extraData** keys. You must format **extraData** as desired before passing it to `sendData(_:)`.

#### Nested JSON with array

Use **\[index]** notation in **fieldName**888 to represent arrays. Only one key with an array index is supported per level. Multi-dimensional arrays are not supported.

*Field Mapping Policies:*

* **nestedJSON** - map fieldnames with dot notation to nested JSON. Arrays are not supported. Is default policy.
* **flatJSON** - map fieldNames to JSON. Flat JSON format uses **fieldname** as a single `key` and does not produce any nested JSON or array.
* **nestedJSONWithArrayOverwrite** - map fieldnames with dot notation to nested JSON and arrays. Replace `extraData` the array with the VGS Collect array.
* **nestedJSONWithArrayMerge** - map fieldnames with dot notation to nested JSON and arrays. Merge VGS Collect and `extraData` array.

Specify `fieldName` mapping policy with `VGSCollectRequestOptions` on `sendData(_:)`.

```swift
/// Define field names
let cardNumberFieldKey = "card_data[0].number"
let cardCVCFieldKey = "card_data[1].cvc"

let vgsCollect = VGSCollect(id: "<VAULT_ID>", environment: .sandbox, dataRegion: "eu-1")
var options = VGSCollectRequestOptions()
options.fieldNameMappingPolicy = .nestedJSONArrayOvewrite
// Or
options.fieldNameMappingPolicy = .nestedJSONArrayMerge

vgsCollect.sendData(path: "/post", requestOptions: options) { requestResult in }
```

Example of transformed JSON structure on `sendData(_:)`

```json
{
  "card_data" :
  [
    { "number" :  "411111111111111"},
    {"cvc": "123"}
  ]
}
```

> Check [more examples](https://github.com/verygoodsecurity/docs-content-vault/blob/update-content/vgs-collect/custom-json-structure-examples/README.md) how to customize JSON structure.

If you need to change the output value format, you can read more about available functionality in [VGSConfiguration](https://docs.verygoodsecurity.com/vault/developer-tools/vgs-collect/ios-sdk/configuration) section.

## Tokenize data

VGS offers two Vault API versions for data tokenization:\
• Vault API v1: Uses `tokenizeData(_:)`. [Learn more](https://docs.verygoodsecurity.com/vault/developer-tools/apis/vault-api-v1).\
• Vault API v2: Uses `createAliases(_:)` and requires **Authorization**. [Learn more](https://docs.verygoodsecurity.com/vault/developer-tools/apis/vault-api).\
\
Each API version requires an appropriate *Upstream URL* specified in your Vault's Route configuration.\\

> • Vault API v2 is our newest Vault API and is recommended for integrations.\
> • If you already use API v1 you can find migration guide to API v2 below.

{% tabs %}
{% tab title="Vault API v2" %}
Makes a tokenization request to Vault API v2 with data from VGSTextFields:

```swift
public func createAliases(routeId: String? = nil, completion block: @escaping (VGSTokenizationResponse) -> Void)
```

Asynchronously makes a tokenization request with data from VGSTextFields:

```swift
public func createAliases(routeId: String? = nil) async throws -> VGSTokenizationResponse
```

Makes a tokenization request with data from VGSTextFields using the Combine framework:

```swift
public func createAliasesDataPublisher(routeId: String? = nil) -> Future<VGSTokenizationResponse, Never>
```

{% endtab %}

{% tab title="Vault API v1" %}
Makes a tokenization request to Vault API v1 with data from VGSTextFields:

```swift
public func tokenizeData(routeId: String? = nil, completion block: @escaping (VGSTokenizationResponse) -> Void)
```

Asynchronously makes a tokenization request with data from VGSTextFields:

```swift
public func tokenizeData(routeId: String? = nil) async throws -> VGSTokenizationResponse
```

Makes a tokenization request with data from VGSTextFields using the Combine framework:

```swift
public func tokenizeDataPublisher(routeId: String? = nil) -> Future<VGSTokenizationResponse, Never>
```

{% endtab %}
{% endtabs %}

### VGSTokenizationResponse

An object you use to handle SDK tokenization request completion.

#### Declaration

```swift
@frozen public enum VGSTokenizationResponse {
    /**
     Success response case

     - Parameters:
        - code: response status code.
        - body: response **JsonData** object.
        - response: URLResponse object represents a URL load response.
    */
  case success(_ code: Int, _ body: JsonData?, _ response: URLResponse?)

    /**
     Failed response case

     - Parameters:
        - code: response status code.
        - data: response **Data** object.
        - response: `URLResponse` object represents a URL load response.
        - error: `Error` object.
    */
    case failure(_ code: Int, _ data: Data?, _ response: URLResponse?, _ error: Error?)
}
```

#### Code example

Send tokenization request with data from VGSTextFields to your organization vault:

{% tabs %}
{% tab title="Vault API v2" %}

You should set authToken into VGSCollect customHeaders before calling the createAliases(:) function:

```swift
vgsCollect.customHeaders = ["Authorization": "Bearer (authToken)"]
vgsCollect.createAliases{ [weak self](response) in
  switch response {
  case .success(_, let resultBody, _):
    let response = (String(data: try! JSONSerialization.data(withJSONObject: resultBody!, options: .prettyPrinted), encoding: .utf8)!)
      print(response)
      return
  case .failure(let code, _, _, let error):
    switch code {
    case 400..<499:
      // Wrong request. This also can happend when your Routs not setup yet or your <vaultId> is wrong
    case VGSErrorType.inputDataIsNotValid.rawValue:
      if let error = error as? VGSError {
        self?.consoleLabel.text = "Error: Input data is not valid. Details:
 (error)"
      }
    default:
      self?.consoleLabel.text = "Error: Something went wrong. Code: (code)"
    }
    print("Submit request error: (code), (String(describing: error))")
    return
  }
}
```

{% endtab %}

{% tab title="Vault API v1" %}

```swift
vgsCollect.tokenizeData { (response) in
  switch response {
  case .success(_, let resultBody, _):
    let response = (String(data: try! JSONSerialization.data(withJSONObject: resultBody!, options: .prettyPrinted), encoding: .utf8)!)
    print("Success: (response)")
    return
  case .failure(let code, _, _, let error):
    switch code {
    case 400..<499:
      // Wrong request. This also can happend when your Routs not setup yet or your <vaultId> is wrong
      print("Error: Wrong Request, code: (code)")
    case VGSErrorType.inputDataIsNotValid.rawValue:
      // When input is not valid according to configuration
      if let error = error as? VGSError {
        print("Error: Input data is not valid. Details: (error)")
      }
  default:
    print("Error: Something went wrong. Code: (code)")
  }
  print("Tokenize request error: (code), (String(describing: error))")
  return
  }
}
```

{% endtab %}
{% endtabs %}

\
Send tokenization request with data from VGSTextFields to your organization vault using the async/await:

{% tabs %}
{% tab title="Vault API v2" %}

```swift

do {
  let response = try await vgsCollect.createAliases()
  switch respose {
    case .success(let code, let data, let response):
      // parse data
    case .failure(let code, let data, let response, let error):
      // handle failed request
      switch code {
      // handle error codes
      }
    }
} catch {
// handle error
}

```

{% endtab %}

{% tab title="Vault API v1" %}

```swift

do {
  let response = try await vgsCollect.tokenizeData()
  switch respose {
    case .success(let code, let data, let response):
      // parse data
    case .failure(let code, let data, let response, let error):
      // handle failed request
      switch code {
      // handle error codes
      }
  }
} catch {
// handle error
}
  
```

{% endtab %}
{% endtabs %}

\
Send toa kenization request with data from VGSTextFields to your organization's vault using the Combine framework:

{% tabs %}
{% tab title="Vault API v2" %}

```swift

  vgsCollect.createAliasesDataPublisher()
    .sink { completion in
      switch completion {
        case .finished:
          // handle finished
        case .failure(let error):
          // handle error
      }
    } receiveValue: { response in
      // parse data
    }
    .store(in: &cancellables)

```

{% endtab %}

{% tab title="Vault API v1" %}

```swift

  vgsCollect.tokenizeDataPublisher()
    .sink { completion in
      switch completion {
        case .finished:
          // handle finished
        case .failure(let error):
          // handle error
      }
    } receiveValue: { response in
      // parse data
    }
    .store(in: &cancellables)

```

{% endtab %}
{% endtabs %}

> * Data will be collected from all `VGSTextFields` registered to current `VGSCollect`instance.\
>   \- Only data from `VGSTextFields` configured with one of available [tokenization configurations](https://docs.verygoodsecurity.com/vault/developer-tools/vgs-collect/ios-sdk/tokenization-configuration) will be tokenized and stored in your's organization vault.\
>   \- Data from `VGSTextFields` configured with other configuration types will be returned as *raw value* along with tokenized data in `VGSTokenizationResponse`.\
>   \- Data from fields with type `.cvc` and `.cardNumber` will not be included to `VGSTokenizationResponse` if [tokenization configurations](https://docs.verygoodsecurity.com/vault/developer-tools/vgs-collect/ios-sdk/tokenization-configuration) not set for appropriate fields.

#### RouteId

The route identifier specifies how data should be routed in your organization's vault. You should follow the next rules when setting `routeId`:

* You should set specific `routeId` when you use multiple routes in your organization's vault.
* RouteId should be a valid **UUID** string; you will receive an error in `VGSTokenizationResponse` in case if it's not valid.
* If you have only one Inbound Route in your organization's vault, you can leave `routeId` as **nil**.

### Migrating from Vault API v1 to v2

Migration from Vault **API v1** to **API v2** will require you to implement the next steps:

* Update your organization's Vault Route with a new **Upstream Host**: use `https://<your-vault-id>.sandbox.vault-api.verygoodvault.com` for **sandbox** or `https://<your-vault-id>.live.vault-api.verygoodvault.com` for a **live** environment.
* Receive Tokenization **authToken** from your backend and set it in **VGSCollect** headers:\
  `vgsCollect.customHeaders = ["Authorization": "Bearer \(authToken)"]`.
* Use `-createAliases(:)` function instead of `-tokenize(:)` creating aliases.

For more details about available attributes and functionality, check [VGSCollectSDK Reference docs](https://verygoodsecurity.github.io/vgs-collect-ios/)
