Custom Credit Card UI
Build your own credit card form while using Moyasar's payment processing APIs.
Overview
To create a custom credit card form:
- Collect card details with your own UI
- Create an
ApiCreditCardSourcewith the card data - Submit the payment via
PaymentService - Handle 3DS authentication with a web view
Step 1: Create the Payment Service
import MoyasarSdk
class CustomPaymentViewModel: ObservableObject {
private let paymentService: PaymentService
init() {
paymentService = PaymentService(apiKey: "pk_test_YOUR_API_KEY")
}
}
Step 2: Create the Card Source and Payment Request
// Create the card source from your form inputs
let cardSource = ApiCreditCardSource(
name: "John Doe",
number: "4111111111111111",
month: "09",
year: "25",
cvc: "456",
manual: "false",
saveCard: "false"
)
// Create the payment request
let paymentRequest = try PaymentRequest(
apiKey: "pk_test_YOUR_API_KEY",
amount: 1000,
currency: "SAR",
description: "Order #12345"
)
// Wrap it in an ApiPaymentRequest
let apiPaymentRequest = ApiPaymentRequest(
paymentRequest: paymentRequest,
callbackUrl: "https://sdk.moyasar.com/return",
source: ApiPaymentSource.creditCard(cardSource)
)
info
Include "sdk": "ios" in the ApiPaymentRequest metadata when using a custom UI.
Step 3: Submit the Payment
func submitPayment() async {
do {
let payment = try await paymentService.createPayment(apiPaymentRequest)
await MainActor.run {
start3DSAuthentication(payment)
}
} catch let error as MoyasarError {
print("Payment creation failed: \(error)")
} catch {
print("Unexpected error: \(error)")
}
}
Step 4: Handle 3DS Authentication
If the payment requires 3DS authentication, redirect to the transaction URL:
func start3DSAuthentication(_ payment: ApiPayment) {
// Check if payment is still in initiated state
guard payment.isInitiated() else {
// Payment already completed (paid, failed, authorized, etc.)
return
}
// Extract the transaction URL
guard case let .creditCard(source) = payment.source,
let transactionUrl = source.transactionUrl,
let url = URL(string: transactionUrl) else {
return
}
// Present your web view with the 3DS URL
present3DSWebView(url)
}
Step 5: Present the 3DS Web View
Create a web view to handle the 3DS authentication flow:
import SwiftUI
import MoyasarSdk
import WebKit
struct PaymentAuthView: UIViewRepresentable {
let url: URL
let onResult: (WebViewResult) -> Void
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.navigationDelegate = context.coordinator
webView.load(URLRequest(url: url))
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(onResult: onResult)
}
class Coordinator: NSObject, WKNavigationDelegate {
let onResult: (WebViewResult) -> Void
init(onResult: @escaping (WebViewResult) -> Void) {
self.onResult = onResult
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// Check if URL matches callback URL to detect completion
if let url = webView.url, url.absoluteString.contains("sdk.moyasar.com/return") {
// Parse the result from URL parameters
// Call onResult with the appropriate WebViewResult
}
}
}
}
Step 6: Handle the Web View Result
func handleWebViewResult(_ result: WebViewResult) {
switch result {
case .completed(let info):
// Update your payment object with the result
currentPayment?.status = ApiPaymentStatus(rawValue: info.status)
if currentPayment?.status == .paid {
print("Payment successful: \(currentPayment!.id)")
} else if case let .creditCard(source) = currentPayment?.source {
print("Payment failed: \(source.message ?? "Unknown")")
}
case .failed(let error):
print("3DS authentication failed: \(error.localizedDescription)")
}
}
Complete Example
See the full implementation in the demo projects:
Next Steps
- Testing Guide — Test with sandbox cards
- Payment Status Reference — Understand payment statuses