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:
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#
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#
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.
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"
}
}Universal Links (recommended)#
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:
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.