iOS

Configuração, transação e exemplo completo em Swift.

No iPhone, a leitura do cartão usa o Tap to Pay on iPhone da Apple. Além do código, o app do parceiro precisa de uma autorização da Apple, o entitlement. Comece pelo pedido à Apple: ele depende de aprovação e é a etapa mais demorada da integração.

Requisitos

ItemValor
iOS17 ou mais novo
AparelhoiPhone compatível com Tap to Pay on iPhone; simulador não é suportado
Conta AppleApple Developer Program de organização (conta individual não pode pedir o entitlement)
Entitlementcom.apple.developer.proximity-reader.payment.acceptance

Habilitar o Tap to Pay on iPhone na conta Apple do parceiro

O app fica na conta Apple Developer do parceiro e é assinado pelo time do parceiro. Ele não precisa estar na conta da Stone. A Stone está listada pela Apple como provedora de pagamentos (PSP) no Brasil.

  1. Pedido do entitlement: a pessoa com o papel Account Holder da conta faz o pedido no formulário de Tap to Pay on iPhone da Apple e indica a Stone como provedora de pagamentos. Em muitas empresas o Account Holder não é do time de desenvolvimento, então combine isso cedo.
  2. Análise da Apple: a Apple avalia cada pedido e não informa prazo. Quando aprova, o entitlement aparece na conta como managed capability.
  3. Entitlement de desenvolvimento: a primeira aprovação libera só o uso em desenvolvimento, com o app instalado pelo Xcode. Ele não permite TestFlight nem App Store.
  4. Entitlement de distribuição: depois dos testes, peça a liberação para distribuição respondendo o e-mail original da Apple. Só com ela é possível enviar builds pelo TestFlight e publicar na App Store.
  5. Revisão do app: a Apple confere se o app segue a Human Interface Guidelines e as diretrizes de marketing do Tap to Pay on iPhone, além das App Review Guidelines. O SDK cuida da vinculação da conta do lojista (aceite dos termos da Apple). A tela que explica ao lojista como usar o Tap to Pay fica com o app do parceiro.
Cronograma

Planeje o cronograma com as duas aprovações: o QA só recebe builds pelo TestFlight depois da segunda.

Configuração no Xcode

  1. No App ID do app (Certificates, Identifiers & Profiles), habilite a capability de Tap to Pay on iPhone.
  2. Gere um provisioning profile novo para esse App ID.
  3. No arquivo .entitlements do app:
<key>com.apple.developer.proximity-reader.payment.acceptance</key>
<true/>

Aparelho de teste (Sandbox)

Em SDX, o iPhone de teste precisa estar no ambiente de teste da Apple:

  1. Crie uma Sandbox Apple Account no App Store Connect da conta do parceiro.
  2. No iPhone de teste, entre com essa conta, conforme a orientação da Apple para a versão do iOS do aparelho.
  3. Reinicie o aparelho e reinstale o app.

Sem isso, o setup falha com 314 (INVALID_READER_TOKEN), mesmo com credenciais corretas.

Aceite dos termos pelo lojista

Na primeira ativação de cada aparelho, o iOS pede ao lojista que aceite os termos do Tap to Pay on iPhone com a conta Apple dele. O aparelho precisa estar com o iCloud conectado (senão o setup volta 309). Se o lojista recusar, volta 307.

Dependência

O canal de distribuição do pacote iOS (TapSDKiOS, com o framework TapCore) e as instruções de acesso são enviados pela Stone no início da integração.

Assim como no Android, existem as variantes de testes (SDX) e de produção.

Configuração

A criação do SDK e o setup estão em Setup do SDK (seções 3.1 e 3.2). Os tipos vêm do framework TapCore:

import TapCore

No Swift, os parâmetros com valor padrão no Kotlin precisam ser passados explicitamente, e objetos Kotlin são acessados por .shared (por exemplo, TapFeeScreens.shared).

Processar transação

O setup deve ser feito antes — veja Setup do SDK. O SDK exibe todas as telas da venda (Setup do SDK, seção 3.4); no iPhone, a tela de aproximação é a do Tap to Pay da Apple. O app só trata o resultado final.

let data = TransactionData(
    amount: 1000,              // R$ 10,00 (centavos)
    paymentMethod: .credit,    // .credit, .debit ou .voucher
    installments: 1,           // 1 = à vista
    softDescriptor: ""         // obrigatório; texto na fatura do cliente
)

paymentHandle = TapService.shared.sdk
    .pay(input: data)
    .watch(onCollect: { event in
        guard let onResult = event as? TransactionEvent.OnResult else { return }
        if onResult.result == ErrorStoneStatus.companion.SUCCESS {
            handleApproved(onResult.transactionResult)
        } else {
            handleError(onResult.result, onResult.transactionResult)
        }
    }, onError: { error in
        print("Erro na transação: \(error)")
    })
  • A forma de pagamento e as parcelas finais são escolhidas pelo lojista na tela do SDK. Com repasse da taxa, o valor cobrado muda; ele chega em TapFeeScreens.shared.onPaymentMethodConfirmed.
  • O watch entrega os eventos na main thread. O OnResult é sempre o último evento; os demais podem ser ignorados.

Cancelamento

O lojista cancela pelas telas do SDK ou pela tela da Apple; nesse caso o OnResult vem com 7011 (OPERATION_CANCELLED_BY_USER). Não chame dispose() no handle antes do OnResult: o SDK fecha as telas e o resultado se perde.

Exemplo completo — ObservableObject

Um ObservableObject que expõe o estado da venda para a UI observar via @Published.

final class PaymentViewModel: ObservableObject {
    @Published var uiState: PaymentUiState = .idle
    private var paymentHandle: TapDisposableHandle?

    init() {
        // Valor final da venda (pode ter repasse da taxa)
        TapFeeScreens.shared.onPaymentMethodConfirmed = { [weak self] finalData in
            self?.saveFinalAmount(finalData)
        }
        // Saída da venda aprovada: nova venda ou fechar
        TapFeeScreens.shared.onSaleFinished = { [weak self] exit in
            self?.uiState = exit == .newSale ? .newSale : .home
        }
    }

    func processPayment(amountCents: Int32) {
        let data = TransactionData(
            amount: amountCents,
            paymentMethod: .credit,
            installments: 1,
            softDescriptor: ""
        )
        paymentHandle = TapService.shared.sdk
            .pay(input: data)
            .watch(onCollect: { [weak self] event in
                guard let self,
                      let r = event as? TransactionEvent.OnResult else { return }
                if r.result == ErrorStoneStatus.companion.SUCCESS {
                    self.registerSale(r.transactionResult)
                } else {
                    self.uiState = .error(r.result.message)
                }
            }, onError: { [weak self] error in
                self?.uiState = .error(error.localizedDescription)
            })
    }
}

Veja também


Integração: Tap to Pay para Terceiros
Etapa 5 de 7