iOS SDK
O SDK iOS do smart checkout permite integrar a experiência de pagamento de forma nativa, proporcionando maior controle sobre a jornada do usuário, melhor performance e suporte a recursos avançados como carteiras digitais e deep links.
🚀 Principais recursos
- Callbacks para sucesso, erro e fechamento.
- Compatível com Apple Pay.
- Personalização visual do bottom sheet.
- Suporte a deep link.
📋 Requisitos
- iOS 13.4 ou superior
- Xcode 14.0 ou superior
⚙️ Instalação
Adicione ao seu Podfile:
Podfile:target 'App' do
use_frameworks!
pod 'BemobiSmartCheckout', :podspec => 'https://smartcheckout-sdk-repo.s3.us-east-1.amazonaws.com/iOS/0.2.1/BemobiSmartCheckout.podspec'
endInstale as dependências:
pod install💳 Configuração do Apple Pay
Para habilitar Apple Pay, adicione ao seu App.entitlements:
App.entitlements:<key>com.apple.developer.in-app-payments</key>
<array>
<string>merchant.seu.identificador</string>
</array>🔗 Integração via deep link
Para receber callbacks do smart checkout via deep link, adicione ao seu App.entitlements:
App.entitlements:<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:smart-checkout.bemobi.com</string>
<string>webcredentials:smart-checkout.bemobi.com</string>
</array>Estrutura do deep link: https://smart-checkout.bemobi.com/callback/{partnerKey}
Importante: para ativar corretamente o deep link, informe ao suporte:
- App ID (Team ID + Bundle ID)
🧠 Inicialização e uso
Inicializar a engine. No seu AppDelegate:
AppDelegate:import UIKit
import BemobiSmartCheckout
@main
final class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
SmartCheckout.initializeEngine()
return true
}
}Exibir o smart checkout
SmartCheckout.showBottomSheet(
from: self, // UIViewController
config: SmartCheckout.Config(
environment: .production,
sessionCode: "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
partnerKey: "abc-1234",
deepLink: URL(string: "https://smart-checkout.bemobi.com/callback/abc-1234"),
initialHeightFraction: 1.0,
style: .flat
),
callbacks: SmartCheckout.Callbacks(
onSuccess: { result in
print("Checkout completed successfully: \(result)")
},
onError: { error in
print("Checkout error: \(error)")
},
onClose: {
print("Checkout closed")
}
)
)Chamar o checkout via WebView (bridge)
import UIKit
import WebKit
import BemobiSmartCheckout
class WebViewController: UIViewController, WKScriptMessageHandler {
private var webView: WKWebView!
private let sessionCode: String
private let environment: String
init(sessionCode: String, environment: String) {
self.sessionCode = sessionCode
self.environment = environment
super.init(nibName: nil, bundle: nil)
// Inicializa a engine se ainda não foi feito no AppDelegate
SmartCheckout.initializeEngine()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func postMessage(_ message: [String: Any]) {
let jsonData = try! JSONSerialization.data(withJSONObject: message, options: [])
let jsonString = String(data: jsonData, encoding: .utf8)!
let script = "window.postMessage(\(jsonString), \"*\");"
DispatchQueue.main.async {
self.webView.evaluateJavaScript(script, completionHandler: nil)
}
}
private func generateHtml() -> String {
return """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<style>
:root {
--white: #ffffff;
--blue-start: #2e7cf6;
--blue-end: #1f63e5;
--blue-shadow: rgba(31, 99, 229, 0.28);
--gray-very-light: #f8fafc;
--gray-light: #e5e7eb;
--gray-divider: #f1f5f9;
--gray-very-dark: #111827;
--slate-deep: #0f172a;
--slate-muted: #64748b;
--shadow-soft: rgba(0, 0, 0, .04);
--green-deep: #166534;
--green-wash: #ecfdf5;
--green-border: #bbf7d0;
--red-deep: #7f1d1d;
--red-wash: #fef2f2;
--red-border: #fecaca;
--indigo-deep: #1e3a8a;
--indigo-wash: #eff6ff;
--indigo-border: #bfdbfe;
}
body {
display: flex;
margin: max(24px, env(safe-area-inset-top, 24px)) 24px max(24px, env(safe-area-inset-bottom, 24px));
gap: 24px;
font-family: ui-monospace, monospace;
font-size: 14px;
flex-direction: column;
}
button {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 14px 20px;
min-width: 220px;
font-size: 16px;
font-weight: 700;
letter-spacing: .2px;
line-height: 1;
color: var(--white);
background: linear-gradient(180deg, var(--blue-start) 0%, var(--blue-end) 100%);
border: none;
border-radius: 12px;
box-shadow: 0 6px 16px var(--blue-shadow);
user-select: none;
transition: transform .04s ease, filter .12s ease;
}
button:active {
transform: translateY(1px) scale(0.99);
filter: brightness(96%);
}
pre {
margin: 6px 0 0;
padding: 8px;
background: var(--gray-very-light);
border: 1px solid var(--gray-light);
border-radius: 6px;
overflow: auto;
font-size: 12px;
line-height: 1.5;
}
#log {
padding: 12px;
border-radius: 6px;
max-height: 60vh;
overflow: auto;
background: var(--white);
color: var(--gray-very-dark);
border: 1px solid var(--gray-light);
box-shadow: 0 2px 10px var(--shadow-soft);
}
#log #empty {
padding: 16px;
text-align: center;
color: var(--slate-muted);
background: var(--gray-very-light);
border: 1px dashed var(--gray-light);
border-radius: 8px;
margin: 8px;
}
#log .item {
border-bottom: 1px solid var(--gray-divider);
padding: 10px 6px;
}
#log .item:last-child {
border-bottom: 0;
}
#log .head {
display: flex;
gap: 8px;
align-items: baseline;
margin-bottom: 6px;
}
#log .time {
color: var(--slate-muted);
font-size: 12px;
}
#log .message {
white-space: pre-wrap;
margin-left: 2px;
}
.badge {
font-size: 11px;
font-weight: 700;
padding: 2px 8px;
border-radius: 999px;
border: 1px solid var(--gray-light);
background: var(--gray-very-light);
color: var(--slate-deep);
}
.success .badge {
color: var(--green-deep);
background: var(--green-wash);
border-color: var(--green-border);
}
.error .badge {
color: var(--red-deep);
background: var(--red-wash);
border-color: var(--red-border);
}
.info .badge {
color: var(--indigo-deep);
background: var(--indigo-wash);
border-color: var(--indigo-border);
}
.meta {
display: flex;
flex-direction: column;
gap: 4px;
color: var(--slate-deep);
}
.meta .label {
font-weight: 700;
text-transform: uppercase;
}
.meta .value {
word-break: break-all;
}
</style>
</head>
<body>
<div class="meta">
<div class="label">Session Code</div>
<div class="value">\(sessionCode)</div>
</div>
<div class="meta">
<div class="label">Environment</div>
<div class="value">\(environment)</div>
</div>
<button onclick="openSmartCheckout()">Open Smart Checkout SDK</button>
<div id="log">
<div id="empty">
No events yet. Trigger a checkout to see logs here.
</div>
</div>
<script>
Object.defineProperty(window, 'smartCheckoutLoaded', {
get() {
const bridge = window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.startPaymentFlow;
return !!bridge && typeof bridge.postMessage === 'function';
},
});
const EVENT_LOG_MAP = {
'checkout:action': { level: 'info', message: 'Custom action' },
'checkout:success': { level: 'success', message: 'Payment completed successfully' },
'checkout:error': { level: 'error', message: 'Payment error' },
'checkout:close': { level: 'info', message: 'Checkout closed' },
};
function appendLog({ level, message, payload }) {
const empty = document.getElementById('empty');
if (empty) empty.remove();
const item = document.createElement('div');
item.className = ['item', level].join(' ');
const head = document.createElement('div');
head.className = 'head';
const badge = document.createElement('span');
badge.className = 'badge';
badge.textContent = level.replace(/^\\w/, char => char.toUpperCase());
const time = document.createElement('span');
time.className = 'time';
time.textContent = new Date().toLocaleTimeString();
const text = document.createElement('div');
text.className = 'message';
text.textContent = message;
head.appendChild(badge);
head.appendChild(time);
item.appendChild(head);
item.appendChild(text);
if (payload) {
const pre = document.createElement('pre');
pre.textContent = JSON.stringify(payload, null, 2);
item.appendChild(pre);
}
const log = document.getElementById('log');
log.prepend(item);
log.scrollTop = 0;
}
function openSmartCheckout() {
if (window.smartCheckoutLoaded) {
const paymentData = {
sessionCode: "\(sessionCode)",
environment: "\(environment)"
};
window.webkit.messageHandlers.startPaymentFlow.postMessage(JSON.stringify(paymentData));
} else {
appendLog({ level: 'error', message: 'Smart Checkout bridge is not ready' });
}
}
window.addEventListener('message', (event) => {
const { type, payload } = event.data;
const log = EVENT_LOG_MAP[type];
if (log) appendLog({ ...log, payload });
});
</script>
</body>
</html>
"""
}
override func viewDidLoad() {
super.viewDidLoad()
let contentController = WKUserContentController()
contentController.add(self, name: "startPaymentFlow")
let configuration = WKWebViewConfiguration()
configuration.userContentController = contentController
webView = WKWebView(frame: self.view.bounds, configuration: configuration)
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webView.loadHTMLString(generateHtml(), baseURL: nil)
view.addSubview(webView)
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "startPaymentFlow" {
let jsonString = message.body as! String
let jsonData = jsonString.data(using: .utf8)!
let jsonObject = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: String]
let sessionCode = jsonObject["sessionCode"]!
let environment = jsonObject["environment"]!
SmartCheckout.setCustomActionHandler { payload in
print("Custom action received: \(payload)")
self.postMessage(["type": "checkout:action", "payload": payload])
}
SmartCheckout.showBottomSheet(
from: self,
config: SmartCheckout.Config(
environment: SmartCheckout.Environment(rawValue: environment.lowercased())!,
sessionCode: sessionCode,
initialHeightFraction: 0.9
),
callbacks: SmartCheckout.Callbacks(
onSuccess: { result in
print("Checkout completed successfully: \(result)")
self.postMessage(["type": "checkout:success", "payload": result])
},
onError: { error in
print("Checkout error: \(error)")
self.postMessage(["type": "checkout:error", "payload": error])
},
onClose: {
print("Checkout closed")
self.postMessage(["type": "checkout:close"])
}
)
)
}
}
}Registrar um handler para ações customizadas
SmartCheckout.setCustomActionHandler { payload in
print("Custom action received: \(payload)")
}Receber o deep link e iniciar o smart checkout. No seu AppDelegate:
AppDelegate:import UIKit
import BemobiSmartCheckout
@main
final class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if let controller = UIApplication.shared.windows.first?.rootViewController {
let config = SmartCheckout.Config(
environment: .production,
deepLink: url
)
let callbacks = SmartCheckout.Callbacks(
onSuccess: { result in
print("Checkout completed successfully: \(result)")
},
onError: { error in
print("Checkout error: \(error)")
},
onClose: {
print("Checkout closed")
}
)
SmartCheckout.showBottomSheet(
from: controller,
config: config,
callbacks: callbacks
)
return true
}
return false
}
}🧠 Funcionalidades
SmartCheckout.initializeEngine(): inicializa a engine necessária para o fluxo do checkout.SmartCheckout.showBottomSheet(from: UIViewController, config: Config, callbacks: Callbacks): exibe o checkout em um bottom sheet.SmartCheckout.setCustomActionHandler(_ handler: @escaping ([String: Any]) -> Void): registra um handler para ações customizadas disparadas pelo fluxo do checkout.
⚙️ Configuração
A configuração do checkout é feita via SmartCheckout.Config:
environment: ambiente (.production,.sandbox,.development).sessionCode: código da sessão (opcional).partnerKey: identificador do parceiro usado no deep link para identificar a origem.deepLink: URL que abre diretamente uma página específica do checkout (opcional).initialHeightFraction: altura inicial do bottom sheet (opcional, 0.0 a 1.0).style: estilo visual (.rounded,.flat)
🔔 Callbacks
onSuccess([String: Any]): sucesso no pagamento.onError([String: Any]): erro no fluxo.onClose(): fechamento do bottom sheet.
🧠 Boas práticas
- Inicialize o SDK apenas uma vez no ciclo de vida do app.
- Utilize deep link para garantir retorno confiável.
- Sempre trate erros e fechamento do checkout.
- Use Webhook como fonte oficial de status da transação.
- Valide o fluxo em diferentes versões de iOS.
🎯 Resumo
O SDK iOS do smart checkout é a forma mais completa de integração mobile, permitindo uma experiência de pagamento fluida, nativa e altamente personalizável, com suporte a Apple Pay e deep linking.
Updated 4 months ago
Did this page help you?