> ## Documentation Index
> Fetch the complete documentation index at: https://developers.bonifiq.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Mobile (Android e iOS)

> Leve o programa de fidelidade BonifiQ para o seu app: comece com o hub pronto em poucas linhas e avance para login, cupons, cashback no checkout e telas próprias

O SDK da BonifiQ traz o programa de fidelidade da loja para dentro do seu app, sem que você precise chamar a API endpoint por endpoint. Ele já resolve o login seguro do cliente, a renovação do token, as telas de pontos e recompensas e o cashback no checkout.

Você pode parar em qualquer passo deste guia: o primeiro já coloca o programa de fidelidade no ar, e cada passo seguinte acrescenta uma capacidade.

<Note>
  **Código aberto.** O SDK está na versão `0.3.0`, publicado no Maven Central e no Swift Package Manager, com o código em [github.com/bonifiq/bonifiq-sdk](https://github.com/bonifiq/bonifiq-sdk) sob a licença Apache 2.0.
</Note>

***

## O que o SDK oferece

O SDK é feito em Kotlin Multiplatform e é distribuído em duas partes:

| Artefato | Para quê |
| - | - |
| `br.com.bonifiq:bonifiq-ui` | O hub de fidelidade (o widget da BonifiQ dentro do app) e telas prontas em Compose Multiplatform: cashback no checkout, saldo e login por código. Já inclui o core. |
| `br.com.bonifiq:bonifiq-core` | A API sem interface: login seguro, saldo, extrato, recompensas, cupons, cashback e regulamento. Para quem quer desenhar as próprias telas. |

<Info>
  **Sem chave de API no app.** O SDK usa a [API Widget](/api-widget/00-introduction), a mesma do widget do site. Cada chamada vai com o SecureToken do próprio cliente, que vale 60 minutos. Nada no app dá acesso aos dados de outros clientes.
</Info>

<CardGroup cols={3}>
  <Card title="Hub pronto" icon="star" href="#passo-1-o-hub-pronto-em-poucas-linhas">
    O widget da BonifiQ no app: saldo, recompensas, missões, quiz e níveis
  </Card>

  <Card title="Login seguro" icon="lock" href="#passo-2-identifique-o-cliente">
    Pela sessão VTEX ou por código enviado por e-mail
  </Card>

  <Card title="Cupom no carrinho" icon="ticket" href="#passo-3-reaja-ao-que-acontece-no-hub">
    O cliente resgata e o app aplica o cupom no carrinho
  </Card>

  <Card title="Cashback no checkout" icon="cart-shopping" href="#passo-4-cashback-no-checkout">
    O cliente usa o saldo de cashback ao finalizar a compra
  </Card>

  <Card title="Telas próprias" icon="code" href="#passo-6-telas-do-seu-jeito">
    Todas as funções da API, sem interface
  </Card>

  <Card title="Sempre atualizado" icon="globe" href="#o-hub-por-dentro">
    As novidades do widget chegam ao app sem publicar uma nova versão
  </Card>
</CardGroup>

***

## Requisitos

| Plataforma | O que precisa |
| - | - |
| **Android** | minSdk 24 e Jetpack Compose. O SDK já declara no manifest dele o que precisa, como a permissão `INTERNET` |
| **iOS** (prévia) | iPhone, ou simulador num Mac com Apple Silicon. O `Info.plist` do app precisa da chave `CADisableMinimumFrameDurationOnPhone` com valor `true`, exigida pelo Compose no iOS |
| **Desktop (JVM)** | O core, o card de cashback e o chip de saldo funcionam com Compose Multiplatform, o que ajuda durante o desenvolvimento. O hub não abre no desktop, que não tem WebView |
| **Hub** ([como funciona](#o-hub-por-dentro)) | No Android, um WebView atualizado, com suporte a `WEB_MESSAGE_LISTENER`. Sem ele, o hub avisa que o programa não está disponível no aparelho |

A versão `0.3.0` é compilada com Kotlin 2.4.20, Compose Multiplatform 1.12.1, Android Gradle Plugin 9.4.1 e compileSdk 37. Use essas versões ou mais novas: versões anteriores podem funcionar, mas não têm suporte.

***

## Por onde começar

| Você quer… | Use | Onde |
| - | - | - |
| Mostrar o programa de fidelidade no app | `BonifiQLoyalty` | [Passo 1](#passo-1-o-hub-pronto-em-poucas-linhas) |
| Mostrar saldo e cupons do cliente logado | Login seguro | [Passo 2](#passo-2-identifique-o-cliente) |
| Aplicar no carrinho o cupom que o cliente resgatou | Eventos do hub | [Passo 3](#passo-3-reaja-ao-que-acontece-no-hub) |
| Deixar o cliente usar cashback ao finalizar a compra | `CashbackCheckoutCard` | [Passo 4](#passo-4-cashback-no-checkout) |
| Mostrar o saldo no topo de qualquer tela | `PointsBalanceChip` | [Passo 5](#passo-5-saldo-em-qualquer-tela) |
| Desenhar as próprias telas | `bonifiq-core` | [Passo 6](#passo-6-telas-do-seu-jeito) |
| Integrar o widget sem o SDK (React Native, Flutter) | Mensagens do widget | [O hub por dentro](#o-hub-por-dentro) |

***

## Como funciona

```mermaid theme={null}
sequenceDiagram
    participant C as Cliente
    participant App as Seu app
    participant SDK as SDK BonifiQ
    participant BQ as API BonifiQ

    C->>App: Faz login na loja
    App->>SDK: loginWithVtex(sessão VTEX)
    SDK->>BQ: Troca a sessão por um SecureToken
    BQ-->>SDK: SecureToken (60 min)
    C->>SDK: Resgata uma recompensa no hub
    SDK->>BQ: Resgate, com o SecureToken
    BQ-->>SDK: Código do cupom
    SDK-->>App: LoyaltyEvent.ApplyCoupon
    App->>App: Aplica o cupom no carrinho
```

* **Login:** o app prova quem é o cliente, pela sessão VTEX ou por um código enviado por e-mail. O SDK troca essa prova por um SecureToken, guarda o token em memória e renova quando pode.
* **Telas:** o hub (o widget da BonifiQ), as telas prontas do SDK ou as suas usam a API Widget com esse token.
* **Eventos:** quando algo precisa acontecer fora da BonifiQ, como aplicar um cupom, o SDK avisa o app. Quem mexe no carrinho é sempre o app.

***

## Passo 1: o hub pronto, em poucas linhas

### Instale

Adicione o SDK ao módulo do app (ou ao módulo compartilhado, se o projeto é Kotlin Multiplatform):

```kotlin build.gradle.kts theme={null}
dependencies {
    implementation("br.com.bonifiq:bonifiq-ui:0.3.0")
}
```

O SDK está no Maven Central, que já vem configurado (`mavenCentral()`) nos projetos Android e Kotlin Multiplatform.

### Crie uma instância

Crie **uma única instância** para o app inteiro e compartilhe, por exemplo, pela sua injeção de dependências:

```kotlin theme={null}
val bonifiq = BonifiQ(BonifiQConfig(tenant = "SEU_TENANT"))
```

O `tenant` é a chave pública da loja na BonifiQ, a mesma usada no header `X-BQ-Tenant` da API Widget.

### Mostre o hub

```kotlin theme={null}
@Composable
fun FidelidadeScreen(bonifiq: BonifiQ) {
    BonifiQLoyalty(bonifiq, modifier = Modifier.fillMaxSize()) { event ->
        // Eventos do hub: veja o passo 3
    }
}
```

Pronto: o programa de fidelidade já está no app.

* **É o widget da BonifiQ:** o mesmo do site da loja, aberto num WebView. O cliente tem saldo, recompensas, cupons, extrato, regulamento, missões, quiz, níveis e indicação.
* **Sem cliente logado:** o widget mostra o programa e convida o cliente a entrar.
* **Com cliente logado** (passo 2): o widget mostra os pontos, as recompensas e os cupons dele.
* **Visual e textos:** vêm do painel da BonifiQ, como no site. Não é preciso configurar nada no app.
* **Sempre atualizado:** quando a BonifiQ melhora o widget, o app recebe a novidade sem publicar uma nova versão.

### Exemplo completo no Android

Os três arquivos abaixo bastam para abrir o hub numa tela do app. A instância fica na classe `Application`, para ser a mesma em todas as telas:

<CodeGroup>
  ```kotlin MinhaLojaApp.kt theme={null}
  import android.app.Application
  import br.com.bonifiq.sdk.BonifiQ
  import br.com.bonifiq.sdk.BonifiQConfig

  class MinhaLojaApp : Application() {
      val bonifiq by lazy { BonifiQ(BonifiQConfig(tenant = "SEU_TENANT")) }
  }
  ```

  ```kotlin FidelidadeActivity.kt theme={null}
  import android.os.Bundle
  import androidx.activity.ComponentActivity
  import androidx.activity.compose.setContent
  import androidx.compose.foundation.layout.fillMaxSize
  import androidx.compose.material3.MaterialTheme
  import androidx.compose.ui.Modifier
  import br.com.bonifiq.sdk.ui.BonifiQLoyalty
  import br.com.bonifiq.sdk.ui.LoyaltyEvent

  class FidelidadeActivity : ComponentActivity() {
      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          val bonifiq = (application as MinhaLojaApp).bonifiq
          setContent {
              MaterialTheme {
                  BonifiQLoyalty(bonifiq, modifier = Modifier.fillMaxSize()) { event ->
                      if (event == LoyaltyEvent.Close) finish()
                  }
              }
          }
      }
  }
  ```

  ```xml AndroidManifest.xml theme={null}
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
      <application android:name=".MinhaLojaApp">
          <activity android:name=".FidelidadeActivity" />
      </application>
  </manifest>
  ```
</CodeGroup>

***

## Passo 2: identifique o cliente

O SDK não chama a sua plataforma de e-commerce. O app entrega ao SDK uma prova de quem é o cliente, e o SDK troca essa prova por um SecureToken da BonifiQ.

As funções de login são `suspend`: chame-as numa coroutine, por exemplo com `viewModelScope.launch`. Elas lançam `BonifiQException` quando não conseguem entrar ([veja os erros](#erros)).

<Tabs>
  <Tab title="Loja VTEX">
    Depois que o cliente faz login na VTEX pelo seu app, entregue ao SDK os tokens da sessão VTEX:

    ```kotlin theme={null}
    viewModelScope.launch {
        try {
            bonifiq.auth.loginWithVtex {
                // Chamado agora e a cada renovação do token: devolva a sessão VTEX atual
                val sessao = vtexSession.create() // POST /api/sessions do Session Manager da VTEX
                VtexSessionTokens(sessao.sessionToken, sessao.segmentToken)
            }
        } catch (e: BonifiQException) {
            // A loja segue funcionando; o hub mostra o convite para entrar
        }
    }
    ```

    O SDK guarda essa função e a chama de novo sempre que precisa renovar o token: aos 55 minutos ou quando a API responde que ele venceu. Para o seu app, o cliente fica logado na BonifiQ enquanto estiver logado na VTEX.
  </Tab>

  <Tab title="Código por e-mail">
    Para lojas sem login de plataforma integrado, o cliente entra com um código de 6 dígitos enviado por e-mail, que vale 10 minutos. Use a tela pronta:

    ```kotlin theme={null}
    EmailCodeLogin(bonifiq, onLoggedIn = { cliente -> /* volta para o hub */ })
    ```

    Ou monte a sua, com as duas chamadas:

    ```kotlin theme={null}
    bonifiq.auth.requestEmailCode(email)           // envia o código
    bonifiq.auth.loginWithEmailCode(email, codigo) // valida e entra
    ```

    <Warning>
      Esse login não renova sozinho, porque precisaria de um código novo. Depois de 60 minutos o estado vira `Expired`, e o app deve pedir o login de novo.
    </Warning>
  </Tab>
</Tabs>

### Quando o app abre de novo

O SDK guarda a sessão só em memória. Cada vez que o app é aberto, o cliente começa anônimo na BonifiQ:

* **Loja VTEX:** se o cliente já está logado na loja, chame `loginWithVtex` de novo na inicialização do app. Ele não percebe nada.
* **Código por e-mail:** o cliente precisa entrar de novo.

### Estado do login

Acompanhe o estado do login para adaptar a interface:

```kotlin theme={null}
val state by bonifiq.auth.state.collectAsState()

when (val s = state) {
    AuthState.Anonymous -> BotaoEntrar()
    is AuthState.Authenticated -> Text("Olá, ${s.customer.name ?: s.customer.email}")
    AuthState.Expired -> Text("Sua sessão expirou. Entre de novo.")
}
```

Quando o cliente sair do app, saia também da BonifiQ:

```kotlin theme={null}
bonifiq.auth.logout()
```

***

## Passo 3: reaja ao que acontece no hub

O hub avisa o app por eventos. São três:

| Evento | Quando acontece | O que o app faz |
| - | - | - |
| `LoyaltyEvent.ApplyCoupon` | O cliente resgatou uma recompensa e tocou em **Usar no carrinho** | Aplica `couponCode` no carrinho da loja |
| `LoyaltyEvent.LoginRequired` | O cliente tocou em entrar, ou a sessão expirou | Abre a tela de login do app |
| `LoyaltyEvent.Close` | O cliente quer sair do hub | Volta para a tela anterior |

```kotlin theme={null}
BonifiQLoyalty(bonifiq, cartId = carrinho.id) { event ->
    when (event) {
        is LoyaltyEvent.ApplyCoupon -> carrinho.aplicarCupom(event.couponCode)
        LoyaltyEvent.LoginRequired -> abrirLogin()
        LoyaltyEvent.Close -> voltar()
    }
}
```

<Tip>
  O cupom é criado direto na plataforma: o hub entrega o código, e quem aplica é o app. O `cartId` (na VTEX, o `orderFormId`) fica reservado: hoje o widget ainda não associa o resgate ao carrinho, mas passar o `cartId` já deixa o app pronto para isso.
</Tip>

***

## Passo 4: cashback no checkout

Se a loja usa cashback, coloque o card pronto na tela de finalização da compra. Ele mostra o saldo, quanto pode ser usado nesta compra, e aplica ou remove o cashback:

```kotlin theme={null}
CashbackCheckoutCard(
    bonifiq = bonifiq,
    cartId = carrinho.id,          // na VTEX, o orderFormId
    cartTotal = carrinho.subtotal, // em reais
    onCartChanged = { recarregarCarrinho() },
)
```

O card tem duas versões, escolhidas pelo `amount`:

| `amount` | O que o cliente vê |
| - | - |
| `CashbackAmount.All` (padrão) | Um botão "Usar R\$ 75,00 de cashback", que usa todo o disponível num toque |
| `CashbackAmount.Custom` | Um campo para digitar quanto usar, até o disponível |

```kotlin theme={null}
CashbackCheckoutCard(
    bonifiq = bonifiq,
    cartId = carrinho.id,
    cartTotal = carrinho.subtotal,
    onCartChanged = { recarregarCarrinho() },
    amount = CashbackAmount.Custom, // o cliente escolhe o valor
)
```

<Frame>
  <img src="https://mintcdn.com/bonifiq/QidE62c9KqboDG2D/images/sdk-mobile/cashback-checkout.png?fit=max&auto=format&n=QidE62c9KqboDG2D&q=85&s=e0f999a0b20ac4ee3c0b81bde9f8e5f5" alt="Card de cashback no checkout com o saldo e o botão Usar R$ 75,00 de cashback" width="412" height="520" data-path="images/sdk-mobile/cashback-checkout.png" />
</Frame>

A BonifiQ aplica o desconto direto no carrinho da plataforma. Por isso, há três cuidados:

1. **Recarregue o carrinho em `onCartChanged`.** Ele é chamado depois de aplicar ou remover o cashback.
2. **Avise a BonifiQ quando o carrinho mudar.** Depois de alterar itens ou cupons, chame `bonifiq.checkout.refreshCashback(cartId)`.
3. **Confirme antes de fechar o pedido.** Chame `refreshCashback` de novo. Se o resultado vier com `removed = true`, recarregue o carrinho e mostre a mensagem ao cliente antes de seguir.

```kotlin theme={null}
val refresh = bonifiq.checkout.refreshCashback(carrinho.id)
if (refresh.removed) {
    recarregarCarrinho()
    mostrarAviso(refresh.message ?: "O cashback foi removido do carrinho. Confira o total.")
}
```

Para os detalhes de como o cashback aparece no carrinho VTEX, veja o guia [Cashback na VTEX](/guias/vtex-cashback-checkout).

***

## Passo 5: saldo em qualquer tela

Mostre o saldo do cliente no topo da loja, com atalho para o hub:

```kotlin theme={null}
PointsBalanceChip(bonifiq, onClick = { abrirFidelidade() })
```

O chip se atualiza sozinho depois de resgates e logins. Se preferir desenhar o seu, use o último saldo conhecido, que o SDK mantém atualizado:

```kotlin theme={null}
val resumo by bonifiq.customer.summary.collectAsState()
Text(resumo?.pointsText ?: "")  // ex.: "1.500 Coins", com o nome que a loja configurou
```

***

## Passo 6: telas do seu jeito

Se o app tem identidade visual própria, use só o `bonifiq-core` e desenhe as telas. O login (passo 2) é o mesmo.

```kotlin build.gradle.kts theme={null}
dependencies {
    implementation("br.com.bonifiq:bonifiq-core:0.3.0")
}
```

Todas as funções são `suspend`. No Swift, elas viram `async throws` ([veja como usar](#usando-o-core-no-swift)):

```kotlin theme={null}
// Cliente
val cliente = bonifiq.customer.info()             // nome, saldo, "1.500 Coins", nível
val extrato = bonifiq.customer.history(page = 1)  // paginado
val expirando = bonifiq.customer.expiringPoints()

// Recompensas
val recompensas = bonifiq.rewards.list()          // isRedeemable, canUse e o motivo quando não dá
val resgate = bonifiq.rewards.redeem(recompensa.id, cartId = carrinho.id)
val cupom = resgate.couponCode
val meusCupons = bonifiq.rewards.redeemed()

// Cashback no checkout
val opcoes = bonifiq.checkout.cashbackOptions(carrinho.id, cartTotal = 150.0)  // saldo, valor usável, regras
bonifiq.checkout.applyCashback(carrinho.id, value = opcoes.usable, cartTotal = 150.0)
bonifiq.checkout.removeCashback(carrinho.id)

// Programa
val regulamento = bonifiq.program.terms()         // HTML
```

### Erros

As funções lançam apenas `BonifiQException`:

| Erro | Significado | O que fazer |
| - | - | - |
| `BonifiQException.Unauthorized` | Sem cliente logado, ou o token foi recusado | Pedir o login de novo |
| `BonifiQException.Api` | A BonifiQ recusou a operação (ex.: pontos insuficientes) | A `message` pode ser mostrada ao cliente |
| `BonifiQException.Network` | Falha de conexão | Oferecer tentar de novo |

```kotlin theme={null}
try {
    val resgate = bonifiq.rewards.redeem(recompensa.id, cartId = carrinho.id)
    resgate.couponCode?.let { carrinho.aplicarCupom(it) }
} catch (e: BonifiQException.Unauthorized) {
    abrirLogin()
} catch (e: BonifiQException) {
    mostrarAviso(e.message ?: "Não foi possível resgatar agora.")
}
```

<Tip>
  Nas telas prontas do SDK (card de cashback, chip de saldo e login por código), você também pode trocar os textos. Eles estão em português por padrão e são substituíveis pelo `LocalBonifiQStrings`. Os textos do hub vêm do painel da BonifiQ.
</Tip>

***

## O hub por dentro

O `BonifiQLoyalty` abre o widget da BonifiQ dentro de um WebView, no modo app. É o mesmo widget do site da loja, com todos os recursos dele, e as novidades chegam sem publicar uma nova versão do app. O login do passo 2 e os eventos do passo 3 ligam o widget ao app.

* **Endereço do widget:** é o configurado para a loja na BonifiQ. Se o time BonifiQ indicar outro endereço para testes, use `widgetUrl`:

  ```kotlin theme={null}
  val bonifiq = BonifiQ(BonifiQConfig(tenant = "SEU_TENANT", widgetUrl = "https://endereco-indicado-pela-bonifiq"))
  ```

* **Segurança:** o token do cliente vai ao widget por mensagem, nunca pela URL. O WebView só conversa com o domínio do widget, e links para outros sites abrem no navegador.

* **Se o widget não carregar** em 12 segundos, ou a configuração da loja não responder, o hub mostra "Não foi possível carregar o programa de fidelidade" com **Tentar novamente**.

* **Sem WebView compatível** (desktop, ou Android com o WebView desatualizado), o hub avisa que o programa não está disponível no aparelho, e o botão **Fechar** emite `LoyaltyEvent.Close`.

* **Carrinho:** o widget ainda não recebe o `cartId`. Os resgates não ficam associados ao carrinho, mas o evento `ApplyCoupon` chega ao app do mesmo jeito.

* **Strava:** em desafios de corrida, o cliente conecta a conta do Strava sem sair do app. O SDK abre o app do Strava, se estiver instalado, ou a tela de login do sistema por cima do app. No Android isso não exige nada; no iOS, abrir o **app** do Strava pede uma configuração opcional ([veja na seção de iOS](#strava-no-ios-opcional)).

<AccordionGroup>
  <Accordion title="Integrando o widget sem o SDK (React Native, Flutter)" icon="plug">
    Apps que não usam o SDK podem abrir o widget num WebView próprio e conversar com ele por mensagens JSON.

    **URL:** `https://widget.bonifiq.com.br/?bqapp=1&tenant=SEU_TENANT`

    **Do widget para o app:**

    | Mensagem | Significado |
    | - | - |
    | `{"type":"ready","version":1}` | O widget carregou. Responda com `auth` se houver cliente logado |
    | `{"type":"applyCoupon","couponCode":"…"}` | Aplicar o cupom no carrinho |
    | `{"type":"loginRequired"}` | Abrir o login do app |
    | `{"type":"tokenExpired"}` | Renovar o SecureToken e responder com `tokenRefreshed` |
    | `{"type":"openUrl","url":"…"}` | Abrir o link no navegador |
    | `{"type":"close"}` | Fechar o hub |
    | `{"type":"error","message":"…"}` | O widget não conseguiu carregar |

    **Do app para o widget:** `auth` (com `secureToken` e `customer: {email, name, id}`), `tokenRefreshed` (com `secureToken`) e `logout`.

    **Canais:**

    * widget → app: `window.ReactNativeWebView.postMessage` no React Native, ou um `JavaScriptChannel` chamado `BonifiQNative` no Flutter;
    * app → widget: `window.BonifiQEmbed.receive(<json>)`.

    O SecureToken do cliente é obtido no [login seguro da API Widget](/api-widget/01-authentication).
  </Accordion>
</AccordionGroup>

***

## iOS (Swift)

<Warning>
  O suporte a iOS está em **prévia**: a API para Swift pode mudar até a versão final.
</Warning>

O framework `BonifiQ` traz o core e as telas. O hub vem pronto como `UIViewController`, para UIKit e SwiftUI.

Instale pelo Swift Package Manager: no Xcode, **File → Add Package Dependencies** com a URL `https://github.com/bonifiq/bonifiq-sdk` (versão `0.3.0` ou mais nova), ou no `Package.swift`:

```swift Package.swift theme={null}
.package(url: "https://github.com/bonifiq/bonifiq-sdk", from: "0.3.0")
```

O pacote exige iOS 15 ou mais novo.

Antes de abrir o hub, adicione ao `Info.plist` do app a chave `CADisableMinimumFrameDurationOnPhone` com valor `true`. Sem ela, o Compose encerra o app ao mostrar a tela.

<CodeGroup>
  ```swift UIKit theme={null}
  import BonifiQ

  let config = BonifiQConfig(
      tenant: "SEU_TENANT",
      baseUrl: BonifiQConfig.companion.DEFAULT_BASE_URL,
      enableLogging: false,
      widgetUrl: nil
  )
  let bonifiq = BonifiQ(config: config, httpEngine: nil)

  let hub = LoyaltyViewControllerKt.LoyaltyViewController(bonifiq: bonifiq, cartId: carrinho.id) { event in
      if let cupom = event as? LoyaltyEventApplyCoupon {
          carrinho.aplicar(cupom: cupom.couponCode)
      }
  }
  navigationController?.pushViewController(hub, animated: true)
  ```

  ```swift SwiftUI theme={null}
  import SwiftUI
  import BonifiQ

  struct FidelidadeView: UIViewControllerRepresentable {
      let bonifiq: BonifiQ
      let cartId: String?
      let onEvent: (LoyaltyEvent) -> Void

      func makeUIViewController(context: Context) -> UIViewController {
          LoyaltyViewControllerKt.LoyaltyViewController(bonifiq: bonifiq, cartId: cartId, onEvent: onEvent)
      }

      func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
  }
  ```
</CodeGroup>

No Swift, os parâmetros da configuração não têm valor padrão: informe todos, como no exemplo.

### Strava no iOS (opcional)

Sem configuração, o cliente conecta o Strava pela tela de login do sistema, que aparece por cima do app e aproveita a sessão do Safari. Para abrir o **app** do Strava, que pede só um toque para autorizar:

1. Registre o esquema `bq.` seguido do bundle id em minúsculas no `Info.plist`:

   ```xml Info.plist theme={null}
   <key>CFBundleURLTypes</key>
   <array>
       <dict>
           <key>CFBundleURLSchemes</key>
           <array><string>bq.com.sualoja.app</string></array>
       </dict>
   </array>
   ```

2. Repasse ao SDK as URLs que o app recebe:

   ```swift theme={null}
   // SwiftUI. No UIKit: application(_:open:options:) ou scene(_:openURLContexts:)
   .onOpenURL { url in _ = BonifiQAuthRedirect.shared.handle(url: url.absoluteString) }
   ```

Se o bundle id tiver `_`, ele não vira um esquema de URL válido, e o cliente conecta o Strava pelo navegador.

### Usando o core no Swift

As funções do [passo 6](#passo-6-telas-do-seu-jeito) chegam ao Swift como `async throws`. O `BonifiQException` vem dentro do erro, em `userInfo["KotlinException"]`:

```swift theme={null}
@MainActor
func carregarSaldo() async {
    do {
        let cliente = try await bonifiq.customer.info()
        saldo.text = cliente.pointsText
    } catch {
        if (error as NSError).userInfo["KotlinException"] is BonifiQExceptionUnauthorized {
            abrirLogin()
        } else {
            mostrarAviso(error.localizedDescription)
        }
    }
}
```

***

## App de demonstração

O app de demonstração é uma loja completa, em Kotlin Multiplatform (Android, iOS e desktop), ligada a uma loja VTEX de testes e ao programa de fidelidade dela. Use-o como referência de integração: cada passo deste guia está implementado nele.

O cliente consegue:

* navegar pelo catálogo, montar o carrinho, entrar na loja (código por e-mail da VTEX) e fechar o pedido;
* ver o saldo no topo da loja;
* resgatar recompensas na aba **Fidelidade** e aplicar o cupom no carrinho com um toque;
* usar cashback na finalização da compra.

| O que ver | Onde está no código |
| - | - |
| Criação do `BonifiQ` | `shared/.../AppContainer.kt` |
| Login VTEX entregue ao SDK, inclusive ao abrir o app, e logout | `shared/.../data/AuthRepository.kt` |
| Hub e eventos (cupom no carrinho, login, fechar) | `shared/.../ui/loyalty/LoyaltyScreen.kt` |
| Saldo no topo da loja | `shared/.../ui/catalog/CatalogScreen.kt` |
| Cashback no checkout | `shared/.../ui/checkout/CheckoutScreen.kt` |
| `refreshCashback` ao mudar o carrinho e antes de fechar o pedido | `shared/.../ui/cart/CartViewModel.kt` e `.../ui/checkout/CheckoutViewModel.kt` |

Para rodar, siga o README do projeto:

* **Desktop:** `./gradlew :desktopApp:run`. No desktop, a aba Fidelidade avisa que o hub não está disponível, porque não há WebView.
* **Android:** `./gradlew :androidApp:assembleDebug` e instale o APK num aparelho
* **iOS:** abra `iosApp/iosApp.xcodeproj` no Xcode

<Note>
  Para receber o app de demonstração, fale com o time BonifiQ em [suporte@bonifiq.com.br](mailto:suporte@bonifiq.com.br).
</Note>

***

## Testando a integração

* **Logs:** com `enableLogging = true`, o SDK escreve no console o método, a URL e o status de cada chamada. No Android, eles aparecem no Logcat com a tag `System.out`. Tokens, headers e corpos nunca são registrados. Desligue antes de publicar.
* **Outro endereço do widget:** se o time BonifiQ indicar um widget de testes, use `widgetUrl`.
* **Cliente de testes:** use um cliente criado para testes. Os resgates gastam pontos de verdade e geram cupons válidos na loja.

Antes de publicar o app, confira:

1. Sem login, o hub abre e mostra as recompensas.
2. Depois do login, o hub mostra o saldo do cliente.
3. Ao fechar e abrir o app com o cliente logado na loja, o saldo aparece sem pedir login de novo (loja VTEX).
4. O cupom resgatado entra no carrinho.
5. Aplicar e remover cashback atualiza o total do carrinho, e o `refreshCashback` roda antes de fechar o pedido.
6. Ao sair do app, o cliente sai também da BonifiQ.
7. O `enableLogging` está desligado.

***

## Dados e privacidade

Use estas informações para preencher a seção de segurança dos dados do Google Play e os detalhes de privacidade da App Store.

* **Para onde vão os dados:** o SDK só se comunica com a API da BonifiQ (`api.bonifiq.com.br`). No hub, o WebView também carrega o widget da BonifiQ.
* **O que é enviado:**
  * o `tenant` da loja, em todas as chamadas;
  * no login VTEX, os tokens da sessão VTEX, que a BonifiQ usa para confirmar o cliente com a VTEX;
  * no login por código, o e-mail e o código;
  * o SecureToken, nas chamadas do cliente logado;
  * o id e o total do carrinho, nos resgates e no cashback.
* **O que fica no aparelho:** nada é gravado em disco. O SecureToken fica só em memória e é descartado quando o app fecha ou no `logout()`.
* **Métricas e erros:** o SDK não coleta métricas de uso. O widget, como no site da loja, usa o Google Analytics para métricas de uso e o Sentry para registrar erros técnicos, sem identificar o cliente.

***

## Solução de problemas

<AccordionGroup>
  <Accordion title="O hub aparece cortado ou com espaço sobrando">
    Dê ao `BonifiQLoyalty` um tamanho definido, por exemplo `Modifier.fillMaxSize()`. Evite colocá-lo dentro de uma coluna com rolagem: o hub já rola sozinho.
  </Accordion>

  <Accordion title="O cliente está logado na loja, mas o hub pede para entrar">
    O SDK só sabe do login quando o app chama `loginWithVtex` (ou o login por código). Confira se o app chama o login depois que o cliente entra na loja e também quando o app abre de novo. Com `enableLogging = true`, a chamada a `vendors/vtex/securelogin` deve aparecer com status 200.
  </Accordion>

  <Accordion title="Recebo BonifiQException.Unauthorized depois de um tempo">
    * **Login por código:** a sessão vale 60 minutos e não é renovada. Peça o login de novo.
    * **Login VTEX:** a função entregue ao `loginWithVtex` precisa devolver os tokens da sessão VTEX **atual**. Se a sessão da VTEX acabou, o cliente precisa entrar na loja de novo.
  </Accordion>

  <Accordion title="O cupom resgatado não entrou no carrinho">
    O SDK não mexe no carrinho: ele entrega o código no evento `LoyaltyEvent.ApplyCoupon`, e o app aplica na plataforma. Confira se o evento é tratado e se o carrinho é recarregado depois de aplicar o cupom.
  </Accordion>

  <Accordion title="O cashback sumiu do carrinho">
    Quando o carrinho muda e deixa de cumprir as regras do cashback (por exemplo, o valor mínimo), a BonifiQ remove o desconto. O `refreshCashback` devolve `removed = true` e uma `message` para mostrar ao cliente.
  </Accordion>

  <Accordion title="O hub mostra que não foi possível carregar o programa">
    O widget não respondeu em 12 segundos, ou a configuração da loja não chegou (`/pub/widget/app/config`). Costuma ser rede instável: **Tentar novamente** carrega de novo. Para investigar, use `enableLogging = true` e veja se a chamada a `app/config` volta com status 200.
  </Accordion>

  <Accordion title="O hub avisa que o programa não está disponível no aparelho">
    O aparelho não tem um WebView compatível. No Android, o hub precisa do `WEB_MESSAGE_LISTENER`, que vem com o Android System WebView atualizado pela Play Store. No desktop o hub nunca abre, porque não há WebView.
  </Accordion>

  <Accordion title="Conectar o Strava abre o navegador, e não o app do Strava">
    * **iOS:** sem a configuração opcional, o SDK usa a tela de login do sistema. Para abrir o app do Strava, registre o esquema e repasse as URLs ([veja como](#strava-no-ios-opcional)). Se você registrou o esquema mas não repassa as URLs, o Strava volta para o app e a conexão não termina.
    * **Android:** o app do Strava abre a partir do Android 11, se estiver instalado. Nas versões anteriores, o login abre numa aba do navegador por cima do app.
    * **Os dois:** um `applicationId` ou bundle id com `_` não vira esquema de URL, e o login cai no navegador externo.
  </Accordion>

  <Accordion title="O app fecha ao abrir o hub no iOS">
    Falta a chave `CADisableMinimumFrameDurationOnPhone` com valor `true` no `Info.plist`. Veja a [seção de iOS](#ios-swift).
  </Accordion>
</AccordionGroup>

***

## Referência rápida

### Configuração (`BonifiQConfig`)

| Campo | Padrão | Para quê |
| - | - | - |
| `tenant` | — | Chave pública da loja na BonifiQ (obrigatório) |
| `baseUrl` | `https://api.bonifiq.com.br` | Host da API. Só muda se o time BonifiQ indicar outro ambiente |
| `enableLogging` | `false` | Registra método, URL e status das chamadas. Nunca registra headers, tokens ou corpos |
| `widgetUrl` | `null` | Outro endereço do widget, se o time BonifiQ indicar. `null` usa o da loja |

### Boas práticas

* **Uma instância por app.** Ela guarda a sessão do cliente e o cache das configurações da loja.
* **Entre na BonifiQ ao abrir o app.** Se o cliente está logado na loja VTEX, chame `loginWithVtex` na inicialização.
* **Faça logout junto com o app.** Chame `bonifiq.auth.logout()` quando o cliente sair.
* **Não guarde o SecureToken.** O SDK cuida dele em memória e renova quando pode.
* **Recarregue o carrinho depois de mexer no cashback**, e confira com `refreshCashback` antes de fechar o pedido.
* **Desligue o `enableLogging` em produção.**

### Versões

O SDK segue o versionamento semântico. Antes da 1.0, uma nova versão `0.x` pode trazer mudanças incompatíveis; cada versão tem as mudanças descritas nas [releases do GitHub](https://github.com/bonifiq/bonifiq-sdk/releases).

Para saber qual versão o app está usando, consulte `BonifiQ.VERSION`.

***

## Dúvidas comuns

<AccordionGroup>
  <Accordion title="Preciso de uma chave de API no app?">
    **Não.** O SDK usa a API Widget, autenticada pelo SecureToken do próprio cliente. A única informação da loja no app é o `tenant`, que é público.
  </Accordion>

  <Accordion title="Minha loja não é VTEX. Posso usar o SDK?">
    **Sim.** O login por código de e-mail funciona para qualquer loja, e o hub, o saldo e as recompensas não dependem da plataforma. A diferença está no carrinho: o SDK entrega o código do cupom e o app aplica na própria plataforma.
  </Accordion>

  <Accordion title="Posso mudar as cores e os textos?">
    O hub é o widget da BonifiQ: cores, textos, nome do programa e nome dos pontos (por exemplo, "Coins") vêm do painel, como no site. Os textos das telas prontas do SDK (cashback, saldo e login por código) podem ser substituídos pelo `LocalBonifiQStrings`. Para controle total da interface, use o `bonifiq-core` ([passo 6](#passo-6-telas-do-seu-jeito)).
  </Accordion>

  <Accordion title="O que acontece quando o token do cliente expira?">
    No login VTEX, o SDK renova sozinho, sem o cliente perceber. No login por código, o estado vira `Expired` depois de 60 minutos: o SDK encerra a sessão no hub e emite `LoginRequired` para o app abrir o login.
  </Accordion>

  <Accordion title="E se o widget não carregar?">
    O SDK espera até 12 segundos. Se o widget não responder, o hub mostra uma mensagem com **Tentar novamente**. O cliente nunca fica com uma tela em branco.
  </Accordion>
</AccordionGroup>

***

## Suporte

Em caso de dúvidas ou problemas na integração:

* **Central de Ajuda**: [suporte.bonifiq.com.br](https://suporte.bonifiq.com.br)
* **E-mail**: [suporte@bonifiq.com.br](mailto:suporte@bonifiq.com.br)
* **API Widget**: [documentação](/api-widget/00-introduction)
* **Código do SDK e problemas**: [github.com/bonifiq/bonifiq-sdk](https://github.com/bonifiq/bonifiq-sdk) (licença Apache 2.0)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.