Skip to main content
Version: 5.6

Supplier Initialisation

Supplier minimum requirement

You are a retailer and you have your own groceries list that need to be synchronized with the Mealz groceries list.

SupplierKey - base64 key to set information

This will be provided by Mealz during implementation. This will include information such as PROD vs UAT & your unique company identifier.

If you just want to play around, you can use the sample Mealz Store:

let supplierKey = "ewoJInN1cHBsaWVyX2lkIjogIjE0IiwKCSJwbGF1c2libGVfZG9tYWluZSI6ICJtaWFtLnRlc3QiLAoJIm1pYW1fb3JpZ2luIjogIm1pYW0iLAoJIm9yaWdpbiI6ICJtaWFtIiwKCSJtaWFtX2Vudmlyb25tZW50IjogIlVBVCIKfQ=="

which you'll see decodes from base 64 to this:

{
"supplier_id": "14",
"plausible_domaine": "miam.test",
"miam_origin": "miam",
"origin": "miam",
"miam_environment": "UAT"
}
tip

Your keys will be provided by our Development team

Basic implementation

There are several options to configure to handle authless users. Here is a basic implemtation where the Mealz initialization process will only start after the user is logged in and has selected a valid store. It will then need to complete the basket synchronization

First to init Mealz we need your application context and also your supplier key

note

Two key have been send to you: one for developement and one for production.

public class MealzManager: ObservableObject {
public static let sharedInstance = MealzManager()

private init() {

Mealz.shared.Core(init: { coreBuilder in
// set provider key
coreBuilder.sdkRequirement(init: { requirementBuilder in
requirementBuilder.key = self.providerKey
})
})
}
}

User setup

Here is how to pass the user ID to the SDK, directly within the host app:

info

You can also enable authless feature, more informations here.

// From anywhere
import mealzcore

// existingUserId is your user id, type String is expected
Mealz.shared.user.updateUserId(userId: existingUserId, authorization: Authorization.userId)

Here is how to inform the SDK whenever the user login state changes. We recommend using Observables or EventListeners to that end.

// file MealzManager.swift
import mealzcore

public class MealzManager {
// CODE

private init() {
// CODE

OBSERVABLE_ON_USER_OBJECT.sink { _ in
// existingUserId is your user id, type String is expected
Mealz.shared.user.updateUserId(userId: existingUserId, authorization: Authorization.userId)
}
}
// CODE
}
info

To get full list of user feature check User configurations.

Store setup

For Mealz to work properly, your user must be connected to a specific store so we can accurately provide recipes with available ingredients. To add the store that the user is currently at, you can use this code:

// From anywhere
import mealzcore

// STORE_ID_IN_HOST_APP is your store id, type String is expected
Mealz.shared.user.setStoreIdWithCallBack(storeId: STORE_ID_IN_HOST_APP){ _ in /** action to execute one mealz store has been fetch and set can be a redirection */}

Basket synchronization Setup

Most importantly, the Mealz Basket must be kept up to date with your in-app basket. The SDK handles this complex logic by translating between Mealz Products & your real products. This mechanism is mandatory -> otherwise products will not be able to be added nor deleted properly between the two stores.

For example, when a user adds a Mealz Recipe to their cart, they are adding Mealz products to their basket. We then send them to you, the retailer, to be translated into your products & then added to your basket.

Likewise, when a product is deleted from your basket, the retailer, we listen for updates. When we notice a product used in a Mealz Recipe was deleted, we update our basket to reflect that change as well.

tip

If at some point, you feel like products magically disappear from Mealz recipes, or are not removed from the app basket while they should be, this is probably related to this part.

Helper Function to map between products

More than likely, your Product object will be quite different than ours so we have created a bridge model called SupplierProduct. Because of this, we recommend creating a helper function or two to translate between your object & the bridge. This will make your code easier to understand.

private func pretendProductsToRetailerProducts(
products: [PretendProduct]
) -> [SupplierProduct] {
return products.map {
return SupplierProduct(
id: $0.id,
quantity: Int32($0.quantity),
name: $0.name,
productIdentifier: nil,
imageURL: $0.imageUrl
)
}
}

BasketSubscriber & BasketListener

We have two protocols BasketSubscriber & BasketListener to coordinate the two baskets together. As the name suggestions, the listeners listens for updates from your basket. The subscriber allows you to listen.

We think it's best to start to create a new class that implements our new protocols. You can copy this & change the name:

class YourNewBasketService: BasketSubscriber, BasketPublisher {
var initialValue: [SupplierProduct]

func receive(event: [SupplierProduct]) {
<#code#>
}

func onBasketUpdate(sendUpdateToSDK: @escaping ([SupplierProduct]) -> Void) {
<#code#>
}
}
info

IMPORTANT: For all upcoming code examples, we will use PretendProduct to replicate YOUR product. In your implementation, use your actual Product class.

onBasketUpdate

Now, we need a way to stream the updates from the Mealz Basket to your own. We recommend using an observable. From there, we can sink your basket (in this case, the ExampleState) with the updates from Mealz.

Here's our example:

import Combine

private var cancellable: AnyCancellable? // used to create stream between mealz basket & our own

func onBasketUpdate(sendUpdateToSDK: @escaping ([SupplierProduct]) -> Void) {
cancellable = PretendBasket.shared.$items.sink { receiveValue in
`sendUpdateToSDK`(
self.pretendProductsToRetailerProducts(products: receiveValue)
)
}
}

initialValue

When Mealz is launching, we need to know the state of the current customer basket. Whenever this basket changes, we will refresh the Mealz SDK.

In our implementation, we create an initializer to pass our DemoProducts into before translating them. Here is our code:

init(initialBasketList: [PretendProduct]) {
self.initialValue = [] // First, initialize all properties as [] so we can use pretendProductsToRetailerProducts

if initialBasketList.count > 0 {
// Now convert (safely) if we have products
self.initialValue = pretendProductsToRetailerProducts(products: initialBasketList)
}
}

So now in our MealzManager, we can call:

let demoBasketService = DemoBasketService(initialBasketList: PretendBasket.shared.items)
danger

IMPORTANT: passing an empty list to Mealz SDK on initialization when there are actual products in the basket will lead to unexpected side effects. If this is done, products in the user's basket will appear as removed by the user in the Mealz basket.

receive

The last step of the basketService is to implement receive. This will make sure both baskets are connected & up to date.

You'll need to create a new function, ours is named updateBasketFromExternalSource that accepts a list of SupplierProducts & updates your basket. The function will iterate through the new products, checking if they are in your basket. If yes, they will either delete them (if the quantity is 0), or update their quantity & info. If no, they will be added to your basket.

info

IMPORTANT: you'll want to create a local temporary list inside this function so that you can edit your basket all at once. If you update your basket with each product iteration, you will have unexpected behavior (& increase time complexity as you'll be iterating through a growing list).

Here is our example:

private func updateBasketFromExternalSource(products: [SupplierProduct]) {
// we need to update the basket all at once, otherwise we will have issues with Mealz updating too frequently
var basketCopy = PretendBasket.shared.items

for product in products {
// check if we already have the product to remove or update info
if let productToUpdateIndex = PretendBasket.shared.items.firstIndex(where: { $0.id == product.id }) {
if product.quantity == 0 { // we know an item is deleted if the qty is 0
if basketCopy.indices.contains(productToUpdateIndex) {
basketCopy.remove(at: productToUpdateIndex)
}
} else {
let item = PretendBasket.shared.items[productToUpdateIndex]
basketCopy[productToUpdateIndex] = PretendProduct( // your product
id: product.id,
name: product.name ?? item.name,
quantity: product.quantity,
imageUrl: product.imageUrl ?? item.imageUrl)
}
} else { // otherwise add it to the client basket
let newProduct = PretendProduct( // your product
id: product.id,
name: product.name ?? "product",
quantity: Int(product.quantity),
imageUrl: product.imageURL
)
basketCopy.append(newProduct)
}
}
// update your basket after all operations
PretendBasket.shared.items = basketCopy
}

and then simply:

func receive(event: [SupplierProduct]) {
updateBasketFromExternalSource(products: event)
}

Putting it all together

let demoBasketService = DemoBasketService(initialBasketList: PretendBasket.shared.items)

Mealz.shared.Core(init: { coreBuilder in
// set supplier key
coreBuilder.sdkRequirement(init: { requirementBuilder in
requirementBuilder.key = supplierKey
})
// set listeners & notifiers
coreBuilder.subscriptions(init: { subscriptionBuilder in
subscriptionBuilder.basket(init: { basketSubscriptionBuilder in
// subscribe
basketSubscriptionBuilder.subscribe(subscriber: demoBasketService)
// push updates
basketSubscriptionBuilder.register(publisher: demoBasketService)
})
})
})

Checkout

There are two functions that need to be called during the checkout experience. Mealz does not collect payment, but we must reset our basket & lodge the transaction to share with your commerical team. Checkout is a crucial part of the Mealz experience.

Payment Started

When your use selects "Buy now", or when the payment tunnel is begun, you must call paymentStarted.

Mealz.shared.basket.paymentStarted(totalAmount: <#T##Double#>, totalProductCount: <#T##Int?#>, clientOrderId: <#T##String?#>)

This will save the payment information on our end & allow us to cross reference with your team for every transaction Mealz is involved with. Internally, we do NOT track payments where the value of products added by Mealz is 0, so you do not need to implement this logic.

Payment Finished

When your payment tunnel is over, & you've returned a "Payment Success", you must call paymentFinished.

danger

It is of crucial importance that you call this function BEFORE clearing your basket and pass the correct values for the totalAmount & totalProductCount. Please ensure these are the values of the transaction, NOT the values after the transaction (probably an empty basket). If you clear your basket, then call this method, all products will be considered as deleted so the ratio of products added by Mealz will not be correct.

Mealz.shared.basket.paymentFinished(totalAmount: <#T##Double#>, totalProductCount: <#T##Int?#>, clientOrderId: <#T##String?#>)

This will clear the basket & lodge the products as purchased, especially with regard to our AI model. The more a recipe is purchased, the more confident we are in the model & products associated. Further, we also can use this data for analytics purposes to see which recipes are most frequently purchased.

This should be called directly before the user pays & is mandatory.

Congratulations, Mealz is good to go 🥳