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 URLSession and Codable
  • Supports Swift concurrency (async/await)