etranzact-global-reloaded/xcel-paygate-sdk-swift
Official Swift SDK for integrating XCEL PayGate payment solutions into your iOS application.
Features
Core SDK
- ✅ Payment Link Generation - Create payment links via API
- ✅ Transaction Status - Check payment status by code
- ✅ XCEL Wallet Payments - Direct wallet-to-wallet transactions
- ✅ Merchant Products - Fetch and manage merchant products
- ✅ Type-Safe API - Full Swift type definitions with Sendable support
- ✅ SwiftUI Views - Ready-to-use payment UI components
- ✅ Async/Await - Modern Swift concurrency support
- ✅ Actor Isolation - Thread-safe API clients
SwiftUI Components
- 🎨 PaymentWebView - Embedded payment page with callbacks
- 🎨 PaymentView - Complete payment flow with loading states
- 🎨 ReceiptView - Beautiful receipt display
- 🎨 Cross-Platform - Works on iOS and macOS
Requirements
- iOS 15.0+ / macOS 12.0+ / watchOS 8.0+ / tvOS 15.0+
- Swift 5.9+
- Xcode 15.0+
Installation
Swift Package Manager
Add the following to your Package.swift file:
dependencies: [
.package(url: "https://github.com/etranzact-global-reloaded/xcel-paygate-sdk-swift.git", from: "1.0.0")
]Or in Xcode:
- Go to File > Add Package Dependencies
- Enter the repository URL
- Select version and add to your target
Quick Start
1. Initialize the SDK
import XcelPayGateSDK
let config = XcelPayGateConfig(
merchantId: "YOUR_MERCHANT_ID",
publicKey: "YOUR_PUBLIC_KEY"
)
let sdk = XcelPayGateSDK(config: config)2. Generate Payment Link
let request = GeneratePaymentLinkRequest(
amount: "10.00",
currency: "GHS",
clientTransactionId: "txn-\(UUID().uuidString)",
customerEmail: "customer@example.com",
customerPhone: "233244000000",
description: "Payment for Order #123",
channel: .mobile,
redirectUrl: "myapp://payment/success",
webhookUrl: "https://your-api.com/webhook"
)
do {
let response = try await sdk.checkout.initiateCheckout(request)
print("Payment Link: \(response.data.paymentLink)")
print("Payment Code: \(response.data.paymentCode)")
} catch {
print("Error: \(error)")
}3. Display Payment Page (SwiftUI)
import SwiftUI
import XcelPayGateSDK
struct CheckoutView: View {
@State private var showPayment = false
@State private var paymentUrl: URL?
var body: some View {
Button("Pay Now") {
initiatePayment()
}
.sheet(isPresented: $showPayment) {
if let url = paymentUrl {
NavigationView {
PaymentView(
paymentUrl: url,
merchantName: "My Store",
onSuccess: { receipt in
print("Payment successful: \(receipt.transactionId)")
showPayment = false
},
onFailure: { error in
print("Payment failed: \(error)")
showPayment = false
},
onCancel: {
showPayment = false
}
)
}
}
}
}
func initiatePayment() {
Task {
let request = GeneratePaymentLinkRequest(
amount: "50.00",
currency: "GHS",
clientTransactionId: "order-\(Date().timeIntervalSince1970)",
channel: .mobile,
redirectUrl: "myapp://payment/success"
)
do {
let response = try await sdk.checkout.initiateCheckout(request)
paymentUrl = URL(string: response.data.paymentLink)
showPayment = true
} catch {
print("Error: \(error)")
}
}
}
}4. Poll Payment Status
let result = try await sdk.checkout.pollTransactionStatus(
paymentCode: "PAYMENT_CODE",
maxAttempts: 24,
intervalSeconds: 5.0,
onStatusChange: { transaction in
print("Status: \(transaction.status)")
}
)
switch result.status {
case .success:
print("Payment completed!")
case .failed:
print("Payment failed")
case .pending:
print("Still pending...")
default:
break
}Advanced Usage
XCEL Wallet Integration
For direct wallet-to-wallet payments:
// 1. Verify customer's XCEL account
let verification = try await sdk.wallet.verifyAccount(
countryCode: "GH",
phoneNumber: "233542023469"
)
if let account = verification.data.first {
print("Account found: \(account.firstName) \(account.lastName)")
}
// 2. Create transaction
let products = [
CreateXcelTransactionRequest.TransactionProduct(
productId: "PROD_001",
amount: "50.00",
merchantFees: "1.50"
)
]
let transaction = try await sdk.wallet.createTransaction(
merchantId: config.merchantId,
payerWalletNo: "PAYER_WALLET",
posWalletNo: "POS_WALLET",
posSchemeCode: "SCHEME_CODE",
referenceId: "REF-\(UUID().uuidString)",
amount: "50.00",
fees: "1.50",
products: products
)
// 3. Poll for payment completion
let result = try await sdk.wallet.pollWalletPayment(
merchantId: config.merchantId,
externalReference: "REF-123",
intervals: [5, 15, 10],
onStatusChange: { transaction in
print("Paid: \(transaction.paid)")
}
)
if result.data.paid {
print("Payment completed!")
}Custom Transaction Options
Customize the checkout experience:
let customTxn = CustomTransaction(
editAmt: true,
minAmt: 5.0,
maxAmt: 1500.5,
borderTheme: "#9c27b0",
receiptSxMsg: "Thank you for your payment!",
receiptFeedbackPhone: "233241234567",
receiptFeedbackEmail: "support@example.com",
payLinkExpiryInDays: 15,
payLinkCanPayMultipleTimes: false,
displayPicture: "https://example.com/logo.png",
xtraCustomerInput: [
CustomTransactionInput(
label: "Order Number",
placeHolder: "Enter order number",
type: .input,
required: true
)
]
)
let request = GeneratePaymentLinkRequest(
amount: "50.00",
currency: "GHS",
clientTransactionId: "txn-123",
channel: .web,
redirectUrl: "myapp://success",
customTxn: customTxn
)Fetch Merchant Details and Products
// Get merchant details
let details = try await sdk.getMerchantDetails()
print("Merchant: \(details.data.data.regName)")
print("Currency: \(details.data.data.currency.currencyCode)")
// Get merchant products
let products = try await sdk.getMerchantProducts()
for product in products.data.data {
print("Product: \(product.name) - \(product.productId)")
}
// Get merchant fees
let fees = try await sdk.getMerchantFees()
print("Total Amount: \(fees.data.totalAmount)")Multiple Products in Payment
let request = GeneratePaymentLinkRequest(
amount: "100.00",
currency: "XAF",
clientTransactionId: "txn-123",
channel: .web,
redirectUrl: "myapp://success",
products: [
PaymentProduct(productId: "PROD_001", amount: "50"),
PaymentProduct(productId: "PROD_002", amount: "50")
]
)URL Parsing Utilities
Parse payment completion URLs manually:
// Parse URL parameters
if let data = PaymentURLParser.parsePaymentCompletionUrl(urlString) {
print("Payment Code: \(data.paymentCode ?? "")")
print("Amount: \(data.amount ?? "")")
print("Status: \(data.status ?? "")")
}
// Check URL status
if PaymentURLParser.isPaymentSuccessUrl(urlString) {
print("Payment succeeded!")
}
if PaymentURLParser.isPaymentFailureUrl(urlString) {
print("Payment failed!")
}
// Extract payment code
if let code = PaymentURLParser.extractPaymentCode(urlString) {
print("Code: \(code)")
}Receipt Generation
Format transaction data as receipt:
let receipt = PaymentReceiptFormatter.formatTransactionReceipt(
transaction: transactionData,
merchantName: "My Store"
)
// Generate formatted text
let receiptText = PaymentReceiptFormatter.formatReceiptText(receipt)
print(receiptText)
// Display in SwiftUI
NavigationLink("View Receipt") {
ReceiptView(receipt: receipt)
}SwiftUI Components
PaymentWebView
Basic web view for payment pages:
PaymentWebView(
url: paymentUrl,
onNavigationChange: { url in
print("Navigated to: \(url)")
},
onComplete: { result in
switch result {
case .success(let data):
print("Payment succeeded: \(data.paymentCode ?? "")")
case .failure(let error):
print("Payment failed: \(error)")
}
}
)PaymentView
Complete payment flow with states:
PaymentView(
paymentUrl: url,
merchantName: "ACME Corp",
onSuccess: { receipt in
// Navigate to receipt screen
showReceipt = true
},
onFailure: { error in
// Show error alert
alertMessage = error.localizedDescription
},
onCancel: {
// Dismiss payment
dismiss()
}
)ReceiptView
Display payment receipt:
ReceiptView(receipt: receipt)Payment Flow
Checkout URL Flow
- Generate Payment Link - Call
initiateCheckout()with payment details - Display Payment Page - Use
PaymentWebViewor open in Safari - Customer Pays - Customer completes payment on XCEL's hosted page
- Monitor Status - Use
pollTransactionStatus()to track payment - Handle Result - Process success/failure callbacks
XCEL Wallet Flow
- Verify Account - Verify customer's XCEL wallet account
- Generate Dynamic Link - Create OTP for transaction
- Create Transaction - Initialize the payment transaction
- Poll Status - Check transaction status at intervals
- Confirm Payment - Verify
paid: truein transaction response
Error Handling
All async methods throw typed errors:
do {
let response = try await sdk.checkout.initiateCheckout(request)
// Success
} catch XcelPayGateError.invalidResponse {
print("Invalid server response")
} catch XcelPayGateError.httpError(let code) {
print("HTTP error: \(code)")
} catch XcelPayGateError.apiError(let message) {
print("API error: \(message)")
} catch {
print("Unknown error: \(error)")
}Testing
Test the integration using the provided test credentials:
- Test Merchant ID:
0pf9ztq7q - Test Paygate URL:
https://paygate.xcelapp.com/pay/0pf9ztq7q
Best Practices
- Use Async/Await - All network calls are async
- Handle Errors - Always catch and handle errors appropriately
- Validate Payment Codes - Check payment codes in callbacks
- Store Transaction IDs - Keep records for reconciliation
- Secure Credentials - Never hardcode API keys in production
- Handle Expiry - Payment links expire after 24 hours by default
- Monitor Webhooks - Implement webhook handlers for reliable status updates
- Use HTTPS - Ensure redirect and webhook URLs use HTTPS
Thread Safety
All API clients use Swift actors for thread safety:
// Safe to call from any thread/task
Task {
let response = try await sdk.checkout.initiateCheckout(request)
}
Task.detached {
let status = try await sdk.checkout.checkTransactionStatus("CODE")
}Example App
Check out the example app in the Examples directory for a complete integration demonstration.
Support
- Documentation: XCEL API Docs
- Business Portal: business.xcelapp.com
- Issues: GitHub Issues
License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Package Metadata
Repository: etranzact-global-reloaded/xcel-paygate-sdk-swift
Default branch: main
README: README.md