Shopping Integration

Stories created in JOIN Studio can include shopping CTAs such as Add to Cart, See Cart, and Shopping Redirect. On iOS, these are delivered through the JOINStoriesListenerDelegate. Implement the callbacks in your app and wire them via JOINStoriesListener.setDelegate.

Events and callback signatures

  • Add to Cart
    • Signature:
public protocol JOINStoriesListenerDelegate: AnyObject {
    // ...
    func joinStoriesAnalyticsCallback(event: JOINTrackingEvent, payload: [String: Any?])
    
    func addToCart(offerId: String, completion: @escaping (Bool) -> Void)
    
    func seeToCart()
    
    func shoppingRedirect(offerId: String?)
}
  • Payload: offerId (may be an empty string when not provided by content)

  • Completion: call completion(true) on success or completion(false) on failure so the SDK can update its UI (e.g., stop loader, show confirmation)

  • Typical use: add the item to your app cart, update UI, optionally dismiss the player

  • See Cart

    • Signature: func seeToCart()
    • Payload: none
    • Typical use: dismiss the player and navigate to your app’s cart screen
  • Shopping Redirect

    • Signature: func shoppingRedirect(offerId: String?)
    • Payload: offerId (may be empty or nil)
    • Typical use: open a PDP or handle a deep link inside your app; if external, route with your web/deep-link handler

Notes

  • Delivery: callbacks are invoked on the main thread.
  • Acknowledgment: for Add to Cart, you must invoke completion(Bool) to acknowledge completion; you do not return a value from the method itself.
  • Robustness: handle missing or empty offerId gracefully.
  • Analytics: if needed, observe joinStoriesAnalyticsCallback(event:payload:) for additional tracking.

Wiring the delegate

Set your delegate as early as convenient (e.g., AppDelegate or scene start):

public final class JOINStoriesListener {
    public static func setDelegate(delegate: JOINStoriesListenerDelegate?) {
        Listener.shared.setDelegate(delegate: delegate)
    }
}

Example:

import UIKit
import JOINStoriesSDK

@main
class AppDelegate: UIResponder, UIApplicationDelegate, JOINStoriesListenerDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        JOINStories.initSDK(teamId: "YOUR_TEAM_ID")
        JOINStoriesListener.setDelegate(delegate: self)
        return true
    }

    // MARK: Shopping callbacks
    func addToCart(offerId: String, completion: @escaping (Bool) -> Void) {
        Cart.shared.add(offerId: offerId) { success in
            completion(success)
            if success {
                // Optional UX: toast/snackbar
            }
        }
    }

    func seeToCart() {
        JOINStories.dismissPlayer() // avoid overlapping UIs
        Router.shared.navigate(to: .cart)
    }

    func shoppingRedirect(offerId: String?) {
        guard let id = offerId, !id.isEmpty else {
            return
        }
        Router.shared.navigate(to: .product(id: id))
    }

    func joinStoriesAnalyticsCallback(event: JOINTrackingEvent, payload: [String : Any?]) {
        Analytics.log("join_\(event)", payload)
    }
}

Best practices

  • Keep UI responsive: do not block the main thread; show lightweight toasts/snackbars and offload heavy work to async code.
  • Dismiss before navigation: if a story player is visible, call JOINStories.dismissPlayer() before pushing a new view controller to avoid overlapping UIs.
  • Error handling: if cart updates fail, communicate failure to the user and allow retry; always call completion(false) on failure.
  • Testing: verify that each CTA set in JOIN Studio triggers the expected callback and that empty or unknown offerId values are handled safely.