iOS · Swift Package Manager · Swift 5.7+
Overview
The official iOS SDK wraps the SabPaisa Payment Gateway 2.0 API for Swift apps. It handles HMAC checksum signing with CryptoKit, presents the checkout page via SFSafariViewController, and exposes every call through Swift's native async/await — with no external dependencies beyond Apple's own frameworks.
Swift Concurrency
Every call is async/await — no callbacks or completion handlers
SFSafariViewController
redirectToCheckout presents the checkout for you
Zero Dependencies
URLSession, JSONSerialization & CryptoKit only
Auto Checksum
verifyReturnUrl validates the HMAC signature for you
Idempotency Keys
Safe refund retries with no duplicate charges
Typed Errors
A single SabPaisaError enum with a case per failure
Requirements
async/await, which requires iOS 13.0 and Swift 5.7 or later. redirectToCheckout presents SFSafariViewController, so it must be called from a UIKit view controller on the main thread.Installation
Install with Swift Package Manager. In Xcode, choose File › Add Package Dependencies, paste the repository URL, and add the SabPaisa package. Or declare it directly in your Package.swift:
1dependencies: [
2 .package(
3 url: "https://bitbucket.org/sabpaisa-wp-29/ios-plugin-2.0.git",
4 from: "1.0.0"
5 )
6]Setup
Create the client once (e.g. in your app startup or a shared service) and reuse it. SabPaisaConfig throws SabPaisaError.configError immediately if any required field is missing or env is invalid.
1import SabPaisa
2
3let config = try SabPaisaConfig(
4 apiKey: "your-api-key",
5 merchantId: "your-merchant-id",
6 secretKey: "your-secret-key",
7 clientCode: "your-client-code",
8 env: .production // or .staging
9)
10
11let sabpaisa = SabPaisaClient(config: config)secretKey signs requests on-device. For production apps that need to keep the secret off the client entirely, create the session on your backend and open the returned checkoutUrl with SFSafariViewController instead.Accept a Payment
50000 for ₹500.AGet the URL and present it yourself
Create a session, read the checkout URL, then open it however you like — SFSafariViewController, WKWebView, or UIApplication.shared.open.
1let request = CreatePaymentRequest(
2 merchantTxnId: "ORDER-\(orderId)",
3 amount: 50000,
4 customerName: "Ravi Kumar",
5 customerEmail: "[email protected]",
6 customerMobile: "9876543210",
7 udfFields: [ // optional user-defined fields (udf1–udf20)
8 "udf1": "campus-north",
9 "udf2": "semester-6",
10 "udf3": "hostel-block-A"
11 ]
12)
13
14let response = try await sabpaisa.payments.createSession(request)
15let checkoutUrl = sabpaisa.payments.getCheckoutUrl(response)
16
17// Open the URL however you prefer — SFSafariViewController, WKWebView, etc.BLet the SDK open the checkout Recommended
redirectToCheckout creates the session and presents SFSafariViewController automatically. Call it on the main thread from a UIKit view controller.
1// Must be called on the main thread
2let response = try await sabpaisa.payments.redirectToCheckout(
3 request,
4 from: viewController
5)Verify Payment After Redirect
When SabPaisa redirects the customer back, parse the callback URL's query parameters (keys are snake_case) into ReturnUrlParams and verify the signature before trusting the result. verifyReturnUrl recomputes the HMAC and returns a Bool.
1// Parse query parameters from the callback URL (keys are snake_case)
2let params = ReturnUrlParams(
3 transactionId: queryParams["transaction_id"] ?? "",
4 merchantTxnId: queryParams["merchant_txn_id"] ?? "",
5 status: queryParams["status"] ?? "",
6 amount: queryParams["amount"] ?? "",
7 paidAmount: queryParams["paid_amount"] ?? "",
8 paymentMode: queryParams["payment_mode"] ?? "",
9 timestamp: queryParams["timestamp"] ?? "",
10 signature: queryParams["signature"] ?? ""
11)
12
13if sabpaisa.payments.verifyReturnUrl(params) {
14 // Signature valid — check params.status for "SUCCESS" / "FAILED"
15 print("Payment status: \(params.status)")
16} else {
17 // Signature invalid — do not trust the result
18}You can also build the params straight from a query-parameter dictionary:
1// expects snake_case keys
2let params = ReturnUrlParams.fromJson(queryParamsDictionary)Transaction Enquiry
Look up the authoritative status of any transaction using your merchant transaction ID.
1let enquiry = try await sabpaisa.transactions.enquiry(
2 merchantTxnId: "ORDER-123"
3)
4
5print(enquiry.data.status) // "SUCCESS", "FAILED", "PENDING"
6print(enquiry.data.txnId) // SabPaisa transaction ID
7print(enquiry.data.paymentMode) // "UPI", "CARD", "NET_BANKING", etc.
8print(enquiry.data.udfData) // User-defined fields (Dictionary)Refunds
Initiate refunds, check refund status, and list refunds with optional filters.
1Create a refund
Pass the SabPaisa txnId from your enquiry, the amount in paise, and an optional reason.
1let refund = try await sabpaisa.refunds.create(CreateRefundRequest(
2 txnId: "sabpaisa-txn-id", // SabPaisa transaction ID from enquiry
3 amount: 50000, // refund amount in paise
4 reason: "Customer request"
5))
6
7print(refund.data.refundId)
8print(refund.data.status)Pass a RequestOptions idempotency key to safely retry without creating duplicates:
1let options = RequestOptions(idempotencyKey: "refund-ORDER-123-50000")
2let refund = try await sabpaisa.refunds.create(params, options: options)2Check refund status
1let status = try await sabpaisa.refunds.getStatus("refund-id")
2print(status.data.status) // "SUCCESS", "PENDING", "FAILED"3List refunds
Filter by transaction and paginate the full list.
1let list = try await sabpaisa.refunds.list(filters: RefundListParams(
2 txnId: "sabpaisa-txn-id",
3 page: 0,
4 size: 20
5))
6
7for refund in list.data {
8 print("\(refund.refundId) — \(refund.status)")
9}
10print("Total: \(list.pagination.total), Page: \(list.pagination.page + 1)")User-Defined Fields (UDF)
CreatePaymentRequest supports up to 20 user-defined fields (udf1 through udf20) via the udfFields dictionary. Each value can be up to 255 characters. These are returned in the enquiry response as udfData.
1let request = CreatePaymentRequest(
2 merchantTxnId: "ORDER-456",
3 amount: 50000,
4 customerName: "Ravi Kumar",
5 customerEmail: "[email protected]",
6 customerMobile: "9876543210",
7 udfFields: [
8 "udf1": "campus-north",
9 "udf2": "semester-6",
10 "udf3": "hostel-block-A",
11 "udf4": "engineering",
12 "udf5": "tuition-fee"
13 // ... up to udf20
14 ]
15)UDF data is returned in the enquiry response:
1let enquiry = try await sabpaisa.transactions.enquiry(
2 merchantTxnId: "ORDER-456"
3)
4
5if let udfData = enquiry.data.udfData {
6 for (key, value) in udfData {
7 print("\(key): \(value)")
8 }
9}Error Handling
Every throwing call surfaces a single SabPaisaError enum. Catch it with catch let error as SabPaisaError and switch over its cases.
| Error case | When it occurs |
|---|---|
| validationError(field, message) | Missing/invalid input — e.g. amount ≤ 0 or empty fields |
| apiError(details) | API returned an error; details.retryable is true for 429/5xx |
| checksumError(message) | Return URL signature verification failed |
| configError(message) | Missing or invalid SDK config fields |
| sdkError(message, code) | Network errors, timeouts, or internal SDK errors |
1do {
2 let response = try await sabpaisa.payments.createSession(request)
3} catch let error as SabPaisaError {
4 switch error {
5 case .validationError(let field, let message):
6 print("Invalid \(field): \(message)")
7 case .apiError(let details):
8 print("API error \(details.code): \(details.message) (HTTP \(details.statusCode))")
9 if details.retryable {
10 // Safe to retry (429 or 5xx)
11 }
12 case .checksumError(let message):
13 print("Checksum failed: \(message)")
14 case .configError(let message):
15 print("Config error: \(message)")
16 case .sdkError(let message, let code):
17 print("SDK error [\(code)]: \(message)")
18 }
19}CreatePaymentRequest Fields
| Parameter | Type | Required | Description |
|---|---|---|---|
| merchantTxnId | String | Yes | Your unique transaction/order ID |
| amount | Int | Yes | Amount in paise (50000 = ₹500) |
| customerName | String | Yes | Customer's full name |
| customerEmail | String | Yes | Customer's email address |
| customerMobile | String | Yes | Customer's mobile number |
| returnUrl | String? | No | Redirect URL after payment (SDK provides a default for in-app) |
| currency | String? | No | Currency code (default: "INR") |
| paymentMode | String? | No | Filter payment mode ("UPI", "CARD", "NB", etc.) |
| udfFields | [String: String]? | No | User-defined fields udf1–udf20 (max 255 chars each) |
| metadata | [String: String]? | No | Additional key-value metadata |
Configuration Reference
| Field | Required | Description |
|---|---|---|
| apiKey | Yes | API key from SabPaisa dashboard |
| merchantId | Yes | Merchant ID for X-Merchant-Id auth header |
| secretKey | Yes | HMAC secret for checksum signing |
| clientCode | Yes | Client/merchant code for API request bodies |
| env | Yes | .staging or .production |
| baseUrl | No | Custom URL override for proxies or local testing |
SDK Internals
The SDK is built entirely on Apple frameworks — nothing to vendor, nothing to audit beyond the platform itself.
| Feature | Detail |
|---|---|
| HTTP client | URLSession (no external dependencies) |
| JSON | JSONSerialization (Foundation) |
| Crypto | CryptoKit (Apple built-in) |
| Retry | Exponential backoff (500ms, 1s, 2s) — max 2 retries |
| Safe retry | Only GET/HEAD or requests with an idempotency key |
| Timeout | 30 seconds default, configurable per request |
| User-Agent | sabpaisa-ios/1.0.0 |
| HMAC comparison | Constant-time (timing-safe) |
Need Help?
For integration support, contact the SabPaisa technical team with:
- •Your merchant ID
- •The trace ID from the error response (
apiErrordetails) - •The SDK version (
1.0.0)
Was this page helpful?
Related Pages
SDKs & Libraries
Choose your platform — Flutter, Java, Python, Node.js
Flutter SDK
Official Flutter SDK — Android, iOS, Web
React Native SDK
Official React Native SDK — pure JS, iOS & Android
Java SDK
Official Java SDK — Spring Boot, Jakarta EE, Quarkus
Quick Start
Get up and running in 30 minutes
API Reference
Complete endpoint documentation
Webhooks
Real-time payment notifications