Smart Engage - IOS SDK

ios - Getting Started

Requirements

The minimum supported iOS version currently is iOS 12 which is available in more than 98% of devices in circulation globally. The target IDE for interacting with the SDK is Xcode 12 onwards.

  • macOS 11.x (or later)
  • Xcode 12.x with Swift 5.x (or later)
  • Latest version of CocoaPods

Open Source SDK Sample app

The most convenient way to get started with SDK integration is to get familiar with our SDK Sample app hosted right here on GitHub. This is a complete Xcode project that produces a working application, showcasing the majority of the features described in this documentation.

Remember to fill-in your API details in the file Config.swift before attempting to compile and run the SDK Sample app.

SDK Setup

The SDK is distributed through CocoaPods. In order to proceed with installing the SDK, as a prerequisite, first install the latest version of CocoaPods:

sudo gem install cocoapods

Now, you are ready to include the SDK in the Podfile of your own project. Keep in mind that Sample is just a placeholder, and should be substituted by your own App Target name:

platform :ios, '12.0'

use_modular_headers!

target 'Sample' do
  pod 'RezolveSDK'
end

Don’t forget to use the .xcworkspace file to open your project in Xcode, instead of the .xcodeproj file, from here on out. As a final step, in order to download the actual SDK binary locally, please execute the following in terminal:

pod install
We encourage you to update your workspace with every new release of the SDK by using pod update RezolveSDK .

JWT Authentication

Overview

Smart Engage is utilizing a server-to-server JWT authentication system, conformant with the RFC7519 standard. If you are not familar with JSON Web Tokens, the site JWT.io provides an excellent primer on the use of JWTs, as well as links to various JWT libraries you can utilize.

When the Partner creates a new user on their auth server, or wishes to associate an existing user with Smart Engage for the first time, the partner must generate the Registration JWT, and then POST it to the /api/v1/authentication/register endpoint. The Smart Engage server will validate the JWT, create a new user, create the user's public/private key pair, and return to you the Smart Engage EntityId.

When a user logs in to your auth system, generate a new Login JWT and supply to CreateSession in the SDK. As long as the JWT is valid, the SDK can talk to Smart Engage. A method is suggested below for smoothly handling JWT timeouts without interrupting the SDK session.

Take a look at the following diagram in order to understand the sequence of the login/registration logic: JWT Flow Chart???might want to host the document elsewhere???

Terminology

Term Definition
partner_id A numerical ID you are assigned by Smart Engage. Usually a 2-4 digit integer.
partner_api_key The API Key you are assigned by Smart Engage. 36 characters including dashes.
partner_auth_key The Auth Key you are assigned by Smart Engage. This plays the role of the JWT Secret. The partner_auth_key is typically a ~90 character hash.
JWT token A JSON Web Token, consisting of a header, payload, and signature. The header and signature are signed with the parther_auth_key, above. It is used as a bearer token when communicating with the Smart Engage server.
accessToken In the iOS and Android code samples, the accessToken is the JWT Token you generated.
deviceId An ID that is randomly generated upon app install and stored. This id is placed in both the JWT payload and x-header sent by the SDK. The Smart Engage server checks that these values match to deter request origin spoofing. All calls except Registration calls require this.

Create the Registration JWT

For testing via cURL or Postman without a server-side JWT library, you can use https://jwt.io/#debugger to generate valid JWT tokens.

Use https://www.epochconverter.com/ to generate the token expiration in Epoch time.

Click here for screenshots showing registration using jwt.io and Postman. ???might want to host elsewhere???

Requirements

You must possess:

Field Description Example
partner_id The numerical ID you are assigned by Smart Engage 317
partner_api_key The API Key you are assigned by Smart Engage a1b2c3d4-e5f6-g7h8-i9j0-a1b2c3d4e5f6
partner_auth_key The Auth Key you are assigned by Smart Engage qwer+4ty ... JYG6XavJg== (approx 90 characters)

JWT Header

Key Value Notes
alg HS512 algorithm, HMAC SHA-512
typ JWT type
{
    "alg": "HS512",
    "typ": "JWT"
}

JWT Payload

Key Value Notes
rezolve_entity_id :NONE: use :NONE: when registering
partner_entity_id your_user_id The unique identifier for your user record. This may be a numerical ID, or an identifying string such as email address.
exp 1520869470 Expiration, as a unix timestamp integer. Set the expiration value to a small number, now() + 30 minutes or less.
{
    "rezolve_entity_id": ":NONE:",
    "partner_entity_id": "partner_entity_id",
    "exp": 1520869470
}

Signature

HMACSHA512(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    ${$partner_auth_key}
)

Sign the header and payload with the partner_auth_key. It is not necessary to decode the key before using it. Pass the whole value as a string to your library's getBytes method.

You may need to specify the charset as UTF8.

  • Example 1, Microsoft: SecretKey(Encoding.UTF8.GetBytes(key));
  • Example 2, Java: Secret.getBytes("UTF-8");

The resulting JWT will look something like this (except without linebreaks); the first third is the header, the second third the payload, and the last third the signature:

eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.
eyJyZXpvbHZlX2VudGl0eV9pZCI6IjpOT05FOiIsInBhcnRuZXJfZW50aXR5X2lkIjoiMTIzIiwiZXhwIjoxNTIwODY5NDcwfQ.
5y2e6QpUKcqTNLTv75nO6a6iFPVxrF8YeAH5NTg2ZO9dkub31GEs0N46Hu2IJf1bQ_vC2IOO9Z2N7drmvA_AKg

Register a new Smart Engage User

After the JWT is created, POST it to the authentication/register endpoint. This will create a Smart Engage User, generate a public/private keypair for the user, and return to you the corresponding Entity ID.

Example, using Sandbox endpoint:

POST: https://core.sbx.eu.rezolve.com/api/v2/authentication/register
-H content-type: application/json
-H x-rezolve-partner-apikey: your-api-key
-H authorization: Bearer signed-jwt
-d {
  "name": "John Doe",
  "last_name": "Doe",
  "first_name": "John",
  "email": "user@domain.com"
}

Responses:

201 OK
{
  "partner_id": "4",
  "entity_id": "d89d-d34fd-fddf45g8xc7-x8c7fddg"
}
400 Bad Request
{
  "errors": [
    {
      "type": "Bad Request",
      "message": "Missing required parameters in request body. Required: [\"field1\", \"field2\"]",
      "code": "6"
    }
  ]
}
422 Unprocessable entity
{
  "errors": [
    {
      "type": "Bad Request",
      "message": "Incorrect or malformed parameters",
      "code": "5"
    }
  ]
}

The endpoint will reply with an Entity ID and the Partner ID. You should save the Entity ID to your authentication database and associate it with the user's record.


Logging in a User

Once a Smart Engage User has been registered and an entity_id obtained, you can log in the user using the instructions below. For returning users, log them in via your normal method in your auth server, and then follow the instructions below. Create a new Login JWT, and use it as the accessToken in the createSession method.

JWT Header

Note the addition of the "auth" line.

Key Value Notes
auth v2 auth version to use, login uses v2
alg HS512 algorithm, HMAC SHA-512
typ JWT type
{
    "auth": "v2",
    "alg": "HS512",
    "typ": "JWT"
}

JWT Payload

Note the addition of the device_id.

Key Value Notes
rezolve_entity_id your_rezolve_entity_id use the entity_id you obtained during registration
partner_entity_id your_partner_entity_id set it to the unique identifier for your user record
exp 1520869470 Expiration, as a unix timestamp integer. Set the expiration value to a small number, now() + 30 minutes or less.
device_id wlkCDA2Hy/CfMqVAShslBAR/0sAiuRIUm5jOg0a An ID randomly generated upon app installation and stored. This ID is placed in both the JWT payload and x-header sent by the SDK. See below for generation instructions.
{
    "rezolve_entity_id": "entity123",
    "partner_entity_id": "partner_entity_id",
    "exp": 1520869470, 
    "device_id": "wlkCDA2Hy/CfMqVAShslBAR/0sAiuRIUm5jOg0a"
}

Signature

HMACSHA512(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    ${$partner_auth_key}
)

Sign the header and payload with the partner_auth_key .

Generating the device_id

iOS uses the iOS "identifierForVendor" string, where the SDK supplies this to the SDK for the x-header. You will need to use this same call to supply the device_id to your auth server for storage with the user profile.

UIDevice.current.identifierForVendor?.uuidString

Mall Flow & Merchants

A consumer can enter the Mall by clicking on a related navigation option within the mobile app. The Mall showcases active merchants in an attractive layout that is conducive to casual browsing and subsequent purchasing.

Once a Merchant is selected, the consumer shifts into Product/Category browsing mode. Once a product has been selected, purchasing follows one of the previous examples of Instant Buy Flow or Cart Buy Flow.

Get list of Merchants in the Mall

All SDK related Managers like merchantManager are already initialised for the developer. In order to get a list of Merchants, proceed by calling getMerchants, providing an implementation of result as a parameter. This will return an array of Merchant objects. Parse each Merchant object separately to get the id, name, tagline, banner, logo, and all other parameters that are useful in your implementation.

rezolveSession?.merchantManager.getMerchants { (result: Result<[Merchant], RezolveError>) in
    switch result {
    case .success(let merchants):
        {
            merchants.forEach {
                // Basic information
                let id = $0.id
                let name = $0.name
                let tagline = $0.tagline
                let termsAndConditions = $0.termsAndConditions

                // Assets
                let banner = $0.banner
                let logo = $0.logo
                let bannerThumbs = $0.bannerThumbs
                let logoThumbs = $0.logoThumbs
            }
        }

    case .failure(let error):
        // Error handling
    }
})

Get list of Merchant's top-level Categories

Display your subcategories and products as returned by the getRootCategoryForMerchantWith call. For subsequent navigation in categories, use getPaginatedCategoriesAndProducts .

let sampleMerchantId = "12"

rezolveSession?.productManager.getRootCategoryForMerchantWith(id: sampleMerchantId) { (result: Result) in
    switch result {
    case .success(let category):
            // Basic information
            let id = category.id
            let parentId = category.parentId
            let name = category.name
            let hasCategories = category.hasCategories
            let hasProducts = category.hasProducts

            // Assets
            let image = category.image
            let imageThumbs = category.imageThumbs

            // Get subcategories, if any
            if hasCategories {
                category.categories?.items.forEach { subCategory in
                    print(subCategory.id)
                    print(subCategory.parentId)
                    print(subCategory.name)

                    // ...
                }
            }

    case .failure(let error):
        // Error handling
    }
})

Accessing a Subcategory

As the consumer navigates the down the category tree, call getPaginatedCategoriesAndProducts to pull a paginated list of subcategories and products for that category.

let sampleMerchantId = "12"
let sampleCategoryId: Int = 70

let pageNavigationFilters = PageNavigationFilter(
    productsFilter: PageNavigation(count: 100, pageIndex: 1, sortBy: "product", sort: .ascending),
    categoryFilter: PageNavigation(count: 100, pageIndex: 1, sortBy: "category", sort: .ascending)
)

rezolveSession?.productManager.getPaginatedCategoriesAndProducts(merchantId: sampleMerchantId, categoryId: sampleCategoryId, pageNavigationFilters: pageNavigationFilters) { (result: Result) in
    switch result {
    case .success(let category):
            // Basic information
            let id = category.id
            let parentId = category.parentId
            let name = category.name
            let hasCategories = category.hasCategories
            let hasProducts = category.hasProducts

            // Assets
            let image = category.image
            let imageThumbs = category.imageThumbs

            // Get subcategories, if any
            if hasCategories {
                category.categories?.items.forEach { subCategory in
                    print(subCategory.id)
                    print(subCategory.parentId)
                    print(subCategory.name)

                    // ...
                }
            }

            // Get display products, if any
            if hasProducts {
                category.products.forEach { displayProduct in
                    print(displayProduct.id)
                    print(displayProduct.name)
                    print(displayProduct.price)

                    // ...
                }
            }

    case .failure(let error):
        // Error handling
    }
})

Merchant & Product Search

Search is available to aid in the presentation and usability of large malls and/or large product catalogs. You can search for Merchants, or for Products.

Merchant Search

Merchant search offers the following features:

  • List all Merchants sorted by Distance.
  • Lookup by Merchant name, and sort results by Distance.
  • Order results Ascending or Descending.
n order to get Merchants sorted by Distance, each Merchant in your ecosystem must configure at least one "Store" in the Merchant Portal, supplying longitude and latitude.

Search for a merchant requires the following parameters:

  • Query string - Optional. Leave it blank to get all Merchants. If more than one term is supplied, terms are matched on an AND basis.
  • Order By - Should merchants be sorted by .location.
  • Order - .ascending or .descending.
  • Location - Optional. The latitude and longitude of the consumer phone. Merchant list cannot be sorted by distance without this parameter.
  • Offset - For pagination, offset is the number of pages of results to skip. Setting this to zero gets the first page of results.
  • Limit - For pagination, the number of results to return per page.
let searchData = MerchantSearchData(
    query: nil,
    orderBy: .location,
    order: .ascending,
    deviceInfo: DeviceInfo.current,
    location: Location(longitude: -73.9502622, latitude: 40.6726499)
)
let pageOffset: Int = 1
let pageLimit: Int = 50

Now we are ready to combine our desired Query structure along with the actual API request:

rezolveSession?.merchantManager.searchForMerchants(using: searchData, page: pageOffset, limit: pageLimit) { (result: Result) in
    switch result {
    case .success(let result):
            // Current state
            print(result.merchants)
            print(result.page)
            print(result.total)

            result.merchants.forEach {
                // Base information
                print($0.id)
                print($0.name)
                print($0.tagline)
                print($0.priority)
                print($0.distance)

                // Assets
                print($0.banner)
                print($0.bannerThumbs)
                print($0.logo)
                print($0.logoThumbs)

                // List of Stores
                if let stores = $0.stores {
                    stores.forEach {
                        print($0.id)
                        print($0.name)
                        print($0.location)
                    }
                }

                // List of Terms & Conditions
                $0.termsAndConditions.forEach {
                    print($0.id)
                    print($0.storeId)
                    print($0.name)
                    print($0.content)
                    print($0.checkboxText)
                }
            }

    case .failure(let error):
        // Error handling
    }
}

Product Search

Product search offers the following features:

  • Search Products across all Merchants in your ecosystem
  • Search the Products of a single Merchant (by specifying a Merchant ID)
  • Sort results by search SCORE or PRICE
  • Order results Ascending or Descending.

To search for products you must supply the following parameters:

  • Query string - Optional. Leave it blank to get all Products. If more than one term is supplied, terms are matched on an AND basis.
  • Merchant ID - Optional. If supplied, restricts Product search to the specified Merchant.
  • Product Type - Can be .products, .acts, or .all.
  • Order By - Should products be sorted by .price.
  • Order - .ascending or .descending.
  • Location - Optional. The latitude and longitude of the consumer phone.
  • Offset - For pagination, offset is the number of pages to skip. Setting this to zero gets the first page of results.
  • Limit - For pagination, the number of results to return per page.
let searchData = ProductSearchData(
    query: "book",
    merchantId: "123",
    orderBy: .price,
    order: .descending,
    location: Location(longitude: -73.9502622, latitude: 40.6726499),
    includeInResults: .products,
    deviceInfo: DeviceInfo.current
)
let pageOffset: Int = 1
let pageLimit: Int = 50

Now we are ready to combine our desired Query structure along with the actual API request:

rezolveSession?.productManager.searchForProducts(using: searchData, page: pageOffset, limit: pageLimit, completionHandler: { (result: Result) in
    switch result {
    case .success(let result):
            // Current state
            print(result.products)
            print(result.page)
            print(result.total)

            result.products.forEach {
                // Base information
                print($0.id)
                print($0.merchantId)
                print($0.merchantName)
                print($0.categoryId)
                print($0.categoryName)
                print($0.price)
                print($0.isAct)
                print($0.isVirtual)
                print($0.hasRequiredOptions)

                // Assets
                print($0.image)
                print($0.imageThumbnails)
            }

    case .failure(let error):
        // Error handling
    }
})

Presenting a Product

Accessing a Product

When the consumer taps on a Product, call getProductDetails to fetch the product's information tree. At this point, the user can either add the product to their cart, tap "Buy now", or press "Back" to return to the Category view.

let sampleMerchantID = "12"
let sampleCategoryID: Int = 70
let sampleProductID = "6"
let sampleProduct = Product(id: sampleProductID)

rezolveSession?.productManager.getProductDetails(merchantId: sampleMerchantID, categoryId: sampleCategoryID, product: sampleProduct) { (result: Result) in
    switch result {
    case .success(let product):
        {
            print(product.id)
            print(product.merchantId)
            print(product.title)
            print(product.subtitle)
            print(product.price)
            print(product.description)

            product.images.forEach {
                print($0)
            }

            product.options?.forEach { option in
                print(option.label)
                print(option.code)
                print(option.extraInfo)

                option.values.forEach { optionValue in
                    print(optionValue.value)
                    print(optionValue.label)
                }
            }

            product.availableOptions?.forEach {
                $0.combination.forEach { variant in
                    print(variant.code)
                    print(variant.value)
                }
            }

            product.customOptions?.forEach {
                print($0.isRequire)
                print($0.optionId)
                print($0.sortOrder)
                print($0.title)
                print($0.optionType)

                $0.values.forEach { value in
                    print(value.sortOrder)
                    print(value.title)
                    print(value.valueId)
                }

                $0.valuesId.forEach { valueId in
                    print(valueId)
                }

                print($0.value)
            }

            print(product.productPlacement)
        }

    case .failure(let error):
        // Error handling
    }
})

Payment & Delivery

A necessary feature for any online shopping platform is management of the user's payment methods and their preferred delivery addresses. Payment cards are the default payment method supported by The SDK.

Managing Payment methods

All payment method management is handled by the rezolveSession's walletManager in the form of PaymentCard instances. A reminder, just like all other managers the walletManager is already initialised for the developer.

In order to add a payment method to the user's payment methods, proceed by calling walletManager's create, providing the payment method in the PaymentCard format as a parameter. Address must be created and its id must be assigned to PaymentCard before creating.

rezolveSession?.walletManager.create(paymentCard: cardToSave, completionHandler: { [weak self] (result: Result) in
   switch result {
      case .success(let card):
         // Handle success
    case .failure(let error):
        // Error handling
    }
})

In order to get a list of user's payment methods, proceed by calling walletManager's getAll.

rezolveSession?.walletManager.getAll(completionHandler: { [weak self] (result: Result<[PaymentCard], RezolveError>) in
   switch result {
      case .success(let cards):
         // Handle success
    case .failure(let error):
        // Error handling
    }
})

In order to get a specific payment method, proceed by calling walletManager's get, providing the payment method's id as a parameter.

let cardId = "1"
rezolveSession?.walletManager.get(id: cardId, completionHandler: { [weak self] (result: Result) in
   switch result {
      case .success(let card):
         // Handle success
    case .failure(let error):
        // Error handling
    }
})

In order to update a specific payment method, proceed by calling walletManager's update, providing the updated payment method in the PaymentCard format as a parameter.

rezolveSession?.walletManager.update(paymentCard: cardToUpdate, completionHandler: { [weak self] (result: Result) in
   switch result {
      case .success(let card):
         // Handle success
    case .failure(let error):
        // Error handling
    }
})

In order to delete a specific payment method, proceed by calling walletManager's delete, providing the payment method to delete in the PaymentCard format as a parameter.

rezolveSession?.walletManager.delete(paymentCard: cardToDelete, completionHandler: { [weak self] (result: Result) in
   switch result {
      case .success():
         // Handle success
    case .failure(let error):
        // Error handling
    }
})

Managing Delivery addresses

All address management is handled by the rezolveSession's addressbookManager in the form of Address instances. A reminder, just like all other managers the addressbookManager is already initialised for the developer.

In order to add a user address to the user's address book, proceed by calling addressbookManager's create, providing the address in the Address format as a parameter.

rezolveSession?.addressbookManager.create(address: address, completionHandler: { [weak self] (result: Result< Address, RezolveError>) in
   switch result {
      case .success(let address):
         // Handle success
    case .failure(let error):
        // Error handling
    }
})

In order to get a list of user's addresses, proceed by calling addressbookManager's getAll.

rezolveSession?.addressbookManager.getAll(completionHandler: { [weak self] (result: Result<[Address], RezolveError>) in
   switch result {
      case .success(let addresses):
         // Handle success
    case .failure(let error):
        // Error handling
    }
})

In order to get a specific address, proceed by calling addressbookManager's get, providing the address's id as a parameter.

let addressId = "42"
rezolveSession?.addressbookManager.get(id: addressId, completionHandler: { [weak self] (result: Result) in
   switch result {
      case .success(let address):
         // Handle success
      }
    case .failure(let error):
        // Error handling
    }
})

In order to update a specific address, proceed by calling addressbookManager's update, providing the updated address in the Address format as a parameter.

rezolveSession?.addressbookManager.update(address: addressToUpdate, completionHandler: { [weak self] (result: Result) in
   switch result {
      case .success(let address):
         // Handle success
    case .failure(let error):
        // Error handling
    }
})

In order to delete an address, proceed by calling addressbookManager's delete, providing the address to delete in the Address format as a parameter.

Warning: Address should not be used by credit card at the time of deleting

rezolveSession?.addressbookManager.delete(address: addressToDelete, completionHandler: { [weak self] (result: Result) in
   switch result {
      case .success():
         // Handle success
    case .failure(let error):
        // Error handling
    }
})

Managing Phone numbers

All phone number management is handled by the rezolveSession's phonebookManager in the form of Phone instances. A reminder, just like all other managers the phonebookManager is already initialised for the developer.

In order to add a phone number to the user's phone book, proceed by calling phonebookManager's create, providing the address in the Phone format as a parameter.

rezolveSession?.phonebookManager.create(phoneToCreate: phone, completionHandler: { [weak self] (result: Result) in
   switch result {
      case .success(let phone):
         // Handle success
    case .failure(let error):
        // Error handling
    }
})

In order to get a list of user's phone numbers, proceed by calling phonebookManager's getAll.

rezolveSession?.phonebookManager.getAll(completionHandler: { [weak self] (result: Result<[Phone], RezolveError>) in switch result { case .success(let phoneNumbers): // Handle success case .failure(let error): // Error handling } })

In order to update a specific phone number, proceed by calling phonebookManager's update, providing the updated phone number in the Phone format as a parameter.

rezolveSession?.phonebookManager.update(phone: phoneToUpdate, completionHandler: { [weak self] (result: Result) in
   switch result {
      case .success(let phone):
         // Handle success
    case .failure(let error):
        // Error handling
    }
})

In order to delete a phone number, proceed by calling phonebookManager's delete, providing the phone number to delete in the Phone format as a parameter.

rezolveSession?.phonebookManager.delete(phone: phoneToDelete, completionHandler: { [weak self] (result: Result) in
   switch result {
      case .success():
         // Handle success
    case .failure(let error):
        // Error handling
    }
})

Instant Buy Flow

Available Payment & Shipping options

Call paymentOptionManager to get Payment & Shipping options for the current Merchant. This tutorial assumes the consumer has chosen a form of Credit Card payment, and that the Product will be delivered to an Address.

You must repeat this SDK method if the user alters the product Variant (size, colour, etc), changes Product Quantity, changes Shipping choice, or changes Payment option.
let sampleCheckoutProduct = createCheckoutProductWithVariant(product: product)
let sampleMerchantID = "12"

rezolveSession?.paymentOptionManager.getPaymentOptionFor(checkoutProduct: sampleCheckoutProduct, merchantId: sampleMerchantID) { (result: Result) in
    switch result {
    case .success(let option):
        {
            // For this example we will assume that the user chooses the first option. Instead, we should display all available options and provide the ability to choose from a User Interface.
            let paymentMethod  = option.supportedPaymentMethods.first!
            let shippingMethod = option.supportedShippingMethods.first!
        }

    case .failure(let error):
        // Error handling
    }
})

Showing Payment Card choices

Use walletManager.getAll to list the available card choices. If the consumer wishes to buy, they will select a payment card to use, and provide confirmation of ordering intent.

We recommend using a "Slide to Buy" button to confirm purchase intent, while preserving the maximum ease of use.

rezolveSession?.walletManager.getAll { (result: Result<[PaymentCard], RezolveError>) in
    // Handle payment cards
}

Creating a Checkout Bundle & Order

Once you have Product information, create a CheckoutProduct object. Then call the SDK's CheckoutManagerV2.checkoutProduct method to create an Order and get Totals. The response Order object includes an Order ID, Order Total, and Price breakdowns.

let sampleProductCheckoutBundle = CheckoutBundle(
    checkoutProduct: checkoutProduct,
    shippingMethod: deliveryMethod,
    merchantId: merchantId,
    optionId: optionId,
    paymentMethod: paymentMethod,
    paymentRequest: nil,
    phoneId: phoneId,
    location: userLocation
)

rezolveSession?.checkoutManager.checkout(bundle: sampleProductCheckoutBundle) { (result: Result) in
    switch result {
    case .success(let order):
        {
            print(order.id)
            print(order.finalPrice)

            // ...
        }

    case .failure(let error):
        // Error handling
    }
})

Finalising the Order & Buying

When the user confirms intent, pass the Credit Card choice and the entered CVV value to the createPaymentRequest method. This creates an encrypted paymentRequest object needed for Checkout.

In this tutorial, we assume the user has chosen Credit Card payment. Note that paymentRequest is actually optional here, and can be null. To determine if it's needed, please check selected SupportedPaymentMethod's type.

Pass a paymentRequest object, checkoutBundleV2 object, the orderId, and an interface or callback to the buyProduct method. The success response will an OrderSummary object. Note that this does not mean the order was confirmed, only that the request was successfully received.

let paymentCard = // RezolveSDK.PaymentCard
let cardCVV = "000" // Card CVV

let sampleProductCheckoutBundle = CheckoutBundle(
    checkoutProduct: checkoutProduct,
    shippingMethod: deliveryMethod,
    merchantId: merchantId,
    optionId: optionId,
    paymentMethod: paymentMethod,
    paymentRequest: PaymentRequest(paymentCard: paymentCard, cvv: cardCVV),
    phoneId: phoneId,
    location: userLocation
)

rezolveSession?.checkoutManager.buy(bundle: sampleProductCheckoutBundle) { (result: Result) in
    switch result {
    case .success(let order):
        {
            print(order.id)
            print(order.partnerId)
            print(order.partnerName)

            // ...
        }

    case .failure(let error):
        // Error handling
    }
})

Cart Buy Flow

Add Product to the Cart

Call CheckoutManagerV2.addProductToCart to add the Product to Cart.

let sampleCheckoutProduct = createCheckoutProductWithVariant(product: product)
let sampleMerchantID = "12"

rezolveSession?.cartManager.createCartWithProduct(sampleCheckoutProduct, merchantId: sampleMerchantID) { (result: Result) in
    switch result {
    case .success(let cart):
        {
            print(cart.id)
            print(cart.merchantId)

            // ...
        }

    case .failure(let error):
        // Handle error gracefully
    }
})

Available Payment & Shipping options

Call paymentOptionManager to get Payment & Shipping options for the current Merchant. This tutorial assumes the consumer has chosen a form of Credit Card payment, and that the Product will be delivered to an Address.

You must repeat this SDK method if the user alters the product Variant (size, colour, etc), changes Product Quantity, changes Shipping choice, or changes Payment option.
let sampleMerchantID = "12"
let sampleCartID = "1"

rezolveSession?.paymentOptionManager.getPaymentOptionsForCartWith(merchantId: sampleMerchantID, cartId: sampleCartID) { (result: Result<[PaymentOption], RezolveError>) in
    switch result {
    case .success(let options):
        {
            // For this example we will assume that the user chooses the first option. Instead, we should display all available options and provide the ability to choose from a User Interface.
            let paymentMethod  = options.first?.supportedPaymentMethods.first
            let shippingMethod = options.first?.supportedShippingMethods.first
        }

    case .failure(let error):
        // Error handling
    }
})

Showing Payment Card choices

Use walletManager.getAll to list the available card choices. If the consumer wishes to buy, they will select a payment card to use, and provide confirmation of ordering intent.

We recommend using a "Slide to Buy" button to confirm purchase intent, while preserving the maximum ease of use.

rezolveSession?.walletManager.getAll { (result: Result<[PaymentCard], RezolveError>) in
    // Handle payment cards
}

Creating a Checkout Bundle & Order

Once you have Product information, create a CheckoutProduct object. Then call the SDK's CheckoutManagerV2.checkoutProduct method to create an Order and get Totals. The response Order object includes an Order ID, Order Total, and Price breakdowns.

let sampleCartCheckoutBundle = CheckoutBundle(
    cartId: cartId,
    shippingMethod: deliveryMethod,
    merchantId: merchantId,
    optionId: optionId,
    paymentMethod: paymentMethod,
    paymentRequest: nil,
    phoneId: phoneId,
    location: userLocation
)

rezolveSession?.checkoutManager.checkout(bundle: sampleCartCheckoutBundle) { (result: Result) in
    switch result {
    case .success(let order):
        {
            print(order.id)
            print(order.finalPrice)

            // ...
        }

    case .failure(let error):
        // Error handling
    }
})

Finalising the Order & Buying

When the user confirms intent, pass the Credit Card choice and the entered CVV value to the createPaymentRequest method. This creates an encrypted paymentRequest object needed for Checkout.

In this tutorial, we assume the user has chosen Credit Card payment. Note that paymentRequest is actually optional here, and can be null. To determine if it's needed, please check selected SupportedPaymentMethod's type.

Pass a paymentRequest object, checkoutBundleV2 object, the orderId, and an interface or callback to the buyProduct method. The success response will an OrderSummary object. Note that this does not mean the order was confirmed, only that the request was successfully received.

let paymentCard = // RezolveSDK.PaymentCard
let cardCVV = "000" // Card CVV

let sampleCartCheckoutBundle = CheckoutBundle(
    cartId: cartId,
    shippingMethod: deliveryMethod,
    merchantId: merchantId,
    optionId: optionId,
    paymentMethod: paymentMethod,
    paymentRequest: PaymentRequest(paymentCard: paymentCard, cvv: cardCVV),
    phoneId: phoneId,
    location: userLocation
)

rezolveSession?.checkoutManager.buy(bundle: sampleCartCheckoutBundle) { (result: Result) in
    switch result {
    case .success(let order):
        {
            print(order.id)
            print(order.partnerId)
            print(order.partnerName)

            // ...
        }

    case .failure(let error):
        // Error handling
    }
})

Click & Collect

Click and Collect enables users to select products online, and pick them up in-store. The consumer may either pay online, or pay in-store. The Click and Collect flow is no different from in the Cart Buy or Instant Buy examples, it is only the user-chosen values that change.

Please note that the commerce platfrom must have Pick Up In Store enabled under the Advanced menu, for this option to apply.

Choices returned by PaymentOptionManager

When you call PaymentOptionManager.getProductOptions, it returns a list of your Payment and Shipment options.

let sampleCheckoutProduct = createCheckoutProductWithVariant(product: product)
let sampleMerchantID = "12"

rezolveSession?.paymentOptionManager.getPaymentOptionFor(checkoutProduct: sampleCheckoutProduct, merchantId: sampleMerchantID) { (result: Result) in
    switch result {
    case .success(let option):
        {
            // For this example we will assume that the user chooses the first option. Instead, we should display all available options and provide the ability to choose from a User Interface.
            let paymentMethod  = option.supportedPaymentMethods.first!
            let shippingMethod = option.supportedDeliveryMethods.first!
        }

    case .failure(let error):
        // Error handling
    }
})

Creating the Delivery Unit/Delivery Method

DeliveryMethod has no need of an id for the object creation, so an empty String used to feel the addressId present in the constructor. See the extension_attributes node for this store id.

"extension_attributes": [ { "value": "2", "code": "pickup_store" } ]

Create the DeliveryMethod as follows:

// Standard Shipping example
let deliveryMethod = CheckoutShippingMethod(type: "flatrate", addressId: address.id)

// Click & Collect example
let deliveryMethod = CheckoutShippingMethod(type: "storepickup", pickupStore: store.pickupStore)

DeliveryMethod is then passed to create the CheckoutBundleV2, which is used to get totals before purchasing the product.


Understnading Acts

Getting and posting of acts is handled by rezolveSession's sspActManager in the form of SspAct instances. A reminder, just like all other managers the sspActManager is already initialised for the developer.

Acts, save for historic items which will be covered at the end of this page, cannot be fetched in bulk. In order to get an act, proceed by calling sspActManager's getAct method. Required parameters are the act's id and an optional parameter describing the act's image width. If no image exists this parameter is ignored by the developer.

rezolveSession?.sspActManager.getAct(actId: actId, completionHandler: { [weak self] (result: Result) in
   switch result {
      case .success(let act):
         // Handle success

         // Basic information         
         let scanId = act.scanId
         let id = act.id
         let dateCreated = act.dateCreated
         let dateUpdated = act.dateUpdated
         let status = act.status
         let name = act.name
         let shortDescription = act.shortDescription
         let longDescription = act.longDescription
         let merchantId = act.merchantId
         let ownerId = act.ownerId
         let images = act.images
         let questions = act.questions

         // Is buy act.
         let isBuy = act.isBuy

         // Is information act.
         let isInformationPage = act.isInformationPage

         // Act custom URL (used to resolve product/category using TriggerManager.resolve(url:)).
         let customUrl = act.customUrl

         // Form builder data. If an act is displayed through custom elements those elements will be available here.
         let pageBuildingBlocks: [PageElement]?
      }
    case .failure(let error):
        // Error handling
    }
})

In order to post an act proceed by calling sspActManager's submitAct method, providing the act's id and act data in SspActSubmission format as parameters. Please note that any answers to relevant act questions are a part of the act data in SspSubmissionAnswer format.

rezolveSession?.sspActManager.submitAct(actId: actId, actSubmission: sspActSubmission, completionHandler: { [weak self] (result: Result< SspActSubmission, RezolveError>) in
   switch result {
      case .success(let actSubmission):
         // Handle success
      }
    case .failure(let error):
        // Error handling
    }
})

In order to fetch historical act submissions by the user proceed by calling sspActManager's submittedActs method, while providing the entity id as a parameter. The request will return a SspActSubmissionBundle instance, while the submitted acts are the data property of this object.

Please note that answers in the act data are in SspSubmissionAnswer format. Meaning the text of the question is unavailable until the developer fetches the act. To get the question texts, the developer has to call the above-mentioned getAct with an adequate act id.

rezolveSession?.sspActManager.submittedActs(entityId: entityId, completionHandler: { [weak self] (result: Result< SspActSubmissionBundle, RezolveError>) in
   switch result {
      case .success(let bundle):
         let submittedActs = bundle.data
         // Handle success
         submittedActs.forEach {
                // User information
                let userId = $0.userId
                let entityId = $0.entityId
                let userName = $0.userName
                let personTitle = $0.personTitle
                let firstName = $0.firstName
                let lastName = $0.lastName
                let email = $0.email
                let phone = $0.phone

                // Submitted act information
                let scanId = $0.scanId
                let latitude = $0.latitude
                let longitude = $0.longitude
                let answers = $0.answers
                let actName = $0.actName
                let hasCoupons = $0.hasCoupons
            }
      }
    case .failure(let error):
        // Error handling
    }
})

Location Triggers

One of the most powerful tools you can take advantage of with Smart Engage is Location Triggers. It keeps a reference of the user's location, and through a decision making mechanism it sends unobtrusive Push Notifications at the right time, thus enticing customers into an action when it is deemed proper.

Initialising

The entry point of Location Triggers is run once, in order to inform the backend about the user's essential information.

let checkInPayload = RXPCheckIn(
    applicationId: APP_BUNDLE,
    version: APP_VERSION,
    os: "IOS",
    pushToken: APP_PUSH_TOKEN,
    pushTokenType: tokenType,
    apiKey: REZOLVE_API_KEY
)
rezolveSession?.rxpManager.requestCheckIn(checkInPayload: checkInPayload, completionHandler: { (result: Result) in
    switch result {
    case .success(let success):
        print("[RXP] Access token success -> \(success.sessionAccessToken)")
    case .failure(let error):
        // Error handling
    }
})
The pushTokenType can be either "APN_DEBUG" or "APN_RELEASE". This decision is based on whether you are running the application in Xcode's debugging mode, or from the AppStore/TestFlight.

Getting available Interest Tags

With the current user already in check-in state, as seen in the previous section, we can now fetch the state of the user's Interest Tags. An array of RXPTag models will be provided as a response to this request, indicating the following parameters for each item:

  • tagId A unique identifier for this item
  • name Descriptive name of the Interest Tag
  • state Indicates whether the Tag is "UNDEFINED", "ENABLED" or "DISABLED" for this user
rezolveSession?.rxpManager.getAllTags(completionHandler: { (result: Result<[RXPTag], RezolveError>) in
    switch result {
    case .success(let models):
        print("[RXP] Tags success -> \(models)")
    case .failure(let error):
        // Error handling
    }
})

Updating Interest Tags

The defining step that subscribes your audience into your established Location Triggers is updating the user's chosen Interest Tags, as seen on the following example:

let tags = RXPSelectedTagList(tags: [
    RXPSelectedTag(tagId: 1, enabled: true),
    ...
    ...
])
rezolveSession?.rxpManager.updateUserTags(tagsPayload: tags, completionHandler: { (result: Result<[RXPTag], RezolveError>) in
    switch result {
    case .success(let models):
        print("[RXP] Tags update success -> \(models)")
    case .failure(let error):
        // Error handling
    }
})

Invoking Location Tracking

After the user has identified to the backend service, and his/her Interest Tags are set, the last step would be to engage Location Tracking. Following, is an example of a class that would handle startMonitoring, stopMonitoring the user's location at will:

class RezolveLocationService {
    var rezolveSsp: RezolveSsp?
    
    private var nearbyEngagementsManager: NearbyEngagementsManager? {
        return rezolveSsp?.nearbyEngagementsManager
    }
    
    init() {
        // Ask permission to show Push Notifications
    }
    
    func startMonitoring() {
        nearbyEngagementsManager?.startMonitoringForNearbyEngagements()
        nearbyEngagementsManager?.delegate = self
    }
    
    func stopMonitoring() {
        nearbyEngagementsManager?.stopMonitoringForNearbyEngagements()
        nearbyEngagementsManager?.stopUpdatingDistanceFromNearbyEngagements()
        nearbyEngagementsManager?.resetNotificationSuppressData()
        nearbyEngagementsManager?.delegate = nil
    }
}

You can initialise this class at the point where the SDK's rezolveSdk?.createSession is finished:

extension RezolveLocationService: NearbyEngagementsManagerDelegate {
    func didStartMonitoring(for circularRegions: [CLCircularRegion], coordinate: CLLocationCoordinate2D, radius: Int) {
        // Informs that the SDK has started monitoring regions
    }
    
    func didEnter(circularRegion: GeofenceData) {
        // The user has entered in region with data `circularRegion`
    }
    
    func didExit(circularRegion: GeofenceData) {
        // The user has left region with data `circularRegion`
    }
    
    func didUpdateCurrentDistanceFrom(location: CLLocationCoordinate2D, geofences: [GeofenceData], beacons: [BeaconData]) {
        // The user updated their current distance from regions
    }
    
    func didReceiveInAppNotification(engagement: EngagementNotification) {
        // Whilst Push Notifications are not allowed, this can function as a fallback
    }
    
    func didFail(with error: Error) {
        // Error handling
    }
}

Scanning Advertisements

One of the most engaging features of Smart Engage is transforming plain advertisements into Shoppable Ads. The user can now use Smart Engage to scan an advertisement that is enriched with Smart Engage's watermark, in order to get presented with one of the following:

  • Merchant landing page
  • Subcategories
  • Promoted Product
  • Interactive Act or Promotion
  • Website

Prerequisites

You will need to add the NSCameraUsageDescription permission into the Info.plist file of your application. A typical explanation of why your Smart Engage-enabled application is asking for this permission would be Allow camera usage for scanning visual engagements. Feel free to add more detail to it, if conditions demand it.

Invoking the ScanManager

Create a designated ViewController to handle the Camera Scan integration. Initialise scanManager, and enable the scan screen using session.startVideoScan().

guard let scanManager = rezolveSession?.getScanManager() else {
    return
}

scanManager.productResultDelegate = self
try? scanManager.startVideoScan(scanCameraView: self.view as! ScanCameraView, rectOfInterest: .frame)

When an image is scanned, the delegation methods of ProductScan will contain all information regarding the engagement that was resolved.

extension ViewController: ProductDelegate {

    func onStartRecognizeImage() {
        // Suggestion: Show an interstitial loader
    }

    func onFinishRecognizeImage() {
        // Suggestion: Hide loader
    }

    func onProductResult(product: Product) {
        // The user has scanned an image that resolved into a `Product`
    }

    func onCategoryResult(merchantId: String, category: Rezolve.Category) {
        // The user has scanned an image that resolved into a `Rezolve.Category`
    }

    func onSspEngagementResult(engagement: ResolverEngagement) {
        // The user has scanned an image that resolved into a `ResolverEngagement`
    }

    func onError(error: String) {
        // Error handling
    }

    func onCategoryProductsResult(merchantId: String, category: Category, productsPage: PageResult) {
        //The user has scanned an image that resolved into RezolveSDK.PageResult
    }
    
    func onInactiveEngagement(payload: RezolveCustomPayload) {
        // Called when eangagement is inactive 
    }
}

Handling Scan results

After the delegation methods have received their designated data objects, refer to the following documentation sections for more information:

  • Learn how to present a Product
  • Present a Rezolve.Category
  • Handle the contents of ResolverEngagement

User Activity

In order to present information about user's activities throughout the app, you will need to use the Activity Manager. It provides insights on purchase history, shipping method, delivery address, total product pricing, and other useful details. Your search criteria depends entirely on providing a Date object, so you can get history items based on specific timeframes.

User Activity Manager

The following features are available:

  • Get all available orders.
  • Get transaction history items for a time period.
  • Get transaction history items with the provided identifier.
  • Post a history item.
All SDK related Managers like userActivityManager are already initialised for the developer.

Get orders from server

To fetch the list of orders from the server, call the getOrders function.

  • Parameters:
    • date: Optional date. Defaults to the date/time the method is called.
    • completionHandler: A closure to be called when the task is completed.

Result will be an array of HistoryTransactionDetails in case of success, or an error if it failed.

rezolveSession?.userActivityManager.getOrders() { (result: Result<[HistoryTransactionDetails], RezolveError>) in
    switch result {
    case .success(let orders):
        {
            orders.forEach {
                // Order's information
                let status = $0.status
                let shippingMethod = $0.shippingMethod
                let shippingAddress = $0.shippingAddress
                let storeDetails = $0.storeDetails
                let price = $0.price
                let paymentMethod = $0.paymentMethod
                let partnerId = $0.partnerId
                let orderId = $0.orderId
                let location = $0.location
                let items = $0.items

                // Merchant's information
                let merchantPhone = $0.merchantPhone
                let merchantName = $0.merchantName
                let merchantId = $0.merchantId
                let merchantEmail = $0.merchantEmail
                let merchantPhone = $0.merchantPhone
                let merchantName = $0.merchantName

                // Client's information
                let firstName = $0.firstName
                let lastName = $0.lastName
                let phone = $0.phone
                let email = $0.email
                let billingAddress = $0.billingAddress

                // Extra information
                let additionalAttributes = $0.additionalAttributes
            }
        }

    case .failure(let error):
        // Error handling
    }
})

Description of the fields for the HistoryTransactionDetails model:

  • Parameters:
    • timestamp: Order placement timestamp.
    • status: Order's status.
    • shippingMethod: Order's shipping method.
    • shippingAddress: Order's shipping address. *Optional
    • storeDetails: Order's store details. *Optional
    • price: Order's price.
    • phone: Client's phone.
    • paymentMethod: Payment method used at order placement.
    • partnerId: Partner's Id.
    • orderId: Order's id.
    • merchantPhone: Merchant's phone.
    • merchantName: Merchant's name.
    • merchantId: Merchant's id.
    • merchantEmail: Merchant's email.
    • lastName: Client's last name. *Optional
    • merchantId: Products being part of an order.
    • location: Optional order placement location. *Optional
    • firstName: Client's first name. *Optional
    • email: Client's email. *Optional
    • billingAddress: Client's billing address. *Optional
    • additionalAttributes: Additional details. *Optional

Get transaction history items for a time period

To fetch a list of historical transactions from the server, call the getHistoryItems function.

  • Parameters:
    • from: Optional initial date.
    • to: Optional end date.
    • completionHandler: A closure to be called when the task is completed.

Result will be an array of HistoryItem in case of success, or an error if it failed.

rezolveSession?.userActivityManager.getHistoryItems() { (result: Result<[HistoryItem], RezolveError>) in
    switch result {
    case .success(let orders):
        {
            orders.forEach {
                // Information
                let id = $0.id
                let catalogName = $0.catalogName
                let productName = $0.productName
                let merchantName = $0.merchantName
                let type = $0.type
                let partnerId = $0.partnerId
                let merchantId = $0.merchantId
                let entityId = $0.entityId
                let catalogId = $0.catalogId
                let productId = $0.productId
                let location = $0.location
            }
        }

    case .failure(let error):
        // Error handling
    }
})

Description of the fields for the HistoryItem model:

  • Parameters:
    • id: The history item identifier.
    • catalogName: The catalog name. *Optional
    • productName: The product name. *Optional
    • merchantName: The merchant name.
    • type: The type.
    • partnerId: The partner identifier.
    • merchantId: The merchant identifier.
    • entityId: The entity identifier. *Optional
    • catalogId: The catalog identifier.
    • productId: The product identifier. *Optional
    • location: The location.

Get transaction history items with the provided identifier

To fetch a list of historical transactions from the server, call the getHistoryItems function.

  • Parameters:
    • id: The scan identifier.
    • completionHandler: A closure to be called when the task is completed.

Result will be an array of HistoryItem in case of success, or an error if it failed.

rezolveSession?.userActivityManager.getHistoryItems() { (result: Result<[HistoryItem], RezolveError>) in
    switch result {
    case .success(let orders):
        {
            orders.forEach {
                // Information
                let id = $0.id
                let catalogName = $0.catalogName
                let productName = $0.productName
                let merchantName = $0.merchantName
                let type = $0.type
                let partnerId = $0.partnerId
                let merchantId = $0.merchantId
                let entityId = $0.entityId
                let catalogId = $0.catalogId
                let productId = $0.productId
                let location = $0.location
            }
        }

    case .failure(let error):
        // Error handling
    }
})

Description of the fields for the HistoryItem model:

  • Parameters:
    • id: The history item identifier.
    • catalogName: The catalog name. *Optional
    • productName: The product name. *Optional
    • merchantName: The merchant name.
    • type: The type.
    • partnerId: The partner identifier.
    • merchantId: The merchant identifier.
    • entityId: The entity identifier. *Optional
    • catalogId: The catalog identifier.
    • productId: The product identifier. *Optional
    • location: The location.

Post a history item

To post a history item to server, call the postHistoryItem function.

  • Parameters:
    • historyItem: The history item to be posted.
    • completionHandler: A closure to be called when the task is completed.

Result will be an array of HistoryItem in case of success, or an error if it failed.

let historyItem = HistoryItem(...)

rezolveSession?.userActivityManager.postHistoryItem(historyItem) { (result: Result) in
    switch result {
    case .success(let historyItem):
        {
            // Information
            let id = historyItem.id
            let catalogName = historyItem.catalogName
            let productName = historyItem.productName
            let merchantName = historyItem.merchantName
            let type = historyItem.type
            let partnerId = historyItem.partnerId
            let merchantId = historyItem.merchantId
            let entityId = historyItem.entityId
            let catalogId = historyItem.catalogId
            let productId = historyItem.productId
            let location = historyItem.location
        }

    case .failure(let error):
        // Error handling
    }
})

Description of the fields for the HistoryItem model:

  • Parameters:
    • id: The history item identifier.
    • catalogName: The catalog name. *Optional
    • productName: The product name. *Optional
    • merchantName: The merchant name.
    • type: The type.
    • partnerId: The partner identifier.
    • merchantId: The merchant identifier.
    • entityId: The entity identifier. *Optional
    • catalogId: The catalog identifier.
    • productId: The product identifier. *Optional
    • location: The location.

Create a guest user

In some applications, it's useful to have a guest User object to pass around even before the (human) user has registered or logged in. Normally, you want this guest user to persist as long as the session persists.

The registration of the guest

Our approach is to create a guest user object in the database and store its data in session.

Result will be an object GuestUser in case of success, or an error if it failed.

struct GuestUser: Codable {
    let username: String
    let email: String?
    let phone: String?
    let sdkEntity: String
    let sdkPartner: String
}

func guestRegister(success: @escaping (String) -> Void, error: @escaping (Error) -> Void) {
        let headers = [
            "x-rezolve-partner-apikey": REZOLVE_API_KEY
        ]
        
        let request = Alamofire.request(
            "...register/guest...",
            method: .post,
            parameters: [:],
            encoding: JSONEncoding.default,
            headers: headers
        )
        
        request.responseJSON { response in
            guard response.result.isSuccess else {
                error(.failedRegistrationResponse)
                return
            }
            
            guard
                let data = response.data,
                let guestUserInfo = try? JSONDecoder().decode(GuestUser.self, from: data) else {
                error(.failedRegistrationDataDecoding)
                return
            }
            success(guestUserInfo.sdkEntity)
        }
    }

The Log in of the guest

When (and if) the guest registers, we log in the guest user. We need the sdkEntity from the previous query.

Result will be an object GuestUser in case of success, or an error if it failed.

struct GuestUser: Codable {
    let username: String
    let email: String?
    let phone: String?
    let sdkEntity: String
    let sdkPartner: String
}

struct GuestUserCredentials: Codable {
    let entityId: String
    let deviceId: String
}

func guestLogin(entityId: String, success: @escaping (GuestUser) -> Void, error: @escaping (Error) -> Void) {
        var data: [String: String]? {
            if let data = try? JSONEncoder().encode(
                    GuestUserCredentials(
                        entityId: entityId,
                        deviceId: Device().id
                    )
            ) {
                return try? JSONSerialization.jsonObject(with: data, options: []) as? [String: String]
            }
            return nil
        }
        
        let headers = [
            "x-rezolve-partner-apikey": REZOLVE_API_KEY
        ]
        
        let request = Alamofire.request(
            "...login/guest...",
            method: .post,
            parameters: data,
            encoding: JSONEncoding.default,
            headers: headers
        )
        
        request.responseJSON { response in
            guard response.result.isSuccess else {
                error(.failedLoginResponse)
                return
            }
            guard
                let data = response.data,
                let guestUserInfo = try? JSONDecoder().decode(GuestUser.self, from: data) else {
                error(.failedLoginDataDecoding)
                return
            }
            
            // Auth information 
            let jwt = response.response?.allHeaderFields["Authorization"] as? String
            let refreshToken = response.response?.allHeaderFields["Refresh-Token"] as? String

            success(guestUserInfo)
        }
    }

Pairing a guest with a logged-in user.

To connect a guest to a logged-in user, we need to send information to the API such as the device ID number. Guests can also create their own accounts by registering themselves on the special page. The action steps will be the same in this case.

func signIn(successCallback: @escaping (String?, String?) -> Void, errorCallback: @escaping (Error?, Int?) -> Void) {
   let data: [String: String] = {
        return [
            "username": "user",
            "password": "password",
            "deviceId": Device().id
        ]
    }()
        
    let headers = [
        "x-rezolve-partner-apikey": REZOLVE_API_KEY
    ]

    let request = Alamofire.request(.../credentials/login...,
        method: .post,
        parameters: data,
        encoding: JSONEncoding.default,
        headers: headers)
        
    request.responseJSON { requestResult in
        if(requestResult.response?.statusCode == 200) {
            // Auth information 
            let jwt = response.response?.allHeaderFields["Authorization"] as? String
            let refreshToken = response.response?.allHeaderFields["Refresh-Token"] as? String

            if let json = requestResult.result.value as? [String: Any] {
                let entityId: String? = json["sdkEntity"] as? String
                let partnerId: String? = json["sdkPartner"] as? String
                successCallback(entityId, partnerId)
            }
        } else {
           errorCallback(requestResult.error, requestResult.response?.statusCode)
        }
    }
}