diff --git a/Sources/RazorpayKit/Constants/APIConstants.swift b/Sources/RazorpayKit/Constants/APIConstants.swift index 64578bf..3422658 100644 --- a/Sources/RazorpayKit/Constants/APIConstants.swift +++ b/Sources/RazorpayKit/Constants/APIConstants.swift @@ -1,31 +1,93 @@ +/// Constants defining the API endpoints and URLs used by the Razorpay API +/// +/// This struct contains static constants for all the API endpoints used to interact with +/// Razorpay's REST API. The constants include the base URL and paths for various resources +/// like orders, payments, customers etc. public struct APIConstants { + /// The base URL for all Razorpay API requests static let baseURL = "https://api.razorpay.com" + + /// API version 1 path component static let v1 = "/v1" + + /// API version 2 path component static let v2 = "/v2" + + /// Path for orders related endpoints static let orderURL = "/orders" + + /// Path for invoice related endpoints static let invoiceURL = "/invoices" + + /// Path for payment related endpoints static let paymentURL = "/payments" + + /// Path for payment links related endpoints static let paymentLinkURL = "/payment_links" + + /// Path for refund related endpoints static let refundURL = "/refunds" + + /// Path for card related endpoints static let cardURL = "/cards" + + /// Path for customer related endpoints static let customerURL = "/customers" + + /// Path for addon related endpoints static let addonURL = "/addons" + + /// Path for transfer related endpoints static let transferURL = "/transfers" + + /// Path for virtual account related endpoints static let virtualAccountURL = "/virtual_accounts" + + /// Path for subscription related endpoints static let subscriptionURL = "/subscriptions" + + /// Path for plan related endpoints static let planURL = "/plans" + + /// Path for QR code payment related endpoints static let qrCodeURL = "/payments/qr_codes" + + /// Path for fund account related endpoints static let fundAccountURL = "/fund_accounts" + + /// Path for settlement related endpoints static let settlementURL = "/settlements" + + /// Path for item related endpoints static let itemURL = "/items" + + /// Path for payment methods related endpoints static let methodsURL = "/methods" + + /// Path for account related endpoints static let accountURL = "/accounts" + + /// Path for stakeholder related endpoints static let stakeholderURL = "/stakeholders" + + /// Path for product related endpoints static let productURL = "/products" + + /// Path for terms and conditions related endpoints static let tncURL = "/tnc" + + /// Path for IIN (Issuer Identification Number) related endpoints static let iinURL = "/iins" + + /// Path for webhook related endpoints static let webhookURL = "/webhooks" + + /// Alternative path for IIN related endpoints static let iin: String = "/iins" + + /// Alternative path for terms and conditions static let tnc: String = "/tnc" + + /// Alternative path for webhooks static let webhook: String = "/webhooks" } diff --git a/Sources/RazorpayKit/Constants/ErrorCode.swift b/Sources/RazorpayKit/Constants/ErrorCode.swift index 0cb1737..4420bce 100644 --- a/Sources/RazorpayKit/Constants/ErrorCode.swift +++ b/Sources/RazorpayKit/Constants/ErrorCode.swift @@ -1,5 +1,16 @@ +/// Error codes returned by the Razorpay API +/// +/// These error codes indicate different types of failures that can occur during API interactions: +/// - ``badRequestError``: Indicates invalid parameters or malformed request +/// - ``gatewayError``: Indicates an error occurred at the payment gateway level +/// - ``serverError``: Indicates an internal server error at Razorpay enum ErrorCode: String { + /// Error code indicating invalid parameters or malformed request case badRequestError = "BAD_REQUEST_ERROR" + + /// Error code indicating a payment gateway level error case gatewayError = "GATEWAY_ERROR" + + /// Error code indicating an internal Razorpay server error case serverError = "SERVER_ERROR" } diff --git a/Sources/RazorpayKit/Errors/RZPError.swift b/Sources/RazorpayKit/Errors/RZPError.swift index 80ffc9c..68f08d2 100644 --- a/Sources/RazorpayKit/Errors/RZPError.swift +++ b/Sources/RazorpayKit/Errors/RZPError.swift @@ -1,13 +1,49 @@ import Foundation -// Define a custom error type that encompasses various Razorpay errors. +/// A custom error type that encompasses various Razorpay API errors. +/// +/// `RZPError` provides structured error information returned by the Razorpay API, +/// including error messages, codes, and additional context. enum RZPError: Error { + /// Indicates an error due to invalid request parameters or format. + /// - Parameters: + /// - message: The main error message + /// - internalErrorCode: Internal error code from Razorpay + /// - field: The specific field that caused the error, if applicable + /// - description: Detailed description of the error + /// - code: Error code string case badRequestError(message: String, internalErrorCode: String?, field: String?, description: String?, code: String?) + + /// Indicates an internal server error from Razorpay. + /// - Parameters: + /// - message: The main error message + /// - internalErrorCode: Internal error code from Razorpay + /// - field: The specific field that caused the error, if applicable + /// - description: Detailed description of the error + /// - code: Error code string case serverError(message: String, internalErrorCode: String?, field: String?, description: String?, code: String?) + + /// Indicates an error from the payment gateway. + /// - Parameters: + /// - message: The main error message + /// - internalErrorCode: Internal error code from Razorpay + /// - field: The specific field that caused the error, if applicable + /// - description: Detailed description of the error + /// - code: Error code string case gatewayError(message: String, internalErrorCode: String?, field: String?, description: String?, code: String?) + + /// Indicates a failure in webhook signature verification. + /// - Parameters: + /// - message: The main error message + /// - internalErrorCode: Internal error code from Razorpay + /// - field: The specific field that caused the error, if applicable + /// - description: Detailed description of the error + /// - code: Error code string case signatureVerificationError(message: String, internalErrorCode: String?, field: String?, description: String?, code: String?) - // Custom string representation of the error + /// A brief description of the error. + /// + /// Returns only the main error message without additional context. var errorDescription: String { switch self { case .badRequestError(let message, _, _, _, _), @@ -18,6 +54,10 @@ enum RZPError: Error { } } + /// A detailed description of the error including all available context. + /// + /// Returns a formatted string containing the error message, code, field, description, + /// and error code when available. Uses "N/A" for missing values. var detailedError: String { switch self { case .badRequestError(message: let message, internalErrorCode: let code, field: let field, description: let description, code: let errorCode): @@ -28,4 +68,3 @@ enum RZPError: Error { } } } - diff --git a/Sources/RazorpayKit/RazorpayClient.swift b/Sources/RazorpayKit/RazorpayClient.swift index c5ff716..c89b158 100644 --- a/Sources/RazorpayKit/RazorpayClient.swift +++ b/Sources/RazorpayKit/RazorpayClient.swift @@ -1,32 +1,124 @@ import NIO import AsyncHTTPClient +/// A client for interacting with the Razorpay API. +/// +/// The `RazorpayClient` provides access to all Razorpay API endpoints through dedicated route handlers. +/// Initialize it with your API credentials to start making API requests. +/// +/// ```swift +/// let httpClient = HTTPClient(eventLoopGroupProvider: .shared) +/// let razorpay = RazorpayClient(httpClient: httpClient, +/// key: "your_key", +/// secret: "your_secret") +/// ``` +/// +/// ## Topics +/// +/// ### Account Management +/// - ``account`` +/// - ``stakeholder`` +/// +/// ### Payment Processing +/// - ``payment`` +/// - ``paymentLink`` +/// - ``order`` +/// - ``refund`` +/// +/// ### Customer Management +/// - ``customer`` +/// - ``card`` +/// - ``token`` +/// +/// ### Banking & Transfers +/// - ``fundAccount`` +/// - ``transfer`` +/// - ``virtualAccount`` +/// - ``settlement`` +/// +/// ### Products & Subscriptions +/// - ``product`` +/// - ``subscription`` +/// - ``invoice`` +/// - ``item`` +/// +/// ### Additional Features +/// - ``addon`` +/// - ``iin`` +/// - ``qrCode`` +/// - ``webhook`` public final class RazorpayClient { + /// Routes for managing Razorpay accounts public var account: RazorpayAccountRoutes + + /// Routes for managing addons public var addon: RazorpayAddonRoutes + + /// Routes for managing cards public var card: RazorpayCardRoutes + + /// Routes for managing customers public var customer: RazorpayCustomerRoutes + + /// Routes for managing fund accounts public var fundAccount: RazorpayFundAccountRoutes + + /// Routes for managing IIN (Issuer Identification Numbers) public var iin: RazorpayIINRoutes + + /// Routes for managing invoices public var invoice: RazorpayInvoiceRoutes + + /// Routes for managing items public var item: RazorpayItemRoutes + + /// Routes for managing orders public var order: RazorpayOrderRoutes + + /// Routes for managing payments public var payment: RazorpayPaymentRoutes + + /// Routes for managing payment links public var paymentLink: RazorpayPaymentLinkRoutes + + /// Routes for managing products public var product: RazorpayProductRoutes + + /// Routes for managing QR codes public var qrCode: RazorpayQRCodeRoutes + + /// Routes for managing refunds public var refund: RazorpayRefundRoutes + + /// Routes for managing settlements public var settlement: RazorpaySettlementRoutes + + /// Routes for managing stakeholders public var stakeholder: RazorpayStakeholderRoutes + + /// Routes for managing subscriptions public var subscription: RazorpaySubscriptionRoutes + + /// Routes for managing tokens public var token: RazorpayTokenRoutes + + /// Routes for managing transfers public var transfer: RazorpayTransferRoutes + + /// Routes for managing virtual accounts public var virtualAccount: RazorpayVirtualAccountRoutes + + /// Routes for managing webhooks public var webhook: RazorpayWebhookRoutes var handler: RazorpayAPIHandler + /// Creates a new Razorpay API client. + /// - Parameters: + /// - httpClient: The HTTP client to use for making requests + /// - key: Your Razorpay API key + /// - secret: Your Razorpay API secret public init(httpClient: HTTPClient, key: String, secret: String) { self.handler = RazorpayAPIHandler(httpClient: httpClient, key: key, secret: secret) account = RazorpayAccountRoutes(client: handler) diff --git a/Sources/RazorpayKit/RazorpayRequest.swift b/Sources/RazorpayKit/RazorpayRequest.swift index 0dc7f10..6a39023 100644 --- a/Sources/RazorpayKit/RazorpayRequest.swift +++ b/Sources/RazorpayKit/RazorpayRequest.swift @@ -4,6 +4,7 @@ import NIOFoundationCompat import NIOHTTP1 import AsyncHTTPClient + extension HTTPClientRequest.Body { public static func string(_ string: String) -> Self { .bytes(.init(string: string)) @@ -43,8 +44,11 @@ struct RazorpayAPIHandler { let queryString = RZPRUTL.convertToQueryString(queryParams) let url = APIConstants.baseURL + path + queryString - var requestHeaders: HTTPHeaders = ["Authorization": authorizationHeader(), "Content-Type": "application/json", - "Accept": "application/json"] + var requestHeaders: HTTPHeaders = [ + "Authorization": authorizationHeader(), + "Content-Type": "application/json", + "Accept": "application/json" + ] headers.forEach { requestHeaders.add(name: $0.name, value: $0.value) } var request = HTTPClientRequest(url: url) diff --git a/Sources/RazorpayKit/Resources/Payment.swift b/Sources/RazorpayKit/Resources/Payment.swift index bc48d72..94aaca1 100644 --- a/Sources/RazorpayKit/Resources/Payment.swift +++ b/Sources/RazorpayKit/Resources/Payment.swift @@ -4,73 +4,257 @@ import NIOHTTP1 import AsyncHTTPClient /// A protocol defining the routes for interacting with payments in the Razorpay API. +/// +/// This protocol provides a comprehensive interface for managing payments through the Razorpay payment gateway. +/// It includes methods for creating, fetching, capturing, refunding payments as well as handling UPI transactions, +/// card details, and OTP-based authentication. +/// +/// ## Topics +/// +/// ### Payment Management +/// - ``all(queryParams:extraHeaders:)`` +/// - ``fetch(paymentID:queryParams:extraHeaders:)`` +/// - ``capture(paymentID:amount:data:extraHeaders:)`` +/// - ``edit(paymentID:data:extraHeaders:)`` +/// +/// ### Refunds +/// - ``refund(paymentID:amount:data:extraHeaders:)`` +/// - ``fetchMultipleRefund(paymentId:queryParams:extraHeaders:)`` +/// - ``fetchRefund(paymentId:refundId:queryParams:extraHeaders:)`` +/// +/// ### Transfers +/// - ``transfer(paymentID:data:extraHeaders:)`` +/// - ``transfers(paymentID:queryParams:extraHeaders:)`` +/// - ``bankTransfer(paymentID:queryParams:extraHeaders:)`` +/// +/// ### Payment Creation +/// - ``createPaymentJson(data:extraHeaders:)`` +/// - ``createRecurringPayment(data:extraHeaders:)`` +/// - ``createUpi(data:extraHeaders:)`` +/// +/// ### Card & UPI Operations +/// - ``fetchCardDetails(paymentID:queryParams:extraHeaders:)`` +/// - ``validateVpa(data:extraHeaders:)`` +/// +/// ### System Status +/// - ``fetchPaymentDowntime(queryParams:extraHeaders:)`` +/// - ``fetchPaymentDowntimeById(downtimeId:queryParams:extraHeaders:)`` +/// +/// ### OTP Management +/// - ``otpGenerate(paymentId:queryParams:extraHeaders:)`` +/// - ``otpSubmit(paymentId:data:extraHeaders:)`` +/// - ``otpResend(paymentId:queryParams:extraHeaders:)`` public protocol PaymentRoutes: RazorpayAPIRoute { /// Fetches all payments with optional query parameters and extra headers. + /// - Parameters: + /// - queryParams: Optional query parameters to filter the payments + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the payment information + /// - Throws: An error if the request fails func all(queryParams: [String: String]?, extraHeaders: [String: String]?) async throws -> [String: Any] /// Fetches a payment by its ID. + /// - Parameters: + /// - paymentID: The unique identifier of the payment + /// - queryParams: Optional query parameters + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the payment details + /// - Throws: An error if the request fails func fetch(paymentID: String, queryParams: [String: String]?, extraHeaders: [String: String]?) async throws -> [String: Any] /// Captures a payment with the given ID and amount. + /// - Parameters: + /// - paymentID: The unique identifier of the payment to capture + /// - amount: The amount to capture in smallest currency unit (e.g., paise for INR) + /// - data: Additional data for the capture request + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the capture response + /// - Throws: An error if the capture fails func capture(paymentID: String, amount: Int, data: [String: Any], extraHeaders: [String: String]?) async throws -> [String: Any] /// Initiates a refund for a payment with the given ID and amount. + /// - Parameters: + /// - paymentID: The unique identifier of the payment to refund + /// - amount: The amount to refund in smallest currency unit + /// - data: Additional data for the refund request + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the refund details + /// - Throws: An error if the refund fails func refund(paymentID: String, amount: Int, data: [String: Any], extraHeaders: [String: String]?) async throws -> [String: Any] /// Creates a transfer for a payment with the given ID. + /// - Parameters: + /// - paymentID: The unique identifier of the payment + /// - data: Transfer details and parameters + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the transfer details + /// - Throws: An error if the transfer creation fails func transfer(paymentID: String, data: [String: Any], extraHeaders: [String: String]?) async throws -> [String: Any] /// Fetches all transfers associated with a payment ID. + /// - Parameters: + /// - paymentID: The unique identifier of the payment + /// - queryParams: Optional query parameters to filter transfers + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the transfers information + /// - Throws: An error if the request fails func transfers(paymentID: String, queryParams: [String: String]?, extraHeaders: [String: String]?) async throws -> [String: Any] /// Fetches bank transfer details associated with a payment ID. + /// - Parameters: + /// - paymentID: The unique identifier of the payment + /// - queryParams: Optional query parameters + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the bank transfer details + /// - Throws: An error if the request fails func bankTransfer(paymentID: String, queryParams: [String: String]?, extraHeaders: [String: String]?) async throws -> [String: Any] /// Creates a JSON payment with the given data. + /// - Parameters: + /// - data: Payment details and parameters + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the created payment details + /// - Throws: An error if the payment creation fails func createPaymentJson(data: [String: Any], extraHeaders: [String: String]?) async throws -> [String: Any] /// Creates a recurring payment with the given data. + /// - Parameters: + /// - data: Recurring payment details and parameters + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the recurring payment details + /// - Throws: An error if the recurring payment creation fails func createRecurringPayment(data: [String: Any], extraHeaders: [String: String]?) async throws -> [String: Any] /// Updates a payment with the given ID and data. + /// - Parameters: + /// - paymentID: The unique identifier of the payment to update + /// - data: Updated payment details + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the updated payment details + /// - Throws: An error if the update fails func edit(paymentID: String, data: [String: Any], extraHeaders: [String: String]?) async throws -> [String: Any] /// Fetches card details for a payment with the given ID. + /// - Parameters: + /// - paymentID: The unique identifier of the payment + /// - queryParams: Optional query parameters + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the card details + /// - Throws: An error if the request fails func fetchCardDetails(paymentID: String, queryParams: [String: String]?, extraHeaders: [String: String]?) async throws -> [String: Any] /// Fetches payment downtime details. + /// - Parameters: + /// - queryParams: Optional query parameters to filter downtime information + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the downtime details + /// - Throws: An error if the request fails func fetchPaymentDowntime(queryParams: [String: String]?, extraHeaders: [String: String]?) async throws -> [String: Any] /// Fetches payment downtime details by ID. + /// - Parameters: + /// - downtimeId: The unique identifier of the downtime period + /// - queryParams: Optional query parameters + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the specific downtime details + /// - Throws: An error if the request fails func fetchPaymentDowntimeById(downtimeId: String, queryParams: [String: String]?, extraHeaders: [String: String]?) async throws -> [String: Any] /// Fetches multiple refund details for a payment ID. + /// - Parameters: + /// - paymentId: The unique identifier of the payment + /// - queryParams: Optional query parameters to filter refunds + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing multiple refund details + /// - Throws: An error if the request fails func fetchMultipleRefund(paymentId: String, queryParams: [String: String]?, extraHeaders: [String: String]?) async throws -> [String: Any] /// Fetches refund detail with the given payment and refund IDs. + /// - Parameters: + /// - paymentId: The unique identifier of the payment + /// - refundId: The unique identifier of the refund + /// - queryParams: Optional query parameters + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the specific refund details + /// - Throws: An error if the request fails func fetchRefund(paymentId: String, refundId: String, queryParams: [String: String]?, extraHeaders: [String: String]?) async throws -> [String: Any] /// Creates a UPI payment with the given data. + /// - Parameters: + /// - data: UPI payment details and parameters + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the UPI payment details + /// - Throws: An error if the UPI payment creation fails func createUpi(data: [String: Any], extraHeaders: [String: String]?) async throws -> [String: Any] /// Validates a VPA with the given data. + /// - Parameters: + /// - data: VPA details to validate + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the validation result + /// - Throws: An error if the validation fails func validateVpa(data: [String: Any], extraHeaders: [String: String]?) async throws -> [String: Any] /// Fetches payment methods with the given data. + /// - Parameters: + /// - data: Optional parameters to filter payment methods + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing available payment methods + /// - Throws: An error if the request fails func fetchMethods(data: [String: String]?, extraHeaders: [String: String]?) async throws -> [String: Any] /// Generates OTP for a payment with the given ID. + /// - Parameters: + /// - paymentId: The unique identifier of the payment + /// - queryParams: Optional query parameters + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the OTP generation response + /// - Throws: An error if the OTP generation fails func otpGenerate(paymentId: String, queryParams: [String: String]?, extraHeaders: [String: String]?) async throws -> [String: Any] /// Submits OTP for a payment with the given ID and data. + /// - Parameters: + /// - paymentId: The unique identifier of the payment + /// - data: OTP and related data + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the OTP submission response + /// - Throws: An error if the OTP submission fails func otpSubmit(paymentId: String, data: [String: Any], extraHeaders: [String: String]?) async throws -> [String: Any] /// Resends OTP for a payment with the given ID. + /// - Parameters: + /// - paymentId: The unique identifier of the payment + /// - queryParams: Optional query parameters + /// - extraHeaders: Optional additional headers for the request + /// - Returns: A dictionary containing the OTP resend response + /// - Throws: An error if the OTP resend fails func otpResend(paymentId: String, queryParams: [String: String]?, extraHeaders: [String: String]?) async throws -> [String: Any] } /// A struct implementing the `PaymentRoutes` protocol for Razorpay payment operations. +/// +/// This struct provides the concrete implementation of all payment-related operations +/// defined in the ``PaymentRoutes`` protocol. It handles the actual HTTP requests to +/// the Razorpay API endpoints and processes the responses. +/// +/// ## Topics +/// +/// ### Initialization +/// ```swift +/// let client = RazorpayAPIHandler() +/// let paymentRoutes = RazorpayPaymentRoutes(client: client) +/// ``` +/// +/// ### Usage Example +/// ```swift +/// // Fetch payment details +/// let paymentDetails = try await paymentRoutes.fetch(paymentID: "pay_123456") +/// +/// // Create a new payment +/// let paymentData = ["amount": 1000, "currency": "INR"] +/// let newPayment = try await paymentRoutes.createPaymentJson(data: paymentData) +/// ``` public struct RazorpayPaymentRoutes: PaymentRoutes { public var headers: HTTPHeaders = [:] private let client: RazorpayAPIHandler diff --git a/Sources/RazorpayKit/Utils/RazorpayUtility.swift b/Sources/RazorpayKit/Utils/RazorpayUtility.swift index 5681999..66be05d 100644 --- a/Sources/RazorpayKit/Utils/RazorpayUtility.swift +++ b/Sources/RazorpayKit/Utils/RazorpayUtility.swift @@ -2,8 +2,23 @@ import Foundation import AsyncHTTPClient import NIOHTTP1 +/// A utility structure containing helper methods for Razorpay API operations. +/// +/// This structure provides static utility functions for common operations like: +/// - Converting dictionary headers to HTTPHeaders +/// - Processing HTTP responses +/// - Converting query parameters to URL query strings struct RZPRUTL { + /// Converts a dictionary of string headers to HTTPHeaders. + /// + /// - Parameter headers: An optional dictionary of string key-value pairs representing HTTP headers. + /// - Returns: An HTTPHeaders object containing the converted headers. + /// + /// ```swift + /// let headers = ["Content-Type": "application/json"] + /// let httpHeaders = RZPRUTL.convertToHTTPHeaders(headers) + /// ``` static func convertToHTTPHeaders(_ headers: [String: String]?) -> HTTPHeaders { var httpHeaders = HTTPHeaders() headers?.forEach { key, value in @@ -12,20 +27,36 @@ struct RZPRUTL { return httpHeaders } - // Assuming HTTPClientResponse is a custom type in your context + /// Processes an HTTP response and converts the body to a dictionary. + /// + /// - Parameter response: The HTTPClientResponse to process. + /// - Returns: A dictionary containing the parsed JSON response. + /// - Throws: `RZPError.badRequestError` if the response cannot be parsed as JSON. + /// + /// ```swift + /// let response = // ... HTTP response ... + /// let json = try await RZPRUTL.processResponse(response) + /// ``` static func processResponse(_ response: HTTPClientResponse) async throws -> [String: Any] { - // Collecting body data up to a specified limit, for example, 1 MB (1_048_576 bytes) - let body = try await response.body.collect(upTo: 1_048_576) - // Assuming `body` is already of type `Data` based on your usage - let data = body // Direct use without `.data` property + let body = try await response.body.collect(upTo: .max) + let data = body - // TODO: Error handling and validation guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { throw RZPError.badRequestError(message: "INVALID_RESPONSE", internalErrorCode: nil, field: nil, description: nil, code: nil) } return json } + /// Converts a dictionary of query parameters to a URL query string. + /// + /// - Parameter queryParams: An optional dictionary of string key-value pairs representing query parameters. + /// - Returns: A URL-encoded query string starting with "?", or an empty string if no parameters are provided. + /// + /// ```swift + /// let params = ["page": "1", "limit": "10"] + /// let queryString = RZPRUTL.convertToQueryString(params) + /// // Returns "?page=1&limit=10" + /// ``` static func convertToQueryString(_ queryParams: [String: String]?) -> String { guard let queryParams = queryParams, !queryParams.isEmpty else { return "" } let queryItems = queryParams.map { "\($0.key)=\($0.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")" } diff --git a/Tests/RazorpayKitTests/RazorpayKitTests.swift b/Tests/RazorpayKitTests/RazorpayKitTests.swift index e663902..357c186 100644 --- a/Tests/RazorpayKitTests/RazorpayKitTests.swift +++ b/Tests/RazorpayKitTests/RazorpayKitTests.swift @@ -1,49 +1,54 @@ -import XCTest +import Testing @testable import RazorpayKit import NIO import AsyncHTTPClient +import NIOHTTP1 -// TODO: Create mock server with all the tests -final class RazorpayKitTests: XCTestCase { - var razorpayClient: RazorpayClient! - var httpClient: HTTPClient! + +struct RazorpayKitTests { + var razorpayClient: RazorpayClient - override func setUp() { - super.setUp() - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) - razorpayClient = RazorpayClient(httpClient: httpClient, key: "RAZORPAY_KEY", secret: "RAZORPAY_SECRET") + init() async throws { + razorpayClient = RazorpayClient(httpClient: HTTPClient.shared, key: "RAZORPAY_KEY", secret: "RAZORPAY_SECRET") } - func testThatOrdersAreFetchedWithPayments() async throws { + // Tests fetching orders with associated payments + // Verifies: + // - Response contains valid items array + // - Each item has a non-zero amount + // - Total count matches items count + @Test("Orders API") + func ordersAreFetchedWithPayments() async throws { let response = try await razorpayClient.order.all(queryParams: ["expand[]":"payments"]) - print("Response: \(response)") - XCTAssertNotNil(response) - // Assuming `response` is a dictionary with a key "items" that contains an array of order dictionaries + #expect(response != nil) + guard let items = response["items"] as? [[String: Any]] else { - XCTFail("Items not found or invalid format") + Issue.record("Items not found or invalid format") return } - // Iterate through each item and extract the amount for item in items { guard let amount = item["amount"] as? Int else { - XCTFail("Amount not found or invalid format in item: \(item)") + Issue.record("Amount not found or invalid format in item: \(item)") continue } - // Perform your test on the amount here - for example, checking it's not zero - XCTAssertNotEqual(amount, 0, "Amount should not be zero") + #expect(amount != 0, "Amount should not be zero") } - // If needed, test the count of items - let expectedCount = response["count"] as? Int - XCTAssertEqual(items.count, expectedCount, "Fetched items count does not match expected count") + if let expectedCount = response["count"] as? Int { + #expect(items.count == expectedCount, "Fetched items count does not match expected count") + } } - - func testCreateOrder() async throws { + + // Tests order creation with standard parameters + // Verifies: + // - Order creation succeeds + // - Response contains valid order ID + @Test func createOrder() async throws { let orderData: [String: Any] = [ - "amount": 1000000, // amount in the smallest currency unit, for example, 1000000 paise = 10000 INR - "currency": "INR", + "amount": 1000000, + "currency": "INR", "receipt": "Receipt no. 1", "notes": [ "notes_key_1": "Tea, Earl Grey, Hot", @@ -51,25 +56,26 @@ final class RazorpayKitTests: XCTestCase { ] ] let response = try await razorpayClient.order.create(data: orderData) - XCTAssertNotNil(response["id"], "Order should have an ID") + #expect(response["id"] != nil, "Order should have an ID") } - - func testFetchOrder() async throws { + + @Test func fetchOrder() async throws { let orderId = "order_MkGOjNTXK79HzQ" let response = try await razorpayClient.order.fetch(orderID: orderId) - XCTAssertEqual(response["id"] as? String, orderId, "Fetched order ID should match the requested order ID") + #expect(response["id"] as? String == orderId, "Fetched order ID should match the requested order ID") } - - func testFetchPayment() async throws { + + @Test("Payments API") + func fetchPayment() async throws { let paymentId = "pay_29QQoUBi66xm2f" let response = try await razorpayClient.payment.fetch(paymentID: paymentId) - XCTAssertEqual(response["id"] as? String, paymentId, "Fetched payment ID should match the requested payment ID") + #expect(response["id"] as? String == paymentId, "Fetched payment ID should match the requested payment ID") } - - func testRefundPayment() async throws { + + @Test func refundPayment() async throws { let paymentId = "pay_29QQoUBi66xm2f" - let refundData: [String: Any] = ["amount": 100] // Refund amount in the smallest currency unit. + let refundData: [String: Any] = ["amount": 100] let response = try await razorpayClient.payment.refund(paymentID: paymentId, amount: 100, data: refundData) - XCTAssertNotNil(response["id"], "Refund should have an ID") + #expect(response["id"] != nil, "Refund should have an ID") } }