Mobile
Swift SDK
Native Swift implementation for integrating YardPay Pro into iOS and macOS apps. Uses URLSession with async/await — no dependencies.
API Client
Create YardPayProClient.swift
// YardPayProClient.swift
import Foundation
enum YPPError: LocalizedError {
case apiError(status: Int, code: String, message: String)
case networkError(Error)
case decodingError(Error)
var errorDescription: String? {
switch self {
case .apiError(let status, let code, let message):
return "[\(status)] \(code): \(message)"
case .networkError(let error):
return "Network error: \(error.localizedDescription)"
case .decodingError(let error):
return "Decoding error: \(error.localizedDescription)"
}
}
}
class YardPayProClient {
let apiKey: String
let baseURL: String
private let session: URLSession
private let decoder = JSONDecoder()
private let encoder = JSONEncoder()
init(apiKey: String, baseURL: String) {
self.apiKey = apiKey
self.baseURL = baseURL
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
self.session = URLSession(configuration: config)
}
func request<T: Decodable>(_ method: String, path: String,
body: Encodable? = nil,
params: [String: String]? = nil) async throws -> T {
var components = URLComponents(string: baseURL + path)!
if let params = params {
components.queryItems = params.map { URLQueryItem(name: $0.key, value: $0.value) }
}
var request = URLRequest(url: components.url!)
request.httpMethod = method
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(apiKey, forHTTPHeaderField: "x-api-key")
if let body = body {
request.httpBody = try encoder.encode(AnyEncodable(body))
}
let (data, response) = try await session.data(for: request)
let httpResponse = response as! HTTPURLResponse
guard (200...299).contains(httpResponse.statusCode) else {
if let err = try? decoder.decode(APIErrorResponse.self, from: data) {
throw YPPError.apiError(
status: httpResponse.statusCode,
code: err.code ?? "api_error",
message: err.message ?? "Unknown error"
)
}
throw YPPError.apiError(
status: httpResponse.statusCode,
code: "api_error",
message: HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode)
)
}
if httpResponse.statusCode == 204 || data.isEmpty {
return EmptyResponse() as! T
}
// Handle OData { "value": [...] } wrapper
if let wrapper = try? decoder.decode(ODataWrapper<T>.self, from: data) {
return wrapper.value
}
do {
return try decoder.decode(T.self, from: data)
} catch {
throw YPPError.decodingError(error)
}
}
}
// --- Helpers ---
private struct APIErrorResponse: Decodable {
let code: String?
let message: String?
}
private struct ODataWrapper<T: Decodable>: Decodable {
let value: T
}
struct EmptyResponse: Decodable {}
private struct AnyEncodable: Encodable {
private let _encode: (Encoder) throws -> Void
init(_ wrapped: Encodable) {
_encode = wrapped.encode
}
func encode(to encoder: Encoder) throws {
try _encode(encoder)
}
}Models
Create YPPModels.swift
// YPPModels.swift
import Foundation
struct PayInvoiceRequest: Encodable {
var invoiceId: Int?
var invoiceIdentifier: String?
var paymentOptionId: Int
var fundingSourceId: Int?
var card: CardInfo?
var billingAddress: BillingAddress?
}
struct CardInfo: Codable {
var name: String
var cardNumber: String
var expiryDate: String
var cvv: String
}
struct BillingAddress: Encodable {
var firstName: String
var lastName: String
var addressLine1: String
var city: String
var country: String
}
struct PaymentResponse: Decodable {
let referenceNumber: String?
let amountPaid: Double?
let amountDue: Double?
let currency: String?
let paymentStatusId: String?
let isSuccess: Bool
let approvalAction: String?
let approvalDataOrUrl: String?
let hostMessage: String?
}
struct YPPPaymentMethod: Decodable {
let id: Int
let code: String
let name: String
let paymentMethodId: String
let isEnabled: Bool
let currencyCode: String?
}
struct FundingSource: Decodable {
let id: Int
let name: String
let paymentOptionId: Int
let statusId: String
let isVerified: Bool
let maskedAccountNumber: String
}
struct Invoice: Decodable {
let id: Int
let identifier: String
let referenceNumber: String?
let invoiceNumber: String?
let description: String?
let status: String
let totalDue: Double?
let totalPaid: Double?
let totalCost: Double
let currencyCode: String
}
struct CreateInvoiceRequest: Encodable {
var description: String?
var currencyCode: String
var paymentDueDate: String?
var items: [InvoiceItemRequest]
var sourceInfo: CounterPartyInfo
var successUrl: String?
}
struct InvoiceItemRequest: Encodable {
var name: String
var unitCost: Double
var quantity: Int
var taxRate: Double?
var invoiceItemTypeId: String?
}
struct CounterPartyInfo: Encodable {
var firstName: String
var lastName: String
var email: String
}Services
Create YPPServices.swift
// YPPServices.swift
import Foundation
class PaymentsService {
private let client: YardPayProClient
init(client: YardPayProClient) { self.client = client }
func payInvoice(_ request: PayInvoiceRequest) async throws -> PaymentResponse {
try await client.request("POST", path: "/api/payment/payinvoice", body: request)
}
func getPaymentMethods(invoiceId: Int) async throws -> [YPPPaymentMethod] {
try await client.request("GET", path: "/v1/invoice/\(invoiceId)/PaymentMethods")
}
func getFundingSources() async throws -> [FundingSource] {
try await client.request("GET", path: "/v1/FundingSource")
}
}
class InvoicesService {
private let client: YardPayProClient
init(client: YardPayProClient) { self.client = client }
func getByReference(code: String) async throws -> Invoice {
try await client.request("GET", path: "/v1/invoice/getByReference(code='\(code)')")
}
func getById(_ id: Int) async throws -> Invoice {
try await client.request("GET", path: "/v1/invoice/\(id)")
}
func create(_ request: CreateInvoiceRequest) async throws -> Invoice {
try await client.request("POST", path: "/v1/invoice", body: request)
}
func qrCodeURL(identifier: String) -> URL {
URL(string: "\(client.baseURL)/v1/invoice/qrcode/\(identifier)")!
}
}SwiftUI Usage
import SwiftUI
struct PaymentView: View {
let invoiceCode: String
@State private var invoice: Invoice?
@State private var isLoading = true
@State private var error: String?
@State private var paymentResult: PaymentResponse?
private let client = YardPayProClient(apiKey: "sk_test_YOUR_KEY", baseURL: "https://your-server.com")
private lazy var payments = PaymentsService(client: client)
private lazy var invoices = InvoicesService(client: client)
var body: some View {
VStack(spacing: 20) {
if isLoading {
ProgressView()
} else if let error {
Text("Error: \(error)").foregroundStyle(.red)
} else if let invoice {
Text(invoice.description ?? "Invoice")
.font(.title2)
Text("\(invoice.currencyCode) \(invoice.totalCost, specifier: "%.2f")")
.font(.largeTitle.bold())
Spacer()
if let result = paymentResult, result.isSuccess {
Label("Paid: \(result.referenceNumber ?? "")", systemImage: "checkmark.circle.fill")
.foregroundStyle(.green)
} else {
Button("Pay Now") {
Task { await pay() }
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
}
}
}
.padding()
.task { await loadInvoice() }
}
private func loadInvoice() async {
do {
let inv = try await InvoicesService(client: client).getByReference(code: invoiceCode)
invoice = inv
isLoading = false
} catch {
self.error = error.localizedDescription
isLoading = false
}
}
private func pay() async {
guard let invoice else { return }
isLoading = true
do {
let result = try await PaymentsService(client: client).payInvoice(
PayInvoiceRequest(
invoiceIdentifier: invoice.identifier,
paymentOptionId: 1,
card: CardInfo(
name: "Test User",
cardNumber: "4111111111111111",
expiryDate: "12/28",
cvv: "123"
)
)
)
paymentResult = result
isLoading = false
} catch {
self.error = error.localizedDescription
isLoading = false
}
}
}Requirements
- Swift 5.9+ / Xcode 15.0+
- iOS 15.0+ / macOS 12.0+
- No external dependencies — uses
URLSessionandCodable - Supports Swift concurrency (async/await)