diff --git a/README.md b/README.md index d67f92a..de8cd78 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ZarinPal In App Billing - Purchase SDK | MPG ============================================ ZarinPal Purchase SDK Provides payment methods on your Android Application. -[پارسی](https://www.zarinpal.com/docs/sdkDocs/android/installation.html) +[پارسی](https://www.zarinpal.com/docs/sdk/android/installation.html) Introduction @@ -40,7 +40,7 @@ Add this to your root settings.gradle at the end of repositories. Add the dependency: ```gradle dependencies { - implementation("com.github.alirezabashi98:zarinpal-sdk:1.0.0") + implementation("com.github.ZarinPal:Android-SDK-Kotlin:1.0.1") } ``` @@ -83,16 +83,17 @@ val request = CreatePaymentRequest( description = "test" ) -try { - CoroutineScope(Dispatchers.IO).launch { - response = +CoroutineScope(Dispatchers.IO).launch { + try{ + val response = zarinPal.createPayment(request, redirectUrl = { paymentGatewayUri, status -> if (status == 100) - print("paymentGatewayUri : $paymentGatewayUri") + Log.v("ZP_Log",paymentGatewayUri) }) + Log.v("ZP_Log","$response") + } catch (ex: Exception) { + ex.printStackTrace() } -} catch (e: Exception) { - print(e.message) } ``` @@ -107,13 +108,14 @@ val request = PaymentVerifyRequest( amount = amount, authority = "your authority" ) -try { - CoroutineScope(Dispatchers.IO).launch { - response = zarinPal.paymentVerify(request) - print(response) + +CoroutineScope(Dispatchers.IO).launch { + try{ + val response = zarinPal.paymentVerify(request) + Log.v("ZP_Log","$response") + } catch (ex: Exception) { + ex.printStackTrace() } -} catch (e: Exception) { - print(e.message) } ``` @@ -125,13 +127,14 @@ import com.example.zarinpal.data.remote.dto.inquiry.PaymentInquiryDataResponse import com.example.zarinpal.data.remote.dto.inquiry.PaymentInquiryRequest val request = PaymentInquiryRequest(authority = "authority") -try { - CoroutineScope(Dispatchers.IO).launch { - response = zarinPal.paymentInquiry(request) - print(response) + +CoroutineScope(Dispatchers.IO).launch { + try{ + val response = zarinPal.paymentInquiry(request) + Log.v("ZP_Log","$response") + } catch (ex: Exception) { + ex.printStackTrace() } -} catch (e: Exception) { - print(e.message) } ``` @@ -146,19 +149,19 @@ import com.example.zarinpal.data.remote.dto.unVerified.PaymentUnVerifiedRequest import com.example.zarinpal.data.remote.dto.Config val zarinPal = ZarinPal( - Config( - merchantId = "your-merchant-id", - ) + Config( + merchantId = "your-merchant-id", ) +) - try { - CoroutineScope(Dispatchers.IO).launch { - response = zarinPal.paymentUnVerified() - print(response) +CoroutineScope(Dispatchers.IO).launch { + try{ + val response = zarinPal.paymentUnVerified() + Log.v("ZP_Log","$response") + } catch (ex: Exception) { + ex.printStackTrace() } - } catch (ex: Exception) { - print(ex.message) - } +} ``` **Transaction Reversal** @@ -173,22 +176,23 @@ import com.example.zarinpal.data.remote.dto.reverse.PaymentReverseDataResponse import com.example.zarinpal.data.remote.dto.reverse.PaymentReverseRequest val zarinPal = ZarinPal( - Config( - merchantId = "your-merchant-id", - ) + Config( + merchantId = "your-merchant-id", ) +) + - try { - CoroutineScope(Dispatchers.IO).launch { +CoroutineScope(Dispatchers.IO).launch { + try{ val request = PaymentReverseRequest( authority = "authority" ) val response = zarinPal.paymentReverse(request) - print(response) - } + Log.v("ZP_Log","$response") } catch (ex: Exception) { - print(ex.message) + ex.printStackTrace() } +} ``` **Refund** @@ -198,11 +202,8 @@ val zarinPal = ZarinPal( ```kotlin import com.example.zarinpal.ZarinPal import com.example.zarinpal.data.remote.dto.Config -import com.example.zarinpal.data.remote.dto.refund.PaymentRefundRequest -import com.example.zarinpal.data.remote.dto.refund.PaymentRefundResponse -import com.example.zarinpal.data.remote.dto.verification.PaymentVerificationDataResponse -import com.example.zarinpal.data.remote.enum.MethodEnum -import com.example.zarinpal.data.remote.enum.ReasonEnum +import com.example.zarinpal.data.remote.dto.reverse.PaymentReverseDataResponse +import com.example.zarinpal.data.remote.dto.reverse.PaymentReverseRequest val zarinPal = ZarinPal( Config( @@ -210,20 +211,17 @@ val zarinPal = ZarinPal( ) ) -try { - CoroutineScope(Dispatchers.IO).launch { - val request = PaymentRefundRequest( - amount = 20000, - description = "des", - sessionId = "id", - method = MethodEnum.PAYA, - reason = ReasonEnum.OTHER, + +CoroutineScope(Dispatchers.IO).launch { + try{ + val request = PaymentReverseRequest( + authority = "authority" ) - val response = zarinPal.paymentRefund(request) - print(response) + val response = zarinPal.paymentReverse(request) + Log.v("ZP_Log","$response") + } catch (ex: Exception) { + ex.printStackTrace() } -} catch (ex: Exception) { - print(ex.message) } ``` @@ -240,23 +238,23 @@ import com.example.zarinpal.data.remote.dto.transaction.TransactionRequest import com.example.zarinpal.data.remote.enum.FilterEnum val zarinPal = ZarinPal( - Config( - token = "your-access-token", - ) + Config( + token = "your-access-token", ) +) - try{ - CoroutineScope(Dispatchers.IO).launch { - val request = TransactionRequest( +CoroutineScope(Dispatchers.IO).launch { + try{ + val request = TransactionRequest( terminalId = textFieldTerminalId.text, filter = FilterEnum.ALL, limit = 25, offset = 0 ) - val response = zarinPal.getTransactions(request) - print(response) + val response = zarinPal.getTransactions(request) + Log.v("ZP_Log","$response") + } catch (ex: Exception) { + ex.printStackTrace() } - } catch (e: Exception) { - print(e.message) - } -``` \ No newline at end of file +} +``` diff --git a/build.gradle.kts b/build.gradle.kts index f7ee546..7d7f387 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,26 +7,19 @@ plugins { } val ktorVersion = "1.6.3" -val libraryVersion = "1.0.0" - +val libraryVersion = "1.0.1" android { namespace = "com.example.zarinpal" compileSdk = 34 - publishing { - singleVariant("release") { - withSourcesJar() - } - } - defaultConfig { - aarMetadata { - minCompileSdk = 24 - } minSdk = 24 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") + aarMetadata { + minCompileSdk = 24 + } } buildTypes { @@ -47,6 +40,12 @@ android { kotlinOptions { jvmTarget = "1.8" } + + publishing { + singleVariant("release") { + withSourcesJar() + } + } } tasks.register("javadocJar") { @@ -64,20 +63,24 @@ publishing { afterEvaluate { from(components["release"]) } + artifact(tasks["javadocJar"]) { classifier = "javadoc" } pom { dependencies { - implementation("io.ktor:ktor-client-core:$ktorVersion") - implementation("io.ktor:ktor-client-android:$ktorVersion") - implementation("io.ktor:ktor-client-serialization:$ktorVersion") - implementation("io.ktor:ktor-client-logging:$ktorVersion") - implementation("ch.qos.logback:logback-classic:1.2.3") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0") + listOf( + "io.ktor:ktor-client-core:$ktorVersion", + "io.ktor:ktor-client-android:$ktorVersion", + "io.ktor:ktor-client-serialization:$ktorVersion", + "io.ktor:ktor-client-logging:$ktorVersion", + "ch.qos.logback:logback-classic:1.2.3", + "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0" + ).forEach { + implementation(it) + } } - } } } @@ -86,23 +89,29 @@ publishing { maven { url = uri("https://maven.pkg.github.com/alirezabashi98/zarinpal-sdk") credentials { - credentials.username = System.getenv("GITHUB_USER") - credentials.password = System.getenv("GITHUB_TOKEN") + username = System.getenv("GITHUB_USER") + password = System.getenv("GITHUB_TOKEN") } } } } + dependencies { - implementation("io.ktor:ktor-client-core:$ktorVersion") - implementation("io.ktor:ktor-client-android:$ktorVersion") - implementation("io.ktor:ktor-client-serialization:$ktorVersion") - implementation("io.ktor:ktor-client-logging:$ktorVersion") - implementation("ch.qos.logback:logback-classic:1.2.3") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0") - implementation("androidx.core:core-ktx:1.13.0") + listOf( + "io.ktor:ktor-client-core:$ktorVersion", + "io.ktor:ktor-client-android:$ktorVersion", + "io.ktor:ktor-client-serialization:$ktorVersion", + "io.ktor:ktor-client-logging:$ktorVersion", + "ch.qos.logback:logback-classic:1.2.3", + "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0", + "androidx.core:core-ktx:1.13.0" + ).forEach { + implementation(it) + } } tasks.register("copyLibs") { from(configurations.getByName("implementation")) into("libs") -} \ No newline at end of file +} + diff --git a/src/main/java/com/example/zarinpal/ZarinPal.kt b/src/main/java/com/example/zarinpal/ZarinPal.kt index 36d564b..ebdcf4d 100644 --- a/src/main/java/com/example/zarinpal/ZarinPal.kt +++ b/src/main/java/com/example/zarinpal/ZarinPal.kt @@ -23,37 +23,28 @@ import com.example.zarinpal.utils.Validator * ZarinPal class handles all interactions with the ZarinPal API. * Provides methods for creating payments, verifying payments, refunds, reversals, and more. */ -class ZarinPal(config: Config) { +class ZarinPal(private val config: Config) { - // Instance of PaymentService used for API calls private val service = PaymentService.create(config) - // Configuration object containing sandbox mode and other settings - private val config = config - - /** - * Creates a new payment and returns the response. - * @param paymentRequest Request data for creating the payment. - * @param redirectUrl Callback function to handle the payment gateway URL and status. - * @return [CreatePaymentDataResponse] containing details of the created payment. - */ suspend fun createPayment( paymentRequest: CreatePaymentRequest, redirectUrl: (paymentGatewayUri: String, status: Int) -> Unit ): CreatePaymentDataResponse? { - Validator.validateMerchantId(paymentRequest.merchantId ?: config.merchantId) - Validator.validateCallbackUrl(paymentRequest.callbackUrl) - Validator.validateAmount(paymentRequest.amount) - Validator.validateMobile(paymentRequest.mobile) - Validator.validateEmail(paymentRequest.email) - Validator.validateCardPan(paymentRequest.cardPan) + with(Validator) { + validateMerchantId(paymentRequest.merchantId ?: config.merchantId) + validateCallbackUrl(paymentRequest.callbackUrl) + validateAmount(paymentRequest.amount) + validateMobile(paymentRequest.metadata?.mobile) + validateEmail(paymentRequest.metadata?.email) + validateCardPan(paymentRequest.cardPan) + } - val paymentResponse = - service.createPayment(paymentRequest) + val paymentResponse = service.createPayment(paymentRequest) val paymentGatewayUri = HttpRoutes.getRedirectUrl( sandBox = paymentRequest.sandBox ?: config.sandBox, - authority = paymentResponse?.authority ?: "" + authority = paymentResponse?.authority.orEmpty() ) redirectUrl(paymentGatewayUri, paymentResponse?.code ?: 0) @@ -61,89 +52,58 @@ class ZarinPal(config: Config) { return paymentResponse } - /** - * Verifies a payment using the authority code. - * @param paymentVerifyRequest Request data for verifying the payment. - * @return [PaymentVerificationDataResponse] containing verification details. - */ - suspend fun paymentVerify( - paymentVerifyRequest: PaymentVerifyRequest - ): PaymentVerificationDataResponse? { - Validator.validateMerchantId(paymentVerifyRequest.merchantId ?: config.merchantId) - Validator.validateAuthority(paymentVerifyRequest.authority) - Validator.validateAmount(paymentVerifyRequest.amount) + suspend fun paymentVerify(paymentVerifyRequest: PaymentVerifyRequest): PaymentVerificationDataResponse? { + with(Validator) { + validateMerchantId(paymentVerifyRequest.merchantId ?: config.merchantId) + validateAuthority(paymentVerifyRequest.authority) + validateAmount(paymentVerifyRequest.amount) + } return service.paymentVerify(paymentVerifyRequest) } - /** - * Inquires about a payment status. - * @param paymentInquiryRequest Request data for the payment inquiry. - * @return [PaymentInquiryDataResponse] containing inquiry details. - */ - suspend fun paymentInquiry( - paymentInquiryRequest: PaymentInquiryRequest - ): PaymentInquiryDataResponse? { - Validator.validateMerchantId(paymentInquiryRequest.merchantId ?: config.merchantId) - Validator.validateAuthority(paymentInquiryRequest.authority) + suspend fun paymentInquiry(paymentInquiryRequest: PaymentInquiryRequest): PaymentInquiryDataResponse? { + with(Validator) { + validateMerchantId(paymentInquiryRequest.merchantId ?: config.merchantId) + validateAuthority(paymentInquiryRequest.authority) + } return service.paymentInquiry(paymentInquiryRequest) } - - /** - * Retrieves unverified payments. - * @param paymentUnVerifiedRequest Optional request data for retrieving unverified payments. - * @return [PaymentUnVerifiedDataResponse] containing unverified payment details. - */ suspend fun paymentUnVerified( paymentUnVerifiedRequest: PaymentUnVerifiedRequest = PaymentUnVerifiedRequest() ): PaymentUnVerifiedDataResponse? { Validator.validateMerchantId(paymentUnVerifiedRequest.merchantId ?: config.merchantId) - return service.paymentUnVerified(paymentUnVerifiedRequest) } - /** - * Retrieves unverified payments. - * @param paymentUnVerifiedRequest Optional request data for retrieving unverified payments. - * @return [PaymentUnVerifiedDataResponse] containing unverified payment details. - */ - suspend fun paymentReverse( - paymentReverseRequest: PaymentReverseRequest - ): PaymentReverseDataResponse? { - Validator.validateMerchantId(paymentReverseRequest.merchantId ?: config.merchantId) - Validator.validateAuthority(paymentReverseRequest.authority) + suspend fun paymentReverse(paymentReverseRequest: PaymentReverseRequest): PaymentReverseDataResponse? { + with(Validator) { + validateMerchantId(paymentReverseRequest.merchantId ?: config.merchantId) + validateAuthority(paymentReverseRequest.authority) + } return service.paymentReverse(paymentReverseRequest) } - /** - * Retrieves transaction history. - * @param transactionRequest Request data for fetching transactions. - * @return A list of [Session] objects containing transaction details. - */ - suspend fun getTransactions( - transactionRequest: TransactionRequest - ): List? { - Validator.validateTerminalId(transactionRequest.terminalId) - Validator.validateLimit(transactionRequest.limit) - Validator.validateOffset(transactionRequest.offset) + suspend fun getTransactions(transactionRequest: TransactionRequest): List? { + with(Validator) { + validateTerminalId(transactionRequest.terminalId) + validateLimit(transactionRequest.limit) + validateOffset(transactionRequest.offset) + } return service.getTransactions(transactionRequest) } - /** - * Processes a payment refund. - * @param paymentRefundRequest Request data for refunding the payment. - * @return [PaymentRefundResponse] containing refund details. - */ - suspend fun paymentRefund( - paymentRefundRequest: PaymentRefundRequest - ): PaymentRefundResponse? { - Validator.validateSessionId(paymentRefundRequest.sessionId) - Validator.validateAmount(paymentRefundRequest.amount, minAmount = 20_000) + suspend fun paymentRefund(paymentRefundRequest: PaymentRefundRequest): PaymentRefundResponse? { + with(Validator) { + validateSessionId(paymentRefundRequest.sessionId) + validateAmount(paymentRefundRequest.amount, minAmount = 20_000) + } return service.paymentRefund(paymentRefundRequest) } -} \ No newline at end of file +} + diff --git a/src/main/java/com/example/zarinpal/data/remote/HttpRoutes.kt b/src/main/java/com/example/zarinpal/data/remote/HttpRoutes.kt index d6d258b..1b67461 100644 --- a/src/main/java/com/example/zarinpal/data/remote/HttpRoutes.kt +++ b/src/main/java/com/example/zarinpal/data/remote/HttpRoutes.kt @@ -1,19 +1,10 @@ package com.example.zarinpal.data.remote /** - * This object contains constants and functions related to the HTTP routes for interacting with the ZarinPal payment gateway. - * - * @property BASE_URL The base URL for the live ZarinPal payment gateway. - * @property BASE_URL_SANDBOX The base URL for the sandbox (test) environment of the ZarinPal payment gateway. - * @property START_PAY_URL The URL endpoint for starting a payment. - * @property BASE_URL_GRAPH The base URL for the GraphQL API of ZarinPal. - * @property PAYMENT The endpoint for creating a payment request. - * @property PAYMENT_VERIFY The endpoint for verifying a payment. - * @property PAYMENT_INQUIRY The endpoint for inquiring about a payment. - * @property PAYMENT_UN_VERIFIED The endpoint for querying unverified payments. - * @property PAYMENT_REVERSE The endpoint for reversing a payment. + * Utility object for building HTTP routes related to the ZarinPal payment gateway. */ object HttpRoutes { + private const val BASE_URL = "https://payment.zarinpal.com" private const val BASE_URL_SANDBOX = "https://sandbox.zarinpal.com" private const val START_PAY_URL = "/pg/StartPay/" @@ -23,68 +14,27 @@ object HttpRoutes { private const val PAYMENT = "/pg/v4/payment/request.json" private const val PAYMENT_VERIFY = "/pg/v4/payment/verify.json" private const val PAYMENT_INQUIRY = "/pg/v4/payment/inquiry.json" - private const val PAYMENT_UN_VERIFIED = "/pg/v4/payment/unVerified.json" + private const val PAYMENT_UNVERIFIED = "/pg/v4/payment/unVerified.json" private const val PAYMENT_REVERSE = "/pg/v4/payment/reverse.json" - /** - * Generates the URL for creating a payment request. - * - * @param sandBox A boolean indicating whether to use the sandbox (test) environment. - * @return The full URL for the payment request. - */ - fun createPayment(sandBox: Boolean): String { - return (if (sandBox) BASE_URL_SANDBOX else BASE_URL) + PAYMENT - } + private fun baseUrl(sandbox: Boolean): String = + if (sandbox) BASE_URL_SANDBOX else BASE_URL + + fun createPayment(sandbox: Boolean): String = + baseUrl(sandbox) + PAYMENT - /** - * Generates the URL for verifying a payment. - * - * @param sandBox A boolean indicating whether to use the sandbox (test) environment. - * @return The full URL for the payment verification request. - */ - fun paymentVerify(sandBox: Boolean): String { - return (if (sandBox) BASE_URL_SANDBOX else BASE_URL) + PAYMENT_VERIFY - } + fun paymentVerify(sandbox: Boolean): String = + baseUrl(sandbox) + PAYMENT_VERIFY - /** - * Generates the URL for inquiring about a payment. - * - * @param sandBox A boolean indicating whether to use the sandbox (test) environment. - * @return The full URL for the payment inquiry request. - */ - fun paymentInquiry(sandBox: Boolean): String { - return (if (sandBox) BASE_URL_SANDBOX else BASE_URL) + PAYMENT_INQUIRY - } + fun paymentInquiry(sandbox: Boolean): String = + baseUrl(sandbox) + PAYMENT_INQUIRY - /** - * Generates the URL for querying unverified payments. - * - * @param sandBox A boolean indicating whether to use the sandbox (test) environment. - * @return The full URL for the unverified payment query request. - */ - fun paymentUnVerified(sandBox: Boolean): String { - return (if (sandBox) BASE_URL_SANDBOX else BASE_URL) + PAYMENT_UN_VERIFIED - } + fun paymentUnverified(sandbox: Boolean): String = + baseUrl(sandbox) + PAYMENT_UNVERIFIED - /** - * Generates the URL for reversing a payment. - * - * @param sandBox A boolean indicating whether to use the sandbox (test) environment. - * @return The full URL for the payment reversal request. - */ - fun paymentReverse(sandBox: Boolean): String { - return (if (sandBox) BASE_URL_SANDBOX else BASE_URL) + PAYMENT_REVERSE - } + fun paymentReverse(sandbox: Boolean): String = + baseUrl(sandbox) + PAYMENT_REVERSE - /** - * Generates the URL for redirecting a user to the payment page with the provided authority. - * - * @param authority The payment authority returned by the payment gateway. - * @param sandBox A boolean indicating whether to use the sandbox (test) environment. - * @return The full URL for redirecting the user to the payment page. - */ - fun getRedirectUrl(authority: String, sandBox: Boolean): String { - if ((authority ?: "").isEmpty()) return "" - return (if (sandBox) BASE_URL_SANDBOX else BASE_URL) + START_PAY_URL + authority - } -} \ No newline at end of file + fun getRedirectUrl(authority: String, sandbox: Boolean): String = + if (authority.isBlank()) "" else baseUrl(sandbox) + START_PAY_URL + authority +} diff --git a/src/main/java/com/example/zarinpal/data/remote/PaymentService.kt b/src/main/java/com/example/zarinpal/data/remote/PaymentService.kt index 294c658..c84bcd3 100644 --- a/src/main/java/com/example/zarinpal/data/remote/PaymentService.kt +++ b/src/main/java/com/example/zarinpal/data/remote/PaymentService.kt @@ -115,7 +115,7 @@ interface PaymentService { ) } defaultRequest { - header("User-Agent", "ZarinPalSdk/v.1.0.0 (android kotlin)") + header("User-Agent", "ZarinPalSdk/v.1.0.1 (android kotlin)") header("Content-Type", "application/json") contentType(ContentType.Application.Json) } diff --git a/src/main/java/com/example/zarinpal/data/remote/PaymentServiceImpl.kt b/src/main/java/com/example/zarinpal/data/remote/PaymentServiceImpl.kt index 045a035..588a49d 100644 --- a/src/main/java/com/example/zarinpal/data/remote/PaymentServiceImpl.kt +++ b/src/main/java/com/example/zarinpal/data/remote/PaymentServiceImpl.kt @@ -1,270 +1,147 @@ package com.example.zarinpal.data.remote - import com.example.zarinpal.data.remote.dto.Config -import com.example.zarinpal.data.remote.dto.create.CreatePaymentDataResponse -import com.example.zarinpal.data.remote.dto.create.CreatePaymentRequest -import com.example.zarinpal.data.remote.dto.create.CreatePaymentResponse -import com.example.zarinpal.data.remote.dto.inquiry.PaymentInquiryDataResponse -import com.example.zarinpal.data.remote.dto.inquiry.PaymentInquiryRequest -import com.example.zarinpal.data.remote.dto.inquiry.PaymentInquiryResponse -import com.example.zarinpal.data.remote.dto.refund.GraphRefundModel -import com.example.zarinpal.data.remote.dto.refund.PaymentRefundRequest -import com.example.zarinpal.data.remote.dto.refund.PaymentRefundResponse -import com.example.zarinpal.data.remote.dto.refund.PaymentRefundResponseModel -import com.example.zarinpal.data.remote.dto.reverse.PaymentReverseDataResponse -import com.example.zarinpal.data.remote.dto.reverse.PaymentReverseRequest -import com.example.zarinpal.data.remote.dto.reverse.PaymentReverseResponse -import com.example.zarinpal.data.remote.dto.transaction.GraphTransactionModel -import com.example.zarinpal.data.remote.dto.transaction.Session -import com.example.zarinpal.data.remote.dto.transaction.TransactionRequest -import com.example.zarinpal.data.remote.dto.transaction.TransactionResponse -import com.example.zarinpal.data.remote.dto.unVerified.PaymentUnVerifiedDataResponse -import com.example.zarinpal.data.remote.dto.unVerified.PaymentUnVerifiedRequest -import com.example.zarinpal.data.remote.dto.unVerified.PaymentUnVerifiedResponse -import com.example.zarinpal.data.remote.dto.verification.PaymentVerificationDataResponse -import com.example.zarinpal.data.remote.dto.verification.PaymentVerificationResponse -import com.example.zarinpal.data.remote.dto.verification.PaymentVerifyRequest +import com.example.zarinpal.data.remote.dto.create.* +import com.example.zarinpal.data.remote.dto.inquiry.* +import com.example.zarinpal.data.remote.dto.refund.* +import com.example.zarinpal.data.remote.dto.reverse.* +import com.example.zarinpal.data.remote.dto.transaction.* +import com.example.zarinpal.data.remote.dto.unVerified.* +import com.example.zarinpal.data.remote.dto.verification.* import io.ktor.client.HttpClient -import io.ktor.client.features.ClientRequestException -import io.ktor.client.features.RedirectResponseException -import io.ktor.client.features.ServerResponseException -import io.ktor.client.request.header -import io.ktor.client.request.post -import io.ktor.client.request.url +import io.ktor.client.call.body +import io.ktor.client.plugins.ClientRequestException +import io.ktor.client.plugins.RedirectResponseException +import io.ktor.client.plugins.ServerResponseException +import io.ktor.client.request.* import io.ktor.client.statement.readText import org.json.JSONArray import org.json.JSONObject -/** - * This class handles the communication with the Payment API. - * It provides methods for creating, verifying, inquiring, refunding, and reversing payments, - * as well as fetching transaction details and handling unverified payments. - */ class PaymentServiceImpl( private val client: HttpClient, private val config: Config ) : PaymentService { - /** - * Creates a new payment. - * @param paymentRequest The request object containing payment details. - * @return A [CreatePaymentDataResponse] object containing the response data, or null if the request fails. - */ - override suspend fun createPayment(paymentRequest: CreatePaymentRequest): CreatePaymentDataResponse? { - return handleRequestWithErrorHandling { - val route = HttpRoutes.createPayment(paymentRequest.sandBox ?: config.sandBox) - - val response = client.post { - url(route) - body = paymentRequest.copyWithConfig(config) - } - response.data + override suspend fun createPayment(request: CreatePaymentRequest): CreatePaymentDataResponse? = + handleRequest { + client.post { + url(HttpRoutes.createPayment(request.sandBox ?: config.sandBox)) + setBody(request.copyWithConfig(config)) + }.data } - } - - /** - * Verifies a payment after it has been made. - * @param paymentVerifyRequest The request object containing payment verification details. - * @return A [PaymentVerificationDataResponse] object containing the verification result, or null if the request fails. - */ - override suspend fun paymentVerify(paymentVerifyRequest: PaymentVerifyRequest): PaymentVerificationDataResponse? { - return handleRequestWithErrorHandling { - val route = HttpRoutes.paymentVerify(paymentVerifyRequest.sandBox ?: config.sandBox) - val response = client.post { - url(route) - body = paymentVerifyRequest.copyWithConfig(config) - } - response.data + override suspend fun paymentVerify(request: PaymentVerifyRequest): PaymentVerificationDataResponse? = + handleRequest { + client.post { + url(HttpRoutes.paymentVerify(request.sandBox ?: config.sandBox)) + setBody(request.copyWithConfig(config)) + }.data } - } - - /** - * Inquires about the status of a payment. - * @param paymentInquiryRequest The request object containing payment inquiry details. - * @return A [PaymentInquiryDataResponse] object containing the inquiry result, or null if the request fails. - */ - override suspend fun paymentInquiry(paymentInquiryRequest: PaymentInquiryRequest): PaymentInquiryDataResponse? { - return handleRequestWithErrorHandling { - val route = HttpRoutes.paymentInquiry(paymentInquiryRequest.sandBox ?: config.sandBox) - val response = client.post { - url(route) - body = paymentInquiryRequest.copyWithConfig(config) - } - response.data + override suspend fun paymentInquiry(request: PaymentInquiryRequest): PaymentInquiryDataResponse? = + handleRequest { + client.post { + url(HttpRoutes.paymentInquiry(request.sandBox ?: config.sandBox)) + setBody(request.copyWithConfig(config)) + }.data } - } - /** - * Retrieves unverified payments. - * @param paymentUnVerifiedRequest The request object containing unverified payment details. - * @return A [PaymentUnVerifiedDataResponse] object containing unverified payments, or null if the request fails. - */ - override suspend fun paymentUnVerified(paymentUnVerifiedRequest: PaymentUnVerifiedRequest): PaymentUnVerifiedDataResponse? { - return handleRequestWithErrorHandling { - val route = - HttpRoutes.paymentUnVerified(paymentUnVerifiedRequest.sandBox ?: config.sandBox) - val response = client.post { - url(route) - body = paymentUnVerifiedRequest.copyWithConfig(config) - } - response.data + override suspend fun paymentUnVerified(request: PaymentUnVerifiedRequest): PaymentUnVerifiedDataResponse? = + handleRequest { + client.post { + url(HttpRoutes.paymentUnVerified(request.sandBox ?: config.sandBox)) + setBody(request.copyWithConfig(config)) + }.data } - } - - /** - * Reverses a payment. - * @param paymentReverseRequest The request object containing payment reversal details. - * @return A [PaymentReverseDataResponse] object containing the reversal result, or null if the request fails. - */ - override suspend fun paymentReverse(paymentReverseRequest: PaymentReverseRequest): PaymentReverseDataResponse? { - return handleRequestWithErrorHandling { - val route = - HttpRoutes.paymentReverse(paymentReverseRequest.sandBox ?: config.sandBox) - val response = client.post { - url(route) - body = paymentReverseRequest.copyWithConfig(config) - } - response.data + override suspend fun paymentReverse(request: PaymentReverseRequest): PaymentReverseDataResponse? = + handleRequest { + client.post { + url(HttpRoutes.paymentReverse(request.sandBox ?: config.sandBox)) + setBody(request.copyWithConfig(config)) + }.data } - } - /** - * Retrieves a list of transactions. - * @param transactionRequest The request object containing transaction filter details. - * @return A list of [Session] objects representing the transactions, or null if the request fails. - */ - override suspend fun getTransactions(transactionRequest: TransactionRequest): List? { - return handleRequestWithErrorHandling { + override suspend fun getTransactions(request: TransactionRequest): List? = + handleRequest { val query = """ - query Sessions(${'$'}terminal_id: ID!, ${'$'}filter: FilterEnum, ${'$'}id: ID, ${'$'}reference_id: String, ${'$'}rrn: String, ${'$'}card_pan: String, ${'$'}email: String, ${'$'}mobile: CellNumber, ${'$'}description: String, ${'$'}limit: Int, ${'$'}offset: Int) { Session(terminal_id: ${'$'}terminal_id, filter: ${'$'}filter, id: ${'$'}id, reference_id: ${'$'}reference_id, rrn: ${'$'}rrn, card_pan: ${'$'}card_pan, email: ${'$'}email, mobile: ${'$'}mobile, description: ${'$'}description, limit: ${'$'}limit, offset: ${'$'}offset) { id, status, amount, description, created_at } } - """.trimIndent() + query Sessions(\$terminal_id: ID!, \$filter: FilterEnum, \$id: ID, \$reference_id: String, \$rrn: String, \$card_pan: String, \$email: String, \$mobile: CellNumber, \$description: String, \$limit: Int, \$offset: Int) { + Session(terminal_id: \$terminal_id, filter: \$filter, id: \$id, reference_id: \$reference_id, rrn: \$rrn, card_pan: \$card_pan, email: \$email, mobile: \$mobile, description: \$description, limit: \$limit, offset: \$offset) { + id + status + amount + description + created_at + } + } + """.trimIndent() - val token = transactionRequest.token ?: config.token - val response = client.post { + client.post { url(HttpRoutes.BASE_URL_GRAPH) - header("Authorization", "Bearer $token") - body = GraphTransactionModel(query = query, variables = transactionRequest) - } - response.data?.session + header("Authorization", "Bearer ${request.token ?: config.token}") + setBody(GraphTransactionModel(query, request)) + }.data?.session } - } - /** - * Refunds a payment. - * @param paymentRefundRequest The request object containing refund details. - * @return A [PaymentRefundResponse] object containing the refund result, or null if the request fails. - */ - override suspend fun paymentRefund(paymentRefundRequest: PaymentRefundRequest): PaymentRefundResponse? { - return handleRequestWithErrorHandling { + override suspend fun paymentRefund(request: PaymentRefundRequest): PaymentRefundResponse? = + handleRequest { val query = """ - mutation AddRefund(${'$'}session_id: ID!,${'$'}amount: BigInteger!,${'$'}description: String,${'$'}method: InstantPayoutActionTypeEnum,${'$'}reason: RefundReasonEnum) {resource: AddRefund(session_id: ${'$'}session_id,amount: ${'$'}amount,description: ${'$'}description,method: ${'$'}method,reason: ${'$'}reason) {terminal_id,id,amount,timeline {refund_amount,refund_time,refund_status}}} - """.trimIndent() + mutation AddRefund(\$session_id: ID!, \$amount: BigInteger!, \$description: String, \$method: InstantPayoutActionTypeEnum, \$reason: RefundReasonEnum) { + resource: AddRefund(session_id: \$session_id, amount: \$amount, description: \$description, method: \$method, reason: \$reason) { + terminal_id + id + amount + timeline { + refund_amount + refund_time + refund_status + } + } + } + """.trimIndent() - val token = paymentRefundRequest.token ?: config.token - val response = client.post { + client.post { url(HttpRoutes.BASE_URL_GRAPH) - header("Authorization", "Bearer $token") - body = GraphRefundModel(query = query, variables = paymentRefundRequest) - } - response.data.resource + header("Authorization", "Bearer ${request.token ?: config.token}") + setBody(GraphRefundModel(query, request)) + }.data?.resource } - } - - /** - * Handles errors and retries requests if necessary. - * @param request A suspending function that performs the request. - * @return The result of the request, or throws an exception if an error occurs. - */ - private suspend fun handleRequestWithErrorHandling(request: suspend () -> T): T { - return try { + private suspend fun handleRequest(request: suspend () -> T): T = + try { request() } catch (e: RedirectResponseException) { - // 3xx - responses - val errorResponse = e.response.readText() - throw Exception(processErrorResponse(errorResponse) ?: e.response.status.description) + throw Exception(extractError(e.response.readText()) ?: e.response.status.description) } catch (e: ClientRequestException) { - // 4xx - responses - val errorResponse = e.response.readText() - throw Exception(processErrorResponse(errorResponse) ?: e.response.status.description) + throw Exception(extractError(e.response.readText()) ?: e.response.status.description) } catch (e: ServerResponseException) { - // 5xx - responses - val errorResponse = e.response.readText() - throw Exception(processErrorResponse(errorResponse) ?: e.response.status.description) + throw Exception(extractError(e.response.readText()) ?: e.response.status.description) } catch (e: Exception) { throw e } - } - - /** - * Processes error responses from the API. - * @param jsonString The JSON string containing the error response. - * @return A readable error message, or null if the error cannot be processed. - */ - private fun processErrorResponse(jsonString: String?): String? { - // Implementation of error response processing - if (jsonString == null) return null - try { - val jsonObject = JSONObject(jsonString) - - // If 'errors' is an object - if (jsonObject.has("errors") && !jsonObject.isNull("errors") && jsonObject.get("errors") is JSONObject) { - val errorObject = jsonObject.getJSONObject("errors") - val message :String?= errorObject.optString("message",null) - val faMessage :String?= errorObject.optString("fa_message",null) - - if(!(faMessage ?: message).isNullOrBlank() && !(faMessage ?: message).isNullOrEmpty()) return faMessage ?: message - - val messageJsonObject = jsonObject.getString("message") - val faMessageJsonObject :String?= jsonObject.optString("fa_message",null) - - return faMessageJsonObject ?: messageJsonObject - } + private fun extractError(json: String?): String? { + if (json.isNullOrBlank()) return null - // If 'errors' is an array - else if (jsonObject.has("errors") && jsonObject.get("errors") is JSONArray) { - val errorsArray = jsonObject.getJSONArray("errors") - - // Iterate through each error in the array - for (i in 0 until errorsArray.length()) { - val errorObject = errorsArray.getJSONObject(i) - - // Check if there's a readable_code error - if (errorObject.has("readable_code")) { - val message = errorObject.getString("message") - val faMessage :String?= errorObject.optString("fa_message", null) - - return faMessage ?: message - } - // Check if there's a validation error - else if (errorObject.has("validation")) { - val validationArray = errorObject.getJSONArray("validation") - for (j in 0 until validationArray.length()) { - val validationObject = validationArray.getJSONObject(j) - val message = validationObject.getString("message") - - // Use the validation message - val faMessage :String?= validationObject.optString("fa_message", null) - return faMessage ?: message - } - } - // Check if there's a other error - else { - val message = errorObject.getString("message") - val faMessage :String?= errorObject.optString("fa_message", null) - - return faMessage ?: message - } - } + return try { + val obj = JSONObject(json) + when (val errors = obj.opt("errors")) { + is JSONObject -> errors.optString("fa_message") ?: errors.optString("message") + is JSONArray -> (0 until errors.length()) + .mapNotNull { errors.optJSONObject(it) } + .flatMap { listOfNotNull( + it.optString("fa_message"), + it.optString("message"), + it.optJSONArray("validation")?.optJSONObject(0)?.optString("fa_message"), + it.optJSONArray("validation")?.optJSONObject(0)?.optString("message") + ) } + .firstOrNull() + else -> obj.optString("fa_message") ?: obj.optString("message") } - } catch (ex: Exception) { - return null + } catch (_: Exception) { + null } - return null } -} \ No newline at end of file +} + diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/Config.kt b/src/main/java/com/example/zarinpal/data/remote/dto/Config.kt index 06b47c2..9031bb1 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/Config.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/Config.kt @@ -1,15 +1,14 @@ package com.example.zarinpal.data.remote.dto /** - * Represents the configuration settings required for interacting with the payment service. + * Defines configuration parameters for accessing the payment service. * - * @property merchantId A unique identifier for the merchant, used to authenticate requests. - * @property token An optional token for additional authentication, if required. - * @property sandBox Specifies whether the application should operate in sandbox mode (test mode). - * Defaults to `false` for production environment. + * @property merchantId The unique identifier used to authenticate the merchant. + * @property token Optional token used for enhanced authentication, if applicable. + * @property sandBox Flag indicating whether sandbox (test) mode is enabled. Defaults to `false`. */ data class Config( val merchantId: String, val token: String? = null, - val sandBox :Boolean=false -) \ No newline at end of file + val sandBox: Boolean = false +) diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/create/CreatePaymentRequest.kt b/src/main/java/com/example/zarinpal/data/remote/dto/create/CreatePaymentRequest.kt index 3df1af9..5594997 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/create/CreatePaymentRequest.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/create/CreatePaymentRequest.kt @@ -7,48 +7,77 @@ import kotlinx.serialization.Serializable /** * Represents the request data required to create a payment. - * - * @property merchantId The unique identifier for the merchant (nullable). - * @property sandBox Indicates if the payment should be processed in sandbox mode (test mode). - * @property description Description of the payment (e.g., "Order #1234"). - * @property callbackUrl The URL to redirect the user to after payment. - * @property amount The total amount of the payment in the smallest currency unit (e.g., IRR). */ @Keep @Serializable data class CreatePaymentRequest( @SerialName("merchant_id") val merchantId: String? = null, + val sandBox: Boolean? = null, val description: String, + @SerialName("callback_url") val callbackUrl: String, + val amount: Int, - val mobile: String?=null, - val email: String?=null, + val metadata: Metadata? = null, + @SerialName("referrer_id") - val referrerId: String?=null, - val currency: String?=null, - val cardPan: String?=null, - val wages: List?=null, + val referrerId: String? = null, + + val currency: String? = null, + val cardPan: String? = null, + val wages: List? = null, ) { + /** - * Creates a copy of the request with the merchantId and sandBox values - * replaced by the ones from the provided [Config] if they are null. + * Returns a copy of this request with merchantId and sandBox populated from [config] if they are null. */ fun copyWithConfig(config: Config): CreatePaymentRequest { - return this.copy( - merchantId = this.merchantId ?: config.merchantId, - sandBox = this.sandBox ?: config.sandBox + return copy( + merchantId = merchantId ?: config.merchantId, + sandBox = sandBox ?: config.sandBox ) } + + constructor( + merchantId: String, + sandBox: Boolean? = null, + description: String, + callbackUrl: String, + amount: Int, + mobile: String? = null, + email: String? = null, + referrerId: String? = null, + currency: String? = null, + cardPan: String? = null, + wages: List? = null + ) : this( + merchantId = merchantId, + sandBox = sandBox, + description = description, + callbackUrl = callbackUrl, + amount = amount, + metadata = if (!mobile.isNullOrBlank() || !email.isNullOrBlank()) Metadata(mobile, email) else null, + referrerId = referrerId, + currency = currency, + cardPan = cardPan, + wages = wages + ) } +@Keep +@Serializable +data class Metadata( + val mobile: String? = null, + val email: String? = null +) @Keep @Serializable data class WagesPaymentRequest( val iban: String, val amount: Int, - val description: String, + val description: String ) diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/create/CreatePaymentResponse.kt b/src/main/java/com/example/zarinpal/data/remote/dto/create/CreatePaymentResponse.kt index 92e7dde..eccaa95 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/create/CreatePaymentResponse.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/create/CreatePaymentResponse.kt @@ -10,32 +10,34 @@ import kotlinx.serialization.json.JsonElement /** * Represents the complete response returned by the payment creation API. * - * @property data Contains the main response data ([CreatePaymentDataResponse]) with payment details. - * @property errors A JSON element containing error details, if any, in the API response. + * @property data Main response data with payment details. + * @property errors JSON element containing error details, if any. */ @Keep @Serializable data class CreatePaymentResponse( val data: CreatePaymentDataResponse, val errors: JsonElement? - ) +) /** * Represents the response data returned after creating a payment. * - * @property authority A unique identifier for the payment session, used for tracking. - * @property message A message indicating the status of the payment creation process. - * @property feeType Specifies the entity responsible for payment fees (e.g., "Merchant" or "Payer"). - * @property fee The fee amount associated with the payment. - * @property code Status code of the payment creation (e.g., 100 for success). + * @property authority Unique ID for the payment session. + * @property message Status message of the payment creation. + * @property feeType Responsible party for the payment fee. + * @property fee Fee amount associated with the payment. + * @property code Status code (e.g., 100 for success). */ @Keep @Serializable data class CreatePaymentDataResponse( val authority: String, val message: String, + @SerialName("fee_type") val feeType: String?, + val fee: Int, - val code: Int, -) \ No newline at end of file + val code: Int +) diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/inquiry/PaymentInquiryRequest.kt b/src/main/java/com/example/zarinpal/data/remote/dto/inquiry/PaymentInquiryRequest.kt index e80e809..378e79a 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/inquiry/PaymentInquiryRequest.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/inquiry/PaymentInquiryRequest.kt @@ -1,4 +1,3 @@ - package com.example.zarinpal.data.remote.dto.inquiry import com.example.zarinpal.data.remote.dto.Config @@ -6,30 +5,26 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** - * Represents the request data required to inquire about a payment. + * Request data for inquiring about a payment. * - * @property merchantId The unique identifier for the merchant (nullable). - * @property sandBox Indicates if the payment inquiry should be processed in sandbox mode (test mode). - * @property authority The authorization code for the payment inquiry (required). + * @property merchantId Optional merchant identifier. + * @property sandBox Indicates whether to use sandbox (test) mode. + * @property authority Required payment authority code. */ @Serializable data class PaymentInquiryRequest( @SerialName("merchant_id") - val merchantId: String?=null, - val sandBox :Boolean?=null, - val authority :String, -){ + val merchantId: String? = null, + val sandBox: Boolean? = null, + val authority: String +) { + /** - * Creates a copy of the request with the merchantId and sandBox values - * replaced by the ones from the provided [Config] if they are null. - * - * @param config The [Config] object that provides default values for merchantId and sandBox. - * @return A new instance of [PaymentInquiryRequest] with updated values. + * Returns a copy of this request using fallback values from [config] + * if [merchantId] or [sandBox] are null. */ - fun copyWithConfig(config: Config): PaymentInquiryRequest { - return this.copy( - merchantId = this.merchantId ?: config.merchantId, - sandBox = this.sandBox ?: config.sandBox - ) - } + fun copyWithConfig(config: Config): PaymentInquiryRequest = copy( + merchantId = merchantId ?: config.merchantId, + sandBox = sandBox ?: config.sandBox + ) } diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/inquiry/PaymentInquiryResponse.kt b/src/main/java/com/example/zarinpal/data/remote/dto/inquiry/PaymentInquiryResponse.kt index 7bf221f..2b063fb 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/inquiry/PaymentInquiryResponse.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/inquiry/PaymentInquiryResponse.kt @@ -6,10 +6,10 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement /** - * Represents the response data returned after inquiring about a payment. + * Represents the response from a payment inquiry. * - * @property data Contains the details of the payment inquiry response. - * @property errors If present, contains any errors returned by the payment gateway. + * @property data Details of the payment inquiry. + * @property errors Error information returned by the payment gateway, if any. */ @Keep @Serializable @@ -19,16 +19,16 @@ data class PaymentInquiryResponse( ) /** - * Represents the data returned from the payment inquiry. + * Contains the details returned from a payment inquiry. * - * @property status The status of the payment inquiry (e.g., "success", "failed"). - * @property code A numeric code indicating the result of the inquiry. - * @property message A descriptive message explaining the result or error. + * @property status Status of the payment (e.g., "success", "failed"). + * @property code Numeric result code of the inquiry. + * @property message Human-readable message about the result or error. */ @Keep @Serializable data class PaymentInquiryDataResponse( - val status:String, + val status: String, val code: Int, - val message: String, + val message: String ) diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/refund/GraphRefundModel.kt b/src/main/java/com/example/zarinpal/data/remote/dto/refund/GraphRefundModel.kt index b93c233..f53f5ff 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/refund/GraphRefundModel.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/refund/GraphRefundModel.kt @@ -1,17 +1,16 @@ package com.example.zarinpal.data.remote.dto.refund -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** - * Represents the GraphQL request for initiating a payment refund. + * Represents a GraphQL request for initiating a payment refund. * - * @property query The GraphQL query string to be executed for the refund operation. - * @property variables Contains the variables required for the refund operation, + * @property query GraphQL query string to execute the refund. + * @property variables Parameters required to process the refund, * including session ID, description, method, reason, amount, and token. */ @Serializable data class GraphRefundModel( - val query :String, + val query: String, val variables: PaymentRefundRequest -) \ No newline at end of file +) diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/refund/PaymentRefundRequest.kt b/src/main/java/com/example/zarinpal/data/remote/dto/refund/PaymentRefundRequest.kt index 4799aba..80e3c3c 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/refund/PaymentRefundRequest.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/refund/PaymentRefundRequest.kt @@ -7,14 +7,14 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** - * Represents the request data required to initiate a payment refund. + * Request data model for initiating a payment refund. * - * @property sessionId The unique identifier for the payment session being refunded. - * @property description A description of the refund (e.g., "Refund for Order #1234"). - * @property method The method used for the refund (e.g., "credit", "bank transfer"). - * @property reason The reason for the refund (e.g., "Product returned", "Payment error"). - * @property amount The amount to be refunded in the smallest currency unit (e.g., IRR). - * @property token A token used for additional security (nullable). + * @property sessionId Unique identifier for the payment session being refunded. + * @property description Description of the refund (e.g., "Refund for Order #1234"). + * @property method Method used for the refund (e.g., "credit", "bank transfer"). + * @property reason Reason for the refund (e.g., "Product returned", "Payment error"). + * @property amount Amount to be refunded in the smallest currency unit (e.g., IRR). + * @property token Optional security token. */ @Keep @Serializable @@ -25,6 +25,5 @@ data class PaymentRefundRequest( val method: MethodEnum, val reason: ReasonEnum, val amount: Int, - val token: String? = null, + val token: String? = null ) - diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/refund/PaymentRefundResponse.kt b/src/main/java/com/example/zarinpal/data/remote/dto/refund/PaymentRefundResponse.kt index 77b634b..25e4aeb 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/refund/PaymentRefundResponse.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/refund/PaymentRefundResponse.kt @@ -4,9 +4,9 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** - * Represents the response data returned after initiating a payment refund. + * Top-level response returned after initiating a payment refund. * - * @property data Contains the details of the refund response. + * @property data Details of the refund operation. */ @Serializable data class PaymentRefundResponseModel( @@ -14,9 +14,9 @@ data class PaymentRefundResponseModel( ) /** - * Contains the data returned from the payment refund process. + * Container for refund resource information. * - * @property resource The detailed response of the refund transaction. + * @property resource Detailed refund transaction data. */ @Serializable data class PaymentRefundDataModel( @@ -24,12 +24,12 @@ data class PaymentRefundDataModel( ) /** - * Represents the details of the refund transaction. + * Refund transaction details. * - * @property terminalId The terminal ID used for the refund. - * @property id The unique identifier of the refund transaction. - * @property amount The total amount refunded in the smallest currency unit (e.g., IRR). - * @property timeline Contains details of the refund process, including the amount, time, and status. + * @property terminalId Terminal ID used for the refund. + * @property id Unique identifier of the refund transaction. + * @property amount Refunded amount in the smallest currency unit (e.g., IRR). + * @property timeline Timeline of the refund process. */ @Serializable data class PaymentRefundResponse( @@ -40,15 +40,15 @@ data class PaymentRefundResponse( ) /** - * Contains the timeline of the refund process, including the amount refunded, time of refund, and status. + * Timeline of the refund process. * - * @property refundAmount The amount refunded in the smallest currency unit (e.g., IRR). - * @property refundTime The time when the refund was processed. - * @property refundStatus The status of the refund (e.g., "completed", "pending"). + * @property refundAmount Refunded amount in the smallest currency unit (e.g., IRR). + * @property refundTime Timestamp of when the refund was processed. + * @property refundStatus Current status of the refund (e.g., "completed", "pending"). */ @Serializable data class PaymentRefundTimeline( @SerialName("refund_amount") val refundAmount: Int, @SerialName("refund_time") val refundTime: String, @SerialName("refund_status") val refundStatus: String -) \ No newline at end of file +) diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/reverse/PaymentReverseRequest.kt b/src/main/java/com/example/zarinpal/data/remote/dto/reverse/PaymentReverseRequest.kt index cd71de9..278af0e 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/reverse/PaymentReverseRequest.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/reverse/PaymentReverseRequest.kt @@ -1,4 +1,3 @@ - package com.example.zarinpal.data.remote.dto.reverse import com.example.zarinpal.data.remote.dto.Config @@ -6,30 +5,28 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** - * Represents the request data required to reverse a payment. + * Data model for a payment reversal request. * - * @property merchantId The unique identifier for the merchant (nullable). - * @property sandBox Indicates if the payment reversal should be processed in sandbox mode (test mode). - * @property authority The authorization code for the payment reversal (required). + * @property merchantId Optional merchant ID. If not provided, a default can be applied. + * @property sandBox Optional flag for sandbox mode (test environment). + * @property authority Required authorization code associated with the original payment. */ @Serializable data class PaymentReverseRequest( @SerialName("merchant_id") - val merchantId: String?=null, - val sandBox :Boolean?=null, - val authority :String, -){ + val merchantId: String? = null, + val sandBox: Boolean? = null, + val authority: String +) { /** - * Creates a copy of the request with the merchantId and sandBox values - * replaced by the ones from the provided [Config] if they are null. + * Creates a copy of the current instance, substituting null values for [merchantId] + * and [sandBox] with the corresponding values from the provided [config]. * - * @param config The [Config] object that provides default values for merchantId and sandBox. - * @return A new instance of [PaymentReverseRequest] with updated values. + * @param config Configuration containing default values. + * @return A new [PaymentReverseRequest] instance with updated fields. */ - fun copyWithConfig(config: Config): PaymentReverseRequest { - return this.copy( - merchantId = this.merchantId ?: config.merchantId, - sandBox = this.sandBox ?: config.sandBox - ) - } + fun copyWithConfig(config: Config): PaymentReverseRequest = copy( + merchantId = merchantId ?: config.merchantId, + sandBox = sandBox ?: config.sandBox + ) } diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/reverse/PaymentReverseResponse.kt b/src/main/java/com/example/zarinpal/data/remote/dto/reverse/PaymentReverseResponse.kt index 8671ef8..e7cd926 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/reverse/PaymentReverseResponse.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/reverse/PaymentReverseResponse.kt @@ -1,15 +1,14 @@ package com.example.zarinpal.data.remote.dto.reverse import androidx.annotation.Keep -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement /** - * Represents the response data returned after reversing a payment. + * Represents the response received after a payment reversal attempt. * - * @property data Contains the details of the payment reversal response. - * @property errors If present, contains any errors returned by the payment gateway. + * @property data Contains details of the reversal result. + * @property errors Optional error information returned by the gateway. */ @Keep @Serializable @@ -19,14 +18,14 @@ data class PaymentReverseResponse( ) /** - * Contains the data returned from the payment reversal process. + * Details the outcome of the payment reversal. * - * @property code A numeric code indicating the result of the reversal. - * @property message A descriptive message explaining the result or error. + * @property code Status code representing the result. + * @property message Description of the result or any error encountered. */ @Keep @Serializable data class PaymentReverseDataResponse( val code: Int, - val message: String, + val message: String ) diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/transaction/GraphTransactionModel.kt b/src/main/java/com/example/zarinpal/data/remote/dto/transaction/GraphTransactionModel.kt index cdaae14..8e1d260 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/transaction/GraphTransactionModel.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/transaction/GraphTransactionModel.kt @@ -1,17 +1,16 @@ package com.example.zarinpal.data.remote.dto.transaction -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** - * Represents the GraphQL request for fetching transactions. + * Represents a GraphQL request used to fetch transaction data. * - * @property query The GraphQL query string to be executed for the transaction operation. - * @property variables Contains the variables required for the transaction operation, - * including terminalId, filter, limit, offset, and token. + * @property query The GraphQL query string to be executed. + * @property variables The set of parameters required for retrieving transactions, + * such as terminalId, filter criteria, pagination limits, and authentication token. */ @Serializable data class GraphTransactionModel( - val query :String, + val query: String, val variables: TransactionRequest -) \ No newline at end of file +) diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/transaction/TransactionRequest.kt b/src/main/java/com/example/zarinpal/data/remote/dto/transaction/TransactionRequest.kt index bba7412..2a0e88c 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/transaction/TransactionRequest.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/transaction/TransactionRequest.kt @@ -6,13 +6,13 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** - * Represents the request data required to fetch transactions. + * Data class for transaction query request. * - * @property terminalId The unique identifier for the terminal from which transactions are being requested. - * @property filter A filter string to apply to the transactions (e.g., "completed", "pending"). - * @property limit The maximum number of transactions to return. - * @property offset The starting point for fetching transactions (used for pagination). - * @property token A token used for additional security (nullable). + * @property terminalId Identifier of the terminal making the request. + * @property filter Criteria used to filter transactions (e.g., completed, pending). + * @property limit Maximum number of transactions to fetch. + * @property offset Starting index for pagination. + * @property token Optional authentication token. */ @Keep @Serializable @@ -22,5 +22,5 @@ data class TransactionRequest( val filter: FilterEnum, val limit: Int, val offset: Int, - val token: String? = null, -) \ No newline at end of file + val token: String? = null +) diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/transaction/TransactionResponse.kt b/src/main/java/com/example/zarinpal/data/remote/dto/transaction/TransactionResponse.kt index 1385d9f..5c37859 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/transaction/TransactionResponse.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/transaction/TransactionResponse.kt @@ -5,42 +5,49 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** - * Represents the response data returned when fetching transactions. + * Represents the full response returned from a transaction fetch request. * - * @property data Contains the list of transaction sessions returned by the request. + * @property data The actual response payload containing transaction sessions. */ @Keep @Serializable data class TransactionResponse( - @SerialName("data") val data: Data? + @SerialName("data") + val data: TransactionData? ) /** - * Contains the data structure of the response, including a list of transaction sessions. + * Container for the session list in the transaction response. * * @property session A list of [Session] objects representing individual transactions. */ @Keep @Serializable -data class Data( - @SerialName("Session") val session: List? +data class TransactionData( + @SerialName("Session") + val session: List? ) /** - * Represents a single transaction session. + * Represents details of a single transaction session. * - * @property id The unique identifier of the transaction session. - * @property status The status of the transaction (e.g., "completed", "pending"). - * @property amount The total amount of the transaction. - * @property description A brief description of the transaction. - * @property createdAt The date and time when the transaction was created. + * @property id Unique identifier of the transaction. + * @property status Status of the transaction (e.g., "completed", "pending"). + * @property amount Transaction amount in the smallest currency unit. + * @property description Brief description of the transaction. + * @property createdAt Timestamp of when the transaction was initiated. */ @Keep @Serializable data class Session( - @SerialName("id") val id: String, - @SerialName("status") val status: String?, - @SerialName("amount") val amount: Long, - @SerialName("description") val description: String, - @SerialName("created_at") val createdAt: String + @SerialName("id") + val id: String, + @SerialName("status") + val status: String?, + @SerialName("amount") + val amount: Long, + @SerialName("description") + val description: String, + @SerialName("created_at") + val createdAt: String ) diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/unVerified/PaymentUnVerifiedRequest.kt b/src/main/java/com/example/zarinpal/data/remote/dto/unVerified/PaymentUnVerifiedRequest.kt index d5aa62e..2641356 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/unVerified/PaymentUnVerifiedRequest.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/unVerified/PaymentUnVerifiedRequest.kt @@ -1,4 +1,3 @@ - package com.example.zarinpal.data.remote.dto.unVerified import com.example.zarinpal.data.remote.dto.Config @@ -6,28 +5,25 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** - * Represents the request data required for unverified payment transactions. + * Request payload for unverified payment operations. * - * @property merchantId The unique identifier for the merchant (nullable). - * @property sandBox Indicates if the request should be processed in sandbox mode (test mode). + * @property merchantId Optional merchant ID; identifies the merchant making the request. + * @property sandBox Optional flag to indicate if the request is in sandbox (test) mode. */ @Serializable data class PaymentUnVerifiedRequest( @SerialName("merchant_id") - val merchantId: String?=null, - val sandBox :Boolean?=null, -){ + val merchantId: String? = null, + val sandBox: Boolean? = null +) { /** - * Creates a copy of the request with the merchantId and sandBox values - * replaced by the ones from the provided [Config] if they are null. + * Returns a new instance with missing fields populated from the provided [config]. * - * @param config The [Config] object that provides default values for merchantId and sandBox. - * @return A new instance of [PaymentUnVerifiedRequest] with updated values. + * @param config Configuration supplying default values. + * @return Updated [PaymentUnVerifiedRequest] instance. */ - fun copyWithConfig(config: Config): PaymentUnVerifiedRequest { - return this.copy( - merchantId = this.merchantId ?: config.merchantId, - sandBox = this.sandBox ?: config.sandBox - ) - } + fun copyWithConfig(config: Config): PaymentUnVerifiedRequest = copy( + merchantId = merchantId ?: config.merchantId, + sandBox = sandBox ?: config.sandBox + ) } diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/unVerified/PaymentUnVerifiedResponse.kt b/src/main/java/com/example/zarinpal/data/remote/dto/unVerified/PaymentUnVerifiedResponse.kt index dd6c2d0..656df9e 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/unVerified/PaymentUnVerifiedResponse.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/unVerified/PaymentUnVerifiedResponse.kt @@ -6,10 +6,10 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement /** - * Represents the response data returned when querying unverified payment transactions. + * Response model for unverified payment queries. * - * @property data Contains the details of the unverified payment response. - * @property errors If present, contains any errors returned by the payment gateway. + * @property data Contains the details of the unverified payment. + * @property errors Optional error information returned by the payment gateway. */ @Keep @Serializable @@ -19,11 +19,11 @@ data class PaymentUnVerifiedResponse( ) /** - * Contains the data returned from the unverified payment query. + * Contains the main response data for unverified payments. * - * @property code A numeric code indicating the result of the request. - * @property message A descriptive message explaining the result or error. - * @property authorities A list of [AuthorityResponse] objects representing the authorities related to the unverified payments. + * @property code Status code representing the result of the query. + * @property message Descriptive message about the outcome. + * @property authorities Optional list of authorities related to unverified transactions. */ @Keep @Serializable @@ -34,20 +34,21 @@ data class PaymentUnVerifiedDataResponse( ) /** - * Represents an authority related to an unverified payment. + * Represents an authority entry related to an unverified transaction. * - * @property authority The authority code for the payment. - * @property amount The amount of the unverified payment. - * @property callbackUrl The URL to which the user will be redirected after payment. - * @property referer The referer URL from which the request was made. - * @property date The date of the unverified payment. + * @property authority Unique identifier for the transaction. + * @property amount Transaction amount in the smallest currency unit. + * @property callbackUrl URL to redirect the user after payment. + * @property referer Referring URL from which the request originated. + * @property date Timestamp of the transaction event. */ @Keep @Serializable data class AuthorityResponse( val authority: String, val amount: Int, - @SerialName("callback_url") val callbackUrl: String, + @SerialName("callback_url") + val callbackUrl: String, val referer: String, val date: String ) diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/verification/PaymentVerifyRequest.kt b/src/main/java/com/example/zarinpal/data/remote/dto/verification/PaymentVerifyRequest.kt index 9dad515..e64f864 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/verification/PaymentVerifyRequest.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/verification/PaymentVerifyRequest.kt @@ -1,4 +1,3 @@ - package com.example.zarinpal.data.remote.dto.verification import com.example.zarinpal.data.remote.dto.Config @@ -6,32 +5,29 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** - * Represents the request data required for verifying a payment. + * Represents the request payload for verifying a payment transaction. * - * @property merchantId The unique identifier for the merchant (nullable). - * @property sandBox Indicates if the payment should be processed in sandbox mode (test mode). - * @property amount The total amount of the payment in the smallest currency unit (e.g., IRR). - * @property authority The authority code provided by the payment gateway for the transaction. + * @property merchantId Optional identifier for the merchant. + * @property sandBox Optional flag indicating whether the request should use sandbox (test) mode. + * @property amount Payment amount in the smallest currency unit (e.g., IRR). + * @property authority Authority code received from the payment gateway for the transaction. */ @Serializable data class PaymentVerifyRequest( @SerialName("merchant_id") - val merchantId: String?=null, - val sandBox :Boolean?=null, + val merchantId: String? = null, + val sandBox: Boolean? = null, val amount: Int, - val authority :String, -){ + val authority: String +) { /** - * Creates a copy of the request with the merchantId and sandBox values - * replaced by the ones from the provided [Config] if they are null. + * Creates a copy of this request, substituting null values with defaults from [config]. * - * @param config The [Config] object that provides default values for merchantId and sandBox. - * @return A new instance of [PaymentVerifyRequest] with updated values. + * @param config Configuration object providing fallback values. + * @return A new instance of [PaymentVerifyRequest] with completed properties. */ - fun copyWithConfig(config: Config): PaymentVerifyRequest { - return this.copy( - merchantId = this.merchantId ?: config.merchantId, - sandBox = this.sandBox ?: config.sandBox - ) - } + fun copyWithConfig(config: Config): PaymentVerifyRequest = copy( + merchantId = merchantId ?: config.merchantId, + sandBox = sandBox ?: config.sandBox + ) } diff --git a/src/main/java/com/example/zarinpal/data/remote/dto/verification/PaymentVerifyResponse.kt b/src/main/java/com/example/zarinpal/data/remote/dto/verification/PaymentVerifyResponse.kt index dc44a4c..fc4dfae 100644 --- a/src/main/java/com/example/zarinpal/data/remote/dto/verification/PaymentVerifyResponse.kt +++ b/src/main/java/com/example/zarinpal/data/remote/dto/verification/PaymentVerifyResponse.kt @@ -6,10 +6,10 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement /** - * Represents the response data returned when verifying a payment. + * Response model for payment verification. * - * @property data Contains the details of the payment verification response. - * @property errors If present, contains any errors returned by the payment gateway. + * @property data Detailed response of the verified payment. + * @property errors Optional error information from the payment gateway. */ @Keep @Serializable @@ -19,18 +19,18 @@ data class PaymentVerificationResponse( ) /** - * Contains the data returned from the payment verification request. + * Contains payment verification result details. * - * @property wages A list of wages associated with the payment (nullable). - * @property code A numeric code indicating the result of the request. - * @property message A descriptive message explaining the result or error. - * @property cardHash The hash of the card used for the payment. - * @property cardPan The primary account number (PAN) of the card used for the payment. - * @property refId The reference ID for the transaction (nullable). - * @property feeType The type of fee applied to the payment. - * @property fee The amount of the fee applied to the payment. - * @property shaparakFee The fee imposed by the Shaparak payment system. - * @property orderId The unique identifier for the order associated with the payment (nullable). + * @property wages Optional list of wages associated with the payment. + * @property code Numeric status code representing the outcome. + * @property message Description of the result or any error. + * @property cardHash Hash of the card used in the transaction. + * @property cardPan Primary account number (PAN) of the card. + * @property refId Optional reference ID for the transaction. + * @property feeType Optional type of fee applied. + * @property fee Amount of the applied fee. + * @property shaparakFee Fee charged by the Shaparak system. + * @property orderId Optional identifier of the associated order. */ @Keep @Serializable diff --git a/src/main/java/com/example/zarinpal/data/remote/enum/FilterEnum.kt b/src/main/java/com/example/zarinpal/data/remote/enum/FilterEnum.kt index 6f132f6..791ddde 100644 --- a/src/main/java/com/example/zarinpal/data/remote/enum/FilterEnum.kt +++ b/src/main/java/com/example/zarinpal/data/remote/enum/FilterEnum.kt @@ -9,5 +9,46 @@ enum class FilterEnum { VERIFIED, TRASH, ACTIVE, - REFUNDED -} \ No newline at end of file + REFUNDED; + + fun isFinal(): Boolean = this in listOf(PAID, REFUNDED, TRASH) + + fun isActive(): Boolean = this in listOf(ACTIVE, VERIFIED) + + fun needsVerification(): Boolean = this == ACTIVE || this == ALL + + fun canBeRefunded(): Boolean = this in listOf(PAID, VERIFIED) + + override fun toString(): String = name.lowercase().replaceFirstChar { it.uppercase() } +} + +object EnvironmentConfig { + enum class Environment(val baseUrl: String) { + SANDBOX("https://sandbox.zarinpal.com/pg/rest/WebGate/"), + PRODUCTION("https://www.zarinpal.com/pg/rest/WebGate/") + } + + private var environment: Environment = Environment.SANDBOX + + val baseUrl: String + get() = environment.baseUrl + + fun useSandbox() { + environment = Environment.SANDBOX + } + + fun useProduction() { + environment = Environment.PRODUCTION + } + + fun isSandbox(): Boolean = environment == Environment.SANDBOX + + fun isProduction(): Boolean = environment == Environment.PRODUCTION + + fun currentEnvironment(): Environment = environment + + fun debugInfo(): String = "Environment: ${environment.name}, Base URL: ${baseUrl}" + + fun resolveEndpoint(path: String): String = baseUrl + path.trimStart('/') +} + diff --git a/src/main/java/com/example/zarinpal/data/remote/enum/MethodEnum.kt b/src/main/java/com/example/zarinpal/data/remote/enum/MethodEnum.kt index 1de4c88..50c4893 100644 --- a/src/main/java/com/example/zarinpal/data/remote/enum/MethodEnum.kt +++ b/src/main/java/com/example/zarinpal/data/remote/enum/MethodEnum.kt @@ -5,5 +5,37 @@ import kotlinx.serialization.Serializable @Serializable enum class MethodEnum { PAYA, - CARD -} \ No newline at end of file + CARD; + + override fun toString(): String = name.lowercase().replaceFirstChar { it.uppercase() } +} + +object EnvironmentConfig { + enum class Environment(val baseUrl: String) { + SANDBOX("https://sandbox.zarinpal.com/pg/rest/WebGate/"), + PRODUCTION("https://www.zarinpal.com/pg/rest/WebGate/") + } + + private var environment: Environment = Environment.SANDBOX + + val baseUrl: String + get() = environment.baseUrl + + fun useSandbox() { + environment = Environment.SANDBOX + } + + fun useProduction() { + environment = Environment.PRODUCTION + } + + fun isSandbox(): Boolean = environment == Environment.SANDBOX + + fun isProduction(): Boolean = environment == Environment.PRODUCTION + + fun currentEnvironment(): Environment = environment + + fun debugInfo(): String = "Environment: ${environment.name}, Base URL: ${baseUrl}" + + fun resolveEndpoint(path: String): String = baseUrl + path.trimStart('/') +} diff --git a/src/main/java/com/example/zarinpal/data/remote/enum/ReasonEnum.kt b/src/main/java/com/example/zarinpal/data/remote/enum/ReasonEnum.kt index 7fd406d..9e3627b 100644 --- a/src/main/java/com/example/zarinpal/data/remote/enum/ReasonEnum.kt +++ b/src/main/java/com/example/zarinpal/data/remote/enum/ReasonEnum.kt @@ -2,11 +2,43 @@ package com.example.zarinpal.data.remote.enum import kotlinx.serialization.Serializable - @Serializable enum class ReasonEnum { DUPLICATE_TRANSACTION, SUSPICIOUS_TRANSACTION, CUSTOMER_REQUEST, - OTHER -} \ No newline at end of file + OTHER; + + override fun toString(): String = name.lowercase().replace("_", " ").replaceFirstChar { it.uppercase() } +} + +object EnvironmentConfig { + enum class Environment(val baseUrl: String) { + SANDBOX("https://sandbox.zarinpal.com/pg/rest/WebGate/"), + PRODUCTION("https://www.zarinpal.com/pg/rest/WebGate/") + } + + private var environment: Environment = Environment.SANDBOX + + val baseUrl: String + get() = environment.baseUrl + + fun useSandbox() { + environment = Environment.SANDBOX + } + + fun useProduction() { + environment = Environment.PRODUCTION + } + + fun isSandbox(): Boolean = environment == Environment.SANDBOX + + fun isProduction(): Boolean = environment == Environment.PRODUCTION + + fun currentEnvironment(): Environment = environment + + fun debugInfo(): String = "Environment: ${environment.name}, Base URL: ${baseUrl}" + + fun resolveEndpoint(path: String): String = baseUrl + path.trimStart('/') +} + diff --git a/src/main/java/com/example/zarinpal/utils/Validator.kt b/src/main/java/com/example/zarinpal/utils/Validator.kt index 0fc467f..bed3869 100644 --- a/src/main/java/com/example/zarinpal/utils/Validator.kt +++ b/src/main/java/com/example/zarinpal/utils/Validator.kt @@ -1,189 +1,91 @@ package com.example.zarinpal.utils /** - * This object contains functions for validating various input fields used in the ZarinPal payment gateway system. + * Provides validation functions for input fields used in the ZarinPal payment system. */ -class Validator { +object Validator { - companion object { - - /** - * Validates the provided merchant ID. It must be in the correct UUID format. - * - * @param merchantId The merchant ID to validate. - * @throws IllegalArgumentException if the merchant ID is null or invalid. - */ - fun validateMerchantId(merchantId: String) { - val pattern = "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$".toRegex() - if (!pattern.matches(merchantId)) { - throw IllegalArgumentException("Invalid merchant_id format. It should be a valid UUID.") - } + fun validateMerchantId(merchantId: String) { + require(merchantId.matches(Regex("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"))) { + "Invalid merchant ID format. Must be a valid UUID." } + } - /** - * Validates the authority string. It must start with 'A' or 'S' followed by 35 alphanumeric characters. - * - * @param authority The authority string to validate. - * @throws IllegalArgumentException if the authority format is invalid. - */ - fun validateAuthority(authority: String) { - val pattern = "^[AS][0-9a-zA-Z]{35}$".toRegex() - if (!pattern.matches(authority)) { - throw IllegalArgumentException("Invalid authority format. It should be a string starting with 'A' or 'S' followed by 35 alphanumeric characters.") - } + fun validateAuthority(authority: String) { + require(authority.matches(Regex("^[AS][0-9a-zA-Z]{35}$"))) { + "Invalid authority format. Must start with 'A' or 'S' followed by 35 alphanumeric characters." } + } - /** - * Validates the payment amount. It must be greater than or equal to the minimum amount. - * - * @param amount The amount to validate. - * @param minAmount The minimum allowed amount (default is 1000). - * @throws IllegalArgumentException if the amount is less than the minimum. - */ - fun validateAmount(amount: Int, minAmount: Int = 1000) { - if (amount < minAmount) { - throw IllegalArgumentException("Amount must be at least $minAmount.") - } - } + fun validateAmount(amount: Int, minAmount: Int = 1000) { + require(amount >= minAmount) { "Amount must be at least $minAmount." } + } - /** - * Validates the callback URL. It must start with "http://" or "https://". - * - * @param callbackUrl The callback URL to validate. - * @throws IllegalArgumentException if the callback URL format is invalid. - */ - fun validateCallbackUrl(callbackUrl: String) { - val pattern = "^https?:\\/\\/.*$".toRegex() - if (!pattern.matches(callbackUrl)) { - throw IllegalArgumentException("Invalid callback URL format. It should start with http:// or https://.") - } + fun validateCallbackUrl(callbackUrl: String) { + require(callbackUrl.matches(Regex("^https?://.*$"))) { + "Invalid callback URL. Must start with http:// or https://." } + } - /** - * Validates the mobile number. It must start with "09" and contain 11 digits. - * - * @param mobile The mobile number to validate. - * @throws IllegalArgumentException if the mobile number is invalid. - */ - fun validateMobile(mobile: String?) { - val pattern = "^09[0-9]{9}$".toRegex() - if (mobile != null && !pattern.matches(mobile)) { - throw IllegalArgumentException("Invalid mobile number format.") - } + fun validateMobile(mobile: String?) { + if (mobile != null) { + require(mobile.matches(Regex("^09[0-9]{9}$"))) { "Invalid mobile number format." } } + } - /** - * Validates the email address. It must follow a valid email format. - * - * @param email The email address to validate. - * @throws IllegalArgumentException if the email format is invalid. - */ - fun validateEmail(email: String?) { - val pattern = "^[^\\s@]+@[^(\\s@)]+\\.[^\\s@]+$".toRegex() - if (email != null && !pattern.matches(email)) { - throw IllegalArgumentException("Invalid email format.") - } + fun validateEmail(email: String?) { + if (email != null) { + require(email.matches(Regex("^[^\s@]+@[^\s@]+\.[^\s@]+$"))) { "Invalid email format." } } + } - /** - * Validates the currency. Allowed values are "IRR" or "IRT". - * - * @param currency The currency to validate. - * @throws IllegalArgumentException if the currency is not in the allowed list. - */ - fun validateCurrency(currency: String?) { - val validCurrencies = listOf("IRR", "IRT") - if (currency != null && currency !in validCurrencies) { - throw IllegalArgumentException("Invalid currency format. Allowed values are 'IRR' or 'IRT'.") + fun validateCurrency(currency: String?) { + if (currency != null) { + require(currency == "IRR" || currency == "IRT") { + "Invalid currency format. Allowed values are 'IRR' or 'IRT'." } } + } - /** - * Validates the wages list. Each wage must include a valid IBAN, positive amount, and a description shorter than 255 characters. - * - * @param wages The list of wages to validate. - * @throws IllegalArgumentException if any of the wage entries is invalid. - */ - fun validateWages(wages: List>?) { - wages?.forEach { wage -> - val iban = wage["iban"] as String - val amount = wage["amount"] as Double - val description = wage["description"] as String + fun validateWages(wages: List>?) { + wages?.forEach { wage -> + val iban = wage["iban"] as? String ?: throw IllegalArgumentException("Wage IBAN is required.") + val amount = wage["amount"] as? Double ?: throw IllegalArgumentException("Wage amount is required.") + val description = wage["description"] as? String ?: throw IllegalArgumentException("Wage description is required.") - val ibanPattern = "^[A-Z]{2}[0-9]{2}[0-9A-Z]{1,30}$".toRegex() - if (!ibanPattern.matches(iban)) { - throw IllegalArgumentException("Invalid IBAN format in wages.") - } - if (amount <= 0) { - throw IllegalArgumentException("Wage amount must be greater than zero.") - } - if (description.length > 255) { - throw IllegalArgumentException("Wage description must be provided and less than 255 characters.") - } + require(iban.matches(Regex("^[A-Z]{2}[0-9]{2}[0-9A-Z]{1,30}$"))) { + "Invalid IBAN format in wages." } + require(amount > 0) { "Wage amount must be greater than zero." } + require(description.length <= 255) { "Wage description must be 255 characters or fewer." } } + } - /** - * Validates the terminal ID. It must contain only digits and cannot be empty. - * - * @param terminalId The terminal ID to validate. - * @throws IllegalArgumentException if the terminal ID is empty or contains non-digit characters. - */ - fun validateTerminalId(terminalId: String) { - val regex = Regex("^[0-9]+$") // Matches only digits. - if (terminalId.isEmpty() || !regex.matches(terminalId)) { - throw IllegalArgumentException("Terminal ID must contain only digits and cannot be empty.") - } + fun validateTerminalId(terminalId: String) { + require(terminalId.isNotEmpty() && terminalId.matches(Regex("^[0-9]+$"))) { + "Terminal ID must contain only digits and cannot be empty." } + } - /** - * Validates the session ID to ensure it is not empty. - * - * @param sessionId The session ID to validate. - * @throws IllegalArgumentException if the session ID is empty. - */ - fun validateSessionId(sessionId: String) { - val regex = Regex("^[0-9]+$") // Matches only digits. - if (sessionId.isEmpty() || !regex.matches(sessionId)) { - throw IllegalArgumentException("Session ID is required.") - } + fun validateSessionId(sessionId: String) { + require(sessionId.isNotEmpty() && sessionId.matches(Regex("^[0-9]+$"))) { + "Session ID must contain only digits and cannot be empty." } + } - /** - * Validates the limit value to ensure it is a positive integer. - * - * @param limit The limit value to validate. - * @throws IllegalArgumentException if the limit is not positive. - */ - fun validateLimit(limit: Int) { - if (limit <= 0) { - throw IllegalArgumentException("Limit must be a positive integer.") - } - } + fun validateLimit(limit: Int) { + require(limit > 0) { "Limit must be a positive integer." } + } - /** - * Validates the offset value to ensure it is a non-negative integer. - * - * @param offset The offset value to validate. - * @throws IllegalArgumentException if the offset is negative. - */ - fun validateOffset(offset: Int) { - if (offset < 0) { - throw IllegalArgumentException("Offset must be a non-negative integer.") - } - } + fun validateOffset(offset: Int) { + require(offset >= 0) { "Offset must be a non-negative integer." } + } - /** - * Validates the card PAN (Primary Account Number) to ensure it is a 16-digit number. - * - * @param cardPan The card PAN value to validate. - * @throws IllegalArgumentException if the card PAN format is invalid. - */ - fun validateCardPan(cardPan: String?) { - val pattern = "^[0-9]{16}$".toRegex() - if (cardPan != null && !pattern.matches(cardPan)) { - throw IllegalArgumentException("Invalid card PAN format. It should be a 16-digit number.") + fun validateCardPan(cardPan: String?) { + if (cardPan != null) { + require(cardPan.matches(Regex("^[0-9]{16}$"))) { + "Invalid card PAN format. Must be a 16-digit number." } } } -} +}