Skip to content
SDK preview. Packages are not publicly published yet — use REST / cURL, or the local development artifacts.

iOS

Open PayTaka checkout from an iOS app with ASWebAuthenticationSession and handle the return — without a secret key in your app.

Your app never talks to PayTaka's payments API. It talks to your small backend, and that backend talks to PayTaka:

Text
iOS app ──▶ your small backend ──(PayTaka secret key)──▶ PayTaka
   ▲                  │
   └──── checkoutUrl ─┘
iOS app ──▶ PayTakaCheckout.present(checkoutURL) ──▶ customer pays ──▶ myapp://… ──▶ app
iOS app ──▶ your backend ──▶ "is my order paid?"       (the authoritative answer)

The PayTaka secret key stays on your backend. This SDK never stores or uses it. Don't have a backend yet? Read I only have an app.

1. Add the package#

PayTakaCheckout is a Swift Package (iOS 15+, macOS 12+). It has one job: open the checkout page and read the return link.

2. Present checkout#

Swift
let result = try await PayTakaCheckout.present(
    checkoutURL: checkoutURL,     // from YOUR backend, for an order
    callbackScheme: "myapp"       // your app's URL scheme
)

It uses ASWebAuthenticationSession, the system's secure browser sheet — never an in-app web view. The checkout URL must be https; javascript:, data:, file: and custom schemes are rejected with PayTakaCheckoutError.invalidCheckoutURL. The callbackScheme is separate: the scheme your backend puts in the payment's return_url (for example myapp://payment-return).

result contains paymentID, statusHint and returnURL.

If the customer closes the sheet, present throws PayTakaCheckoutError.closedByUser. That is not a failed payment — they may have already paid. Ask your backend for the order's status.

SwiftUI#

PayView.swift · Swift
import SwiftUI
import PayTakaCheckout

/// 1. Your backend creates the payment for an ORDER.  2. The app opens checkout.  3. The customer
/// returns through `myapp://payment-return`.  4. The app asks YOUR backend for the order's real status.
public struct PayView: View {
    private let backend = MerchantBackend(baseURL: URL(string: "https://your-backend.example.com")!)
    private let orderID = "order-1001"   // the order being paid; the server decides its amount
    @StateObject private var checkout = PayTakaCheckoutSession()
    @State private var message = "Ready"

    public init() {}

    public var body: some View {
        VStack(spacing: 16) {
            Button("Pay for \(orderID)") {
                Task {
                    do {
                        let url = try await backend.checkoutURL(forOrder: orderID)
                        checkout.start(checkoutURL: url, callbackScheme: "myapp")
                    } catch {
                        message = "Could not start payment"
                    }
                }
            }
            Text(message)
        }
        .onChange(of: checkout.state) { state in
            switch state {
            case .returned:
                // result.statusHint is only a hint — never fulfil or unlock from it.
                // Refresh the order from YOUR backend.
                message = "Checking your payment…"
                Task { message = "Order status: \((try? await backend.status(ofOrder: orderID)) ?? "unknown")" }
            case .closed:
                // The customer closed the sheet. That is NOT a failed payment: ask your backend.
                message = "Checkout closed. Checking your order…"
                Task { message = "Order status: \((try? await backend.status(ofOrder: orderID)) ?? "unknown")" }
            case .failed:
                message = "Could not open checkout"
            case .idle, .presenting:
                break
            }
        }
    }
}

PayTakaCheckoutSession is a tiny ObservableObject (states idle, presenting, returned, closed, failed). UIKit apps call PayTakaCheckout.present from a Task and may pass their own presentationAnchor.

MerchantBackend.swift · Swift
import Foundation

/// The app never talks to PayTaka's payments API and never holds a secret key. It asks YOUR backend
/// about an ORDER: the backend decides the amount from its own order, creates the payment with its
/// secret key, and returns only the checkout_url. The app never sends an amount.
struct MerchantBackend {
    let baseURL: URL

    func checkoutURL(forOrder orderID: String) async throws -> String {
        var request = URLRequest(url: baseURL.appendingPathComponent("orders/\(orderID)/pay"))
        request.httpMethod = "POST"
        let (data, _) = try await URLSession.shared.data(for: request)
        return try JSONDecoder().decode([String: String].self, from: data)["checkout_url"] ?? ""
    }

    /// The authoritative state of the order, from YOUR backend (which asks PayTaka / receives its webhook).
    func status(ofOrder orderID: String) async throws -> String {
        let url = baseURL.appendingPathComponent("orders/\(orderID)/status")
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode([String: String].self, from: data)["status"] ?? "unknown"
    }
}

Custom URL schemes (myapp://) can be claimed by any other app. Where practical, use a Universal Link — an https link on a domain you own, verified with apple-app-site-association — as your return_url and call the iOS 17.4+ overload:

Swift
try await PayTakaCheckout.present(
    checkoutURL: checkoutURL,
    universalLinkHost: "shop.example.com",
    path: "/payment-return"
)

Custom schemes remain fully supported and are the simplest way to start. If the app receives a link some other way (onOpenURL), PayTakaCheckout.parseReturn(url) reads the same hints and returns nil for links that aren't PayTaka returns.