From 0e36abf87b599f019c7d428641839a1540513e48 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 7 Sep 2023 19:57:45 +0300 Subject: [PATCH 01/73] Removed useless todo on callbacks --- daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index f37e15b0..f9e606eb 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -166,7 +166,7 @@ class Daraja constructor( amount = amount.toString(), transactionType = transactionType.name, phoneNumber = phoneNumber.getDarajaPhoneNumber(), - callBackUrl = callbackUrl, // ToDo: Figure out how callback urls work + callBackUrl = callbackUrl, accountReference = accountReference ?: businessShortCode, partyA = phoneNumber, partyB = businessShortCode From 025ae96648b65144740fbfc941eb48508186fac1 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Sat, 9 Sep 2023 15:07:10 +0300 Subject: [PATCH 02/73] Added @Throws to Daraja interface funcs --- daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index f9e606eb..5e6c62fa 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -18,6 +18,7 @@ package com.vickbt.darajakmp import com.vickbt.darajakmp.network.DarajaApiService import com.vickbt.darajakmp.network.DarajaHttpClientFactory +import com.vickbt.darajakmp.network.models.DarajaException import com.vickbt.darajakmp.network.models.DarajaPaymentRequest import com.vickbt.darajakmp.network.models.DarajaPaymentResponse import com.vickbt.darajakmp.network.models.DarajaToken @@ -122,6 +123,7 @@ class Daraja constructor( * * @return [DarajaToken] * */ + @Throws(DarajaException::class) fun requestAccessToken(): DarajaResult = runBlocking { withContext(ioCoroutineContext) { return@withContext darajaApiService.fetchAccessToken() @@ -141,6 +143,7 @@ class Daraja constructor( * * @return [DarajaPaymentResponse] * */ + @Throws(DarajaException::class) fun initiateMpesaExpressPayment( businessShortCode: String, amount: Int, @@ -184,6 +187,7 @@ class Daraja constructor( * * @return [DarajaTransactionResponse] * */ + @Throws(DarajaException::class) fun queryMpesaTransaction( businessShortCode: String, checkoutRequestID: String From 607b68ad00d391e220205cb1297545de08f644c8 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Sat, 9 Sep 2023 15:26:06 +0300 Subject: [PATCH 03/73] Wrap daraja.initiateMpesaExpressPayment in a try-catch block --- app-iOS/app-iOS/ContentView.swift | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/app-iOS/app-iOS/ContentView.swift b/app-iOS/app-iOS/ContentView.swift index 85e55853..d83c3fe1 100644 --- a/app-iOS/app-iOS/ContentView.swift +++ b/app-iOS/app-iOS/ContentView.swift @@ -81,16 +81,20 @@ func initiateMpesaPayment(daraja:Daraja, transactionDesc: String, callbackUrl: String, accountReference: String){ - let response=daraja.initiateMpesaExpressPayment(businessShortCode: businessShortCode, amount: amount, phoneNumber: phoneNumber,transactionType: DarajaTransactionType.customerpaybillonline, transactionDesc: "M-Pesa payment", callbackUrl: "https://mydomain.com/path", accountReference: "Daraja KMP iOS") - - response.onSuccess(action: {data in - print(data.self) - }) - .onFailure(action: {error in - print(error) - }) - - print(response) + do{ + let response=try daraja.initiateMpesaExpressPayment(businessShortCode: businessShortCode, amount: amount, phoneNumber: phoneNumber,transactionType: DarajaTransactionType.customerpaybillonline, transactionDesc: "M-Pesa payment", callbackUrl: "https://mydomain.com/path", accountReference: "Daraja KMP iOS") + + response.onSuccess(action: {data in + print(data.self) + }) + .onFailure(action: {error in + print(error) + }) + + print(response) + }catch{ + print("An error occured") + } } struct ContentView_Previews: PreviewProvider { From 480675910afe60c4f3f67c64d567551ffaa6c352 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Tue, 12 Sep 2023 18:24:00 +0300 Subject: [PATCH 04/73] Update readme --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 1edd3484..8644caf7 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ ## Table of Content - [Prerequisite](#prerequisite) +- [Features](#features) - [Usage](#usage) - [Android - Kotlin](#android---kotlin) - [Setting Up](#setting-up) @@ -33,6 +34,22 @@ To get started, you’ll need to create an account on the Daraja API portal to u After successfully creating an account on the Daraja API portal and creating a new Daraja app, you’ll need to add your ___consumer key___, ___consumer secret___ and ___pass key___ obtained from the Daraja API portal to your project. +## Features + +The SDK offers the following functionalities from the Daraja API: + +- [x] Authorization - Gives you a time bound access token to call allowed APIs. +- [x] M-Pesa Express - Merchant initiated online payments. +- [ ] Customer To Business (C2B) +- [ ] Business To Customer (B2C) - Transact between an M-Pesa short code to a phone number registered on M-Pesa. +- [x] Transaction Status - Check the status of a transaction. +- [ ] Account Balanace - Enquire the balance on an M-Pesa BuyGoods (Till Number) +- [ ] Reversal - Reverses an M-Pesa transaction. +- [ ] Tax Remittance - This API enables businesses to remit tax to Kenya Revenue Authority (KRA). +- [ ] Business Pay Bill - Pay bills directly from your business account to a pay bill number, or a paybill store. +- [ ] Business Buy Goods - Pay for goods and services directly from your business account to a till number or merchant store number. + + ## Usage # Android - Kotlin From fdfe7caea38ea43c15b076eb5fd9ab45764fa388 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Tue, 12 Sep 2023 18:28:25 +0300 Subject: [PATCH 05/73] Update README.md --- README.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8644caf7..9e600d03 100644 --- a/README.md +++ b/README.md @@ -82,15 +82,7 @@ The SDK offers the following functionalities from the Daraja API: - Add your consumer secret, consumer key and pass key to your project. You can get them from the [Daraja API portal](https://developer.safaricom.co.ke/MyApps). -```Kotlin -object Constants { - const val CONSUMER_SECRET="your_consumer_secret" - const val CONSUMER_KEY="your_consumer_key" - const val PASS_KEY="your_pass_key" -} -``` - -> You should not add your daraja API environment variables in a production application because it is a vulnerability to expose your environment secrets/variables in your version control system. Ideally, you should add them to your `Local.properties` files as demonstrated in the [sample](https://github.com/VictorKabata/DarajaMultiplatform/tree/main/app-android) android application. +> You should not add your daraja API environment variables in a production application because it is a vulnerability to expose your environment secrets/variables in your version control system. Ideally, you should add them to your `local.properties` files as demonstrated in the [sample](https://github.com/VictorKabata/DarajaMultiplatform/tree/main/app-android) android application. - Create an instance of the Daraja object by passing the daraja environment variables. The daraja object provides functions to request for an access token and initiate M-Pesa express STK request. @@ -105,7 +97,7 @@ val daraja: Daraja = Daraja.Builder() > Network logging is enabled by default when using Daraja Multiplatform. in sandbox/testing mode. The logs can be accessed from the logcat in Android Studio under the `Daraja Multiplatform` tag. -> Network logs are strictly disabled in production mode. +> Network logs are disabled in production mode. ### Request Access Token From 556b643ff87e82d2422b9762cbf81bdfd74e5f34 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Tue, 12 Sep 2023 18:54:04 +0300 Subject: [PATCH 06/73] Update daraja interface func names --- .../commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index 5e6c62fa..a3eb001e 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -121,10 +121,12 @@ class Daraja constructor( /**Request access token that is used to authenticate to Daraja APIs * + * @throws DarajaException * @return [DarajaToken] * */ @Throws(DarajaException::class) - fun requestAccessToken(): DarajaResult = runBlocking { + @ObjCName(swiftName = "authorization") + fun authorization(): DarajaResult = runBlocking { withContext(ioCoroutineContext) { return@withContext darajaApiService.fetchAccessToken() } @@ -141,10 +143,12 @@ class Daraja constructor( * @param [callbackUrl] This is a valid secure URL that is used to receive notifications from M-Pesa API. It is the endpoint to which the results will be sent by M-Pesa API. * @param [accountReference] This is an alpha-numeric parameter that is defined by your system as an Identifier of the transaction for CustomerPayBillOnline transaction type. * + * @throws DarajaException * @return [DarajaPaymentResponse] * */ @Throws(DarajaException::class) - fun initiateMpesaExpressPayment( + @ObjCName(swiftName = "mpesaExpress") + fun mpesaExpress( businessShortCode: String, amount: Int, phoneNumber: String, @@ -185,10 +189,12 @@ class Daraja constructor( * @param [businessShortCode] This is organizations shortcode (Paybill or Buygoods - A 5 to 7 digit account number) used to identify an organization and receive the transaction. * @param [checkoutRequestID] This is a global unique identifier of the processed checkout transaction request. * + * @throws DarajaException * @return [DarajaTransactionResponse] * */ @Throws(DarajaException::class) - fun queryMpesaTransaction( + @ObjCName(swiftName = "transactionStatus") + fun transactionStatus( businessShortCode: String, checkoutRequestID: String ): DarajaResult = runBlocking { From eb9ba8b415bd15de3aec763e2d4089b28d75bd83 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Tue, 12 Sep 2023 19:12:29 +0300 Subject: [PATCH 07/73] Update android sample app --- .../com/vickbt/app_android/ui/screens/home/HomeViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-android/src/main/java/com/vickbt/app_android/ui/screens/home/HomeViewModel.kt b/app-android/src/main/java/com/vickbt/app_android/ui/screens/home/HomeViewModel.kt index fe3e8eb1..01a4ecfa 100644 --- a/app-android/src/main/java/com/vickbt/app_android/ui/screens/home/HomeViewModel.kt +++ b/app-android/src/main/java/com/vickbt/app_android/ui/screens/home/HomeViewModel.kt @@ -38,7 +38,7 @@ class HomeViewModel constructor(private val daraja: Daraja) : ViewModel() { callbackUrl: String, accountReference: String ) = viewModelScope.launch { - val response = daraja.initiateMpesaExpressPayment( + val response = daraja.mpesaExpress( businessShortCode = businessShortCode.trim(), amount = amount, phoneNumber = phoneNumber.trim(), From dda94280961d22e33cab6e9e2dca205fa2c0afa7 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Tue, 12 Sep 2023 19:16:05 +0300 Subject: [PATCH 08/73] Update daraja result swift name --- .../kotlin/com/vickbt/darajakmp/utils/DarajaResult.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaResult.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaResult.kt index 448e9032..73461f2a 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaResult.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaResult.kt @@ -24,10 +24,11 @@ import kotlin.native.ObjCName @ObjCName(swiftName = "DarajaResult") sealed class DarajaResult { @ObjCName(swiftName = "Success") - data class Success(val data: T) : DarajaResult() + data class Success(@ObjCName(swiftName = "data") val data: T) : DarajaResult() @ObjCName(swiftName = "Error") - data class Failure(val exception: DarajaException) : DarajaResult() + data class Failure(@ObjCName(swiftName = "error") val exception: DarajaException) : + DarajaResult() // object Loading : DarajaResult() ToDo } @@ -43,6 +44,7 @@ internal fun DarajaResult.getOrNull(): T? { /**Returns exception of type [DarajaException] on failure * + * @throws DarajaException * @receiver [DarajaResult] * */ internal fun DarajaResult.throwOnFailure(): DarajaException { From 735ee4d39d1243e3e344db7bd9ab05ef28681a42 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Wed, 13 Sep 2023 19:21:53 +0300 Subject: [PATCH 09/73] Added prettyPrint to httpClient --- .../com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt index e950556b..155a762c 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt @@ -62,6 +62,7 @@ internal class DarajaHttpClientFactory constructor(private val environment: Dara install(ContentNegotiation) { json( Json { + prettyPrint = true ignoreUnknownKeys = true isLenient = true } From a7fb494a8934353e3f3f6fa8152a58f4c879cc1e Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Wed, 13 Sep 2023 19:24:47 +0300 Subject: [PATCH 10/73] Removed httpCache declaration --- .../java/com/vickbt/app_android/ui/screens/home/HomeScreen.kt | 2 ++ .../com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app-android/src/main/java/com/vickbt/app_android/ui/screens/home/HomeScreen.kt b/app-android/src/main/java/com/vickbt/app_android/ui/screens/home/HomeScreen.kt index 720e2f2a..e5c9d558 100644 --- a/app-android/src/main/java/com/vickbt/app_android/ui/screens/home/HomeScreen.kt +++ b/app-android/src/main/java/com/vickbt/app_android/ui/screens/home/HomeScreen.kt @@ -16,6 +16,7 @@ package com.vickbt.app_android.ui.screens.home +import android.util.Log import android.widget.Toast import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -164,6 +165,7 @@ fun HomeScreen(viewModel: HomeViewModel = get()) { Toast.makeText(context, "Success: $it", Toast.LENGTH_SHORT).show() }?.onFailure { Toast.makeText(context, "Error: ${it.errorMessage}", Toast.LENGTH_SHORT).show() + Log.e("VicKbt", "Error: $it") } } diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt index 155a762c..bdb38d4c 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt @@ -56,9 +56,7 @@ internal class DarajaHttpClientFactory constructor(private val environment: Dara url { protocol = URLProtocol.HTTPS } } } - - install(HttpCache) - + install(ContentNegotiation) { json( Json { From 85f58301954871c98367f185da676e30c0af571f Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Wed, 13 Sep 2023 19:25:30 +0300 Subject: [PATCH 11/73] Return error from parseNetworkError func --- .../com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt | 2 +- .../kotlin/com/vickbt/darajakmp/network/DarajaSafeApiCall.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt index bdb38d4c..61a5959d 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt @@ -56,7 +56,7 @@ internal class DarajaHttpClientFactory constructor(private val environment: Dara url { protocol = URLProtocol.HTTPS } } } - + install(ContentNegotiation) { json( Json { diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaSafeApiCall.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaSafeApiCall.kt index 1b329a46..b06e9501 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaSafeApiCall.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaSafeApiCall.kt @@ -59,6 +59,6 @@ internal suspend fun parseNetworkError( errorResponse: HttpResponse? = null, exception: Exception? = null ): DarajaException { - throw errorResponse?.body() + return errorResponse?.body() ?: DarajaException(requestId = "0", errorCode = "0", errorMessage = exception?.message) } From da882e78e57d60e79eb49d6d6f1bca2d5156d2e3 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Wed, 13 Sep 2023 20:43:59 +0300 Subject: [PATCH 12/73] Update Utils.kt --- .../com/vickbt/darajakmp/utils/Utils.kt | 72 ------------------- 1 file changed, 72 deletions(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/Utils.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/Utils.kt index 3499b292..e69de29b 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/Utils.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/Utils.kt @@ -1,72 +0,0 @@ -/* - * Copyright 2022 Daraja Multiplatform - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.vickbt.darajakmp.utils - -import com.vickbt.darajakmp.network.models.DarajaException -import io.ktor.util.encodeBase64 -import kotlinx.datetime.Instant -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toLocalDateTime - -/**Format current timestamp to YYYYMMDDHHmmss format*/ -internal fun Instant.getDarajaTimestamp(): String { - val currentDateTime = this.toLocalDateTime(TimeZone.currentSystemDefault()) - - val year = currentDateTime.year - val month = currentDateTime.monthNumber.asFormattedWithZero() - val dayOfMonth = currentDateTime.dayOfMonth.asFormattedWithZero() - val hour = currentDateTime.hour.asFormattedWithZero() - val minutes = currentDateTime.minute.asFormattedWithZero() - val seconds = currentDateTime.second.asFormattedWithZero() - - return "$year$month$dayOfMonth$hour$minutes$seconds" -} - -/** - * Formats time values that have a single digit by prefixing them with an extra zero - * e.g "1:00" becomes "01:00" - */ -internal fun Int.asFormattedWithZero(): Comparable<*> = when (this < 10) { - true -> "0$this" - false -> this -} - -// Shortcode+Passkey+Timestamp -/** Generates a base 64 string by encoding a combination of [shortCode], [passkey] and [timestamp]*/ -internal fun getDarajaPassword(shortCode: String, passkey: String, timestamp: String): String { - val password = shortCode + passkey + timestamp - - return password.encodeBase64() -} - -/**Format phone number provided by user to format that Daraja API recognises ie. 254714023125 - * - * @return [phoneNumber] Formatted string to match a valid Safaricom/M-Pesa number - * @throws [DarajaException] - * */ -internal fun String.getDarajaPhoneNumber(): String { - val phoneNumber = this.replace("\\s".toRegex(), "") - - return when { - phoneNumber.matches(Regex("^(?:254)?[17](?:\\d\\d|0[0-8]|(9[0-2]))\\d{6}\$")) -> phoneNumber - phoneNumber.matches(Regex("^0?[17](?:\\d\\d|0[0-8]|(9[0-2]))\\d{6}\$")) -> - phoneNumber.replaceFirst("0", "254") - phoneNumber.matches(Regex("^(?:\\+254)?[17](?:\\d\\d|0[0-8]|(9[0-2]))\\d{6}\$")) -> - phoneNumber.replaceFirst("+", "") - else -> throw DarajaException("Invalid phone number format provided: $this") - } -} From 623476cb012ac2949cbb1d8c184cbb70f25f0e9b Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Wed, 13 Sep 2023 20:44:15 +0300 Subject: [PATCH 13/73] Update Utils.kt --- .../com/vickbt/darajakmp/utils/Utils.kt | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/Utils.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/Utils.kt index e69de29b..3499b292 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/Utils.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/Utils.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2022 Daraja Multiplatform + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.vickbt.darajakmp.utils + +import com.vickbt.darajakmp.network.models.DarajaException +import io.ktor.util.encodeBase64 +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime + +/**Format current timestamp to YYYYMMDDHHmmss format*/ +internal fun Instant.getDarajaTimestamp(): String { + val currentDateTime = this.toLocalDateTime(TimeZone.currentSystemDefault()) + + val year = currentDateTime.year + val month = currentDateTime.monthNumber.asFormattedWithZero() + val dayOfMonth = currentDateTime.dayOfMonth.asFormattedWithZero() + val hour = currentDateTime.hour.asFormattedWithZero() + val minutes = currentDateTime.minute.asFormattedWithZero() + val seconds = currentDateTime.second.asFormattedWithZero() + + return "$year$month$dayOfMonth$hour$minutes$seconds" +} + +/** + * Formats time values that have a single digit by prefixing them with an extra zero + * e.g "1:00" becomes "01:00" + */ +internal fun Int.asFormattedWithZero(): Comparable<*> = when (this < 10) { + true -> "0$this" + false -> this +} + +// Shortcode+Passkey+Timestamp +/** Generates a base 64 string by encoding a combination of [shortCode], [passkey] and [timestamp]*/ +internal fun getDarajaPassword(shortCode: String, passkey: String, timestamp: String): String { + val password = shortCode + passkey + timestamp + + return password.encodeBase64() +} + +/**Format phone number provided by user to format that Daraja API recognises ie. 254714023125 + * + * @return [phoneNumber] Formatted string to match a valid Safaricom/M-Pesa number + * @throws [DarajaException] + * */ +internal fun String.getDarajaPhoneNumber(): String { + val phoneNumber = this.replace("\\s".toRegex(), "") + + return when { + phoneNumber.matches(Regex("^(?:254)?[17](?:\\d\\d|0[0-8]|(9[0-2]))\\d{6}\$")) -> phoneNumber + phoneNumber.matches(Regex("^0?[17](?:\\d\\d|0[0-8]|(9[0-2]))\\d{6}\$")) -> + phoneNumber.replaceFirst("0", "254") + phoneNumber.matches(Regex("^(?:\\+254)?[17](?:\\d\\d|0[0-8]|(9[0-2]))\\d{6}\$")) -> + phoneNumber.replaceFirst("+", "") + else -> throw DarajaException("Invalid phone number format provided: $this") + } +} From 68a2a7d3b98b07d0231b548b65a6c55c4f05a20d Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Wed, 13 Sep 2023 21:48:38 +0300 Subject: [PATCH 14/73] Updated daraja result tests --- .../vickbt/darajakmp/network/DarajaApiService.kt | 3 ++- .../darajakmp/network/DarajaApiServiceTest.kt | 1 - .../vickbt/darajakmp/utils/DarajaResultTest.kt | 16 +++------------- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt index 2ca5fced..7a9f235b 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt @@ -50,7 +50,8 @@ internal class DarajaApiService constructor( .expireAfterWrite(3600.toDuration(DurationUnit.SECONDS)).build() ) { - /**Initiate API call using the [httpClient] provided by Ktor to fetch Daraja API access token + /** ToDo: Handle daraja auth errors correctly + * Initiate API call using the [httpClient] provided by Ktor to fetch Daraja API access token * of type [DarajaToken]*/ internal suspend fun fetchAccessToken(): DarajaResult = darajaSafeApiCall { val key = "$consumerKey:$consumerSecret" diff --git a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt index a64b783b..44d29356 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt @@ -40,7 +40,6 @@ import kotlin.test.assertNull import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest -@OptIn(ExperimentalCoroutinesApi::class) class DarajaApiServiceTest { private val mockDarajaHttpClient = MockDarajaHttpClient() diff --git a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/DarajaResultTest.kt b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/DarajaResultTest.kt index 7a34fde7..8dee2a39 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/DarajaResultTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/DarajaResultTest.kt @@ -19,6 +19,7 @@ package com.vickbt.darajakmp.utils import com.vickbt.darajakmp.network.models.DarajaException import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertNotNull import kotlin.test.assertNull @@ -62,15 +63,6 @@ class DarajaResultTest { assertEquals(expected = "Success", actual = result) } - /*@Test - fun darajaResult_getOrThrow_returns_exception_on_error() { - val result = DarajaResult.Failure(DarajaException()).getOrThrow() - - assertFailsWith { - result - } - }*/ - @Test fun darajaResult_onSuccess_returns_data_on_success() { val result = DarajaResult.Success(data = "Success") @@ -86,7 +78,7 @@ class DarajaResultTest { val result = DarajaResult.Failure(darajaException) result.onSuccess { - assertNull(it) // ToDo: Unreachable code + assertNull(it) } } @@ -125,9 +117,7 @@ class DarajaResultTest { fun darajaResult_onSuccess_onFailure_on_error() { val result = DarajaResult.Failure(darajaException) - result.onSuccess { - assertNull(it) // ToDo: Unreachable code - }.onFailure { + result.onFailure { assertNotNull(it) assertEquals(expected = it, actual = darajaException) } From 4c96213a5b800f2706ddd4c89c7a8cd796651a4c Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 14 Sep 2023 20:46:34 +0300 Subject: [PATCH 15/73] Removed @Throws exception in daraja interface --- daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index a3eb001e..b3ff3c19 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -124,7 +124,6 @@ class Daraja constructor( * @throws DarajaException * @return [DarajaToken] * */ - @Throws(DarajaException::class) @ObjCName(swiftName = "authorization") fun authorization(): DarajaResult = runBlocking { withContext(ioCoroutineContext) { @@ -146,7 +145,6 @@ class Daraja constructor( * @throws DarajaException * @return [DarajaPaymentResponse] * */ - @Throws(DarajaException::class) @ObjCName(swiftName = "mpesaExpress") fun mpesaExpress( businessShortCode: String, @@ -192,7 +190,6 @@ class Daraja constructor( * @throws DarajaException * @return [DarajaTransactionResponse] * */ - @Throws(DarajaException::class) @ObjCName(swiftName = "transactionStatus") fun transactionStatus( businessShortCode: String, From 698e3bfe42b6ef79a507e513d503e763fccee8f9 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 14 Sep 2023 20:57:05 +0300 Subject: [PATCH 16/73] Removed fked up tests --- .../darajakmp/network/DarajaApiServiceTest.kt | 69 ------------------- 1 file changed, 69 deletions(-) diff --git a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt index 44d29356..617c01b6 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt @@ -121,29 +121,6 @@ class DarajaApiServiceTest { assertEquals(expected = darajaToken, actual = cachedToken) } - @Test - fun fetchAccessToken_400_failure_are_caught() = runTest { - // given - mockDarajaHttpClient.throwError( - httpStatus = HttpStatusCode.BadRequest, - response = AccessToken400JSON - ) - - // when - then - val exception = assertFailsWith { - darajaApiService.fetchAccessToken() - } - - assertEquals( - expected = exception, - actual = DarajaException( - requestId = "43301-58413611-1", - errorCode = "400.008.01", - errorMessage = "Invalid Authentication passed" - ) - ) - } - @Test fun initiateMpesaExpress_success_returns_darajaPaymentResponse() = runTest { assertNull(mockInMemoryCache.get(1)) @@ -166,52 +143,6 @@ class DarajaApiServiceTest { assertNotNull(mockInMemoryCache.get(1)) } - @Test - fun initiateMpesaExpress_500_failure_are_caught() = runTest { - // given - mockDarajaHttpClient.throwError( - httpStatus = HttpStatusCode.InternalServerError, - response = MpesaExpress500JSON - ) - - // when - then - val exception = assertFailsWith { - darajaApiService.queryTransaction(queryDarajaTransactionRequest = queryDarajaTransactionRequest) - } - - assertEquals( - expected = exception, - actual = DarajaException( - requestId = "119414-258858845-1", - errorCode = "500.001.1001", - errorMessage = "Unable to lock subscriber, a transaction is already in process for the current subscriber" - ) - ) - } - - @Test - fun initiateMpesaExpress_404_failure_are_caught() = runTest { - // given - mockDarajaHttpClient.throwError( - httpStatus = HttpStatusCode.Unauthorized, - response = InvalidAccessTokenJSON - ) - - // when - then - val exception = assertFailsWith { - darajaApiService.queryTransaction(queryDarajaTransactionRequest = queryDarajaTransactionRequest) - } - - assertEquals( - expected = exception, - actual = DarajaException( - requestId = "16813-15-1", - errorCode = "404.001.04", - errorMessage = "Invalid Access Token" - ) - ) - } - @Test fun queryTransaction_success_returns_darajaTransactionResponse() = runTest { // when From d9d1d1b6a28d711317f9f513b6abc2aa1a26b370 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 14 Sep 2023 22:01:44 +0300 Subject: [PATCH 17/73] Update model classes naming --- .../ui/screens/home/HomeViewModel.kt | 4 ++-- .../kotlin/com/vickbt/darajakmp/Daraja.kt | 18 +++++++-------- .../darajakmp/network/DarajaApiService.kt | 14 +++++------ .../network/models/C2BRequestBody.kt | 8 +++++++ ...Request.kt => DarajaTransactionRequest.kt} | 4 ++-- ...ymentRequest.kt => MpesaExpressRequest.kt} | 4 ++-- ...entResponse.kt => MpesaExpressResponse.kt} | 4 ++-- .../darajakmp/network/DarajaApiServiceTest.kt | 23 +++++++------------ 8 files changed, 40 insertions(+), 39 deletions(-) create mode 100644 daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequestBody.kt rename daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/{QueryDarajaTransactionRequest.kt => DarajaTransactionRequest.kt} (91%) rename daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/{DarajaPaymentRequest.kt => MpesaExpressRequest.kt} (97%) rename daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/{DarajaPaymentResponse.kt => MpesaExpressResponse.kt} (95%) diff --git a/app-android/src/main/java/com/vickbt/app_android/ui/screens/home/HomeViewModel.kt b/app-android/src/main/java/com/vickbt/app_android/ui/screens/home/HomeViewModel.kt index 01a4ecfa..59bf955b 100644 --- a/app-android/src/main/java/com/vickbt/app_android/ui/screens/home/HomeViewModel.kt +++ b/app-android/src/main/java/com/vickbt/app_android/ui/screens/home/HomeViewModel.kt @@ -19,7 +19,7 @@ package com.vickbt.app_android.ui.screens.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.vickbt.darajakmp.Daraja -import com.vickbt.darajakmp.network.models.DarajaPaymentResponse +import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.utils.DarajaResult import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -27,7 +27,7 @@ import kotlinx.coroutines.launch class HomeViewModel constructor(private val daraja: Daraja) : ViewModel() { - private val _mpesaResponse = MutableStateFlow?>(null) + private val _mpesaResponse = MutableStateFlow?>(null) val mpesaResponse get() = _mpesaResponse.asStateFlow() fun initiateMpesaPayment( diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index b3ff3c19..3b06fe4c 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -19,11 +19,11 @@ package com.vickbt.darajakmp import com.vickbt.darajakmp.network.DarajaApiService import com.vickbt.darajakmp.network.DarajaHttpClientFactory import com.vickbt.darajakmp.network.models.DarajaException -import com.vickbt.darajakmp.network.models.DarajaPaymentRequest -import com.vickbt.darajakmp.network.models.DarajaPaymentResponse +import com.vickbt.darajakmp.network.models.MpesaExpressRequest +import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.network.models.DarajaToken import com.vickbt.darajakmp.network.models.DarajaTransactionResponse -import com.vickbt.darajakmp.network.models.QueryDarajaTransactionRequest +import com.vickbt.darajakmp.network.models.DarajaTransactionRequest import com.vickbt.darajakmp.utils.DarajaEnvironment import com.vickbt.darajakmp.utils.DarajaResult import com.vickbt.darajakmp.utils.DarajaTransactionType @@ -143,7 +143,7 @@ class Daraja constructor( * @param [accountReference] This is an alpha-numeric parameter that is defined by your system as an Identifier of the transaction for CustomerPayBillOnline transaction type. * * @throws DarajaException - * @return [DarajaPaymentResponse] + * @return [MpesaExpressResponse] * */ @ObjCName(swiftName = "mpesaExpress") fun mpesaExpress( @@ -154,7 +154,7 @@ class Daraja constructor( transactionDesc: String, callbackUrl: String, accountReference: String? = null - ): DarajaResult = runBlocking { + ): DarajaResult = runBlocking { val timestamp = Clock.System.now().getDarajaTimestamp() val darajaPassword = getDarajaPassword( @@ -163,7 +163,7 @@ class Daraja constructor( timestamp = timestamp ) - val darajaPaymentRequest = DarajaPaymentRequest( + val mpesaExpressRequest = MpesaExpressRequest( businessShortCode = businessShortCode, password = darajaPassword, timestamp = timestamp, @@ -178,7 +178,7 @@ class Daraja constructor( ) withContext(ioCoroutineContext) { - return@withContext darajaApiService.initiateMpesaStk(darajaPaymentRequest = darajaPaymentRequest) + return@withContext darajaApiService.initiateMpesaStk(mpesaExpressRequest = mpesaExpressRequest) } } @@ -202,7 +202,7 @@ class Daraja constructor( timestamp = timestamp ) - val queryDarajaTransactionRequest = QueryDarajaTransactionRequest( + val darajaTransactionRequest = DarajaTransactionRequest( businessShortCode = businessShortCode, password = darajaPassword, timestamp = timestamp, @@ -210,7 +210,7 @@ class Daraja constructor( ) withContext(ioCoroutineContext) { - return@withContext darajaApiService.queryTransaction(queryDarajaTransactionRequest) + return@withContext darajaApiService.queryTransaction(darajaTransactionRequest) } } } diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt index 7a9f235b..7da577bc 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt @@ -16,11 +16,11 @@ package com.vickbt.darajakmp.network -import com.vickbt.darajakmp.network.models.DarajaPaymentRequest -import com.vickbt.darajakmp.network.models.DarajaPaymentResponse +import com.vickbt.darajakmp.network.models.MpesaExpressRequest +import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.network.models.DarajaToken import com.vickbt.darajakmp.network.models.DarajaTransactionResponse -import com.vickbt.darajakmp.network.models.QueryDarajaTransactionRequest +import com.vickbt.darajakmp.network.models.DarajaTransactionRequest import com.vickbt.darajakmp.utils.DarajaEndpoints import com.vickbt.darajakmp.utils.DarajaResult import com.vickbt.darajakmp.utils.getOrThrow @@ -69,7 +69,7 @@ internal class DarajaApiService constructor( } /**Initiate API call using the [httpClient] provided by Ktor to trigger Mpesa Express payment on Daraja API */ - internal suspend fun initiateMpesaStk(darajaPaymentRequest: DarajaPaymentRequest): DarajaResult = + internal suspend fun initiateMpesaStk(mpesaExpressRequest: MpesaExpressRequest): DarajaResult = darajaSafeApiCall { val accessToken = inMemoryCache.get(1) { fetchAccessToken().getOrThrow() @@ -77,19 +77,19 @@ internal class DarajaApiService constructor( return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.INITIATE_MPESA_EXPRESS) { headers { append(HttpHeaders.Authorization, "Bearer ${accessToken.accessToken}") } - setBody(darajaPaymentRequest) + setBody(mpesaExpressRequest) }.body() } /**Initiate API call using the [httpClient] provided by Ktor to query the status of an Mpesa Express payment transaction*/ - internal suspend fun queryTransaction(queryDarajaTransactionRequest: QueryDarajaTransactionRequest): DarajaResult = + internal suspend fun queryTransaction(darajaTransactionRequest: DarajaTransactionRequest): DarajaResult = darajaSafeApiCall { val key = "$consumerKey:$consumerSecret" val base64EncodedKey = key.encodeBase64() return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.QUERY_MPESA_TRANSACTION) { headers { append(HttpHeaders.Authorization, "Basic $base64EncodedKey") } - setBody(queryDarajaTransactionRequest) + setBody(darajaTransactionRequest) }.body() } } diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequestBody.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequestBody.kt new file mode 100644 index 00000000..c51be350 --- /dev/null +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequestBody.kt @@ -0,0 +1,8 @@ +package com.vickbt.darajakmp.network.models + +data class C2BRequestBody( + val ConfirmationURL: String, + val ResponseType: String, + val ShortCode: Int, + val ValidationURL: String +) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryDarajaTransactionRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaTransactionRequest.kt similarity index 91% rename from daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryDarajaTransactionRequest.kt rename to daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaTransactionRequest.kt index 419d5182..16966821 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryDarajaTransactionRequest.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaTransactionRequest.kt @@ -20,9 +20,9 @@ import kotlin.native.ObjCName import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -@ObjCName(swiftName = "QueryDarajaTransactionRequest") +@ObjCName(swiftName = "DarajaTransactionRequest") @Serializable -data class QueryDarajaTransactionRequest( +data class DarajaTransactionRequest( @SerialName("BusinessShortCode") val businessShortCode: String, diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaPaymentRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressRequest.kt similarity index 97% rename from daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaPaymentRequest.kt rename to daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressRequest.kt index af06e7f0..b8501d3c 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaPaymentRequest.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressRequest.kt @@ -20,12 +20,12 @@ import kotlin.native.ObjCName import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -@ObjCName(swiftName = "DarajaPaymentRequest") +@ObjCName(swiftName = "MpesaExpressRequest") @Serializable /** * Request body sent to Daraja API to request Mpesa Express payment. * */ -data class DarajaPaymentRequest( +data class MpesaExpressRequest( /**This is organizations shortcode (Paybill or Buygoods - A 5 to 7 digit account number) used to identify an organization and receive the transaction.*/ @SerialName("BusinessShortCode") diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaPaymentResponse.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressResponse.kt similarity index 95% rename from daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaPaymentResponse.kt rename to daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressResponse.kt index af2326a0..a02f45ab 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaPaymentResponse.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressResponse.kt @@ -20,12 +20,12 @@ import kotlin.native.ObjCName import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -@ObjCName(swiftName = "DarajaPaymentResponse") +@ObjCName(swiftName = "MpesaExpressResponse") @Serializable /** * Response returned by Daraja API on successful Mpesa Express payment initiation. * */ -data class DarajaPaymentResponse( +data class MpesaExpressResponse( /**This is a global unique Identifier for any submitted payment request.*/ @SerialName("MerchantRequestID") var merchantRequestID: String, diff --git a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt index 617c01b6..9551173e 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt @@ -16,28 +16,21 @@ package com.vickbt.darajakmp.network -import com.vickbt.darajakmp.network.models.AccessToken400JSON -import com.vickbt.darajakmp.network.models.DarajaException -import com.vickbt.darajakmp.network.models.DarajaPaymentRequest -import com.vickbt.darajakmp.network.models.DarajaPaymentResponse +import com.vickbt.darajakmp.network.models.MpesaExpressRequest +import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.network.models.DarajaToken import com.vickbt.darajakmp.network.models.DarajaTransactionResponse -import com.vickbt.darajakmp.network.models.InvalidAccessTokenJSON -import com.vickbt.darajakmp.network.models.MpesaExpress500JSON -import com.vickbt.darajakmp.network.models.QueryDarajaTransactionRequest +import com.vickbt.darajakmp.network.models.DarajaTransactionRequest import com.vickbt.darajakmp.utils.DarajaResult import com.vickbt.darajakmp.utils.DarajaTransactionType import io.github.reactivecircus.cache4k.Cache import io.ktor.client.HttpClient -import io.ktor.http.HttpStatusCode import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertFailsWith import kotlin.test.assertNotNull import kotlin.test.assertNull -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest class DarajaApiServiceTest { @@ -54,7 +47,7 @@ class DarajaApiServiceTest { accessToken = "wWAHdtiE4GCSGv2ocfzQ0WHefwAJ", expiresIn = "3599" ) - private val darajaPaymentRequest = DarajaPaymentRequest( + private val mpesaExpressRequest = MpesaExpressRequest( businessShortCode = "654321", password = "password", phoneNumber = "254708374149", @@ -68,7 +61,7 @@ class DarajaApiServiceTest { accountReference = "Account reference" ) - private val queryDarajaTransactionRequest = QueryDarajaTransactionRequest( + private val darajaTransactionRequest = DarajaTransactionRequest( businessShortCode = "654321", password = "password", timestamp = "timestamp", @@ -127,9 +120,9 @@ class DarajaApiServiceTest { // when val actualResult = - darajaApiService.initiateMpesaStk(darajaPaymentRequest = darajaPaymentRequest) + darajaApiService.initiateMpesaStk(mpesaExpressRequest = mpesaExpressRequest) val expectedResult = DarajaResult.Success( - DarajaPaymentResponse( + MpesaExpressResponse( merchantRequestID = "6093-85819535-1", checkoutRequestID = "ws_CO_16122022001707470708374149", responseCode = "0", @@ -147,7 +140,7 @@ class DarajaApiServiceTest { fun queryTransaction_success_returns_darajaTransactionResponse() = runTest { // when val actualResult = - darajaApiService.queryTransaction(queryDarajaTransactionRequest = queryDarajaTransactionRequest) + darajaApiService.queryTransaction(darajaTransactionRequest = darajaTransactionRequest) val expectedResult = DarajaResult.Success( DarajaTransactionResponse( responseCode = "0", From 9cc9ab7bb76312b51ead4807e818ebe5adbf88c3 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 14 Sep 2023 22:10:35 +0300 Subject: [PATCH 18/73] Delete unused data class --- .../com/vickbt/darajakmp/network/models/C2BRequestBody.kt | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequestBody.kt diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequestBody.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequestBody.kt deleted file mode 100644 index c51be350..00000000 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequestBody.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.vickbt.darajakmp.network.models - -data class C2BRequestBody( - val ConfirmationURL: String, - val ResponseType: String, - val ShortCode: Int, - val ValidationURL: String -) From d98c506f246c5ce7db02180eed658edd139d80c5 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 14 Sep 2023 23:12:07 +0300 Subject: [PATCH 19/73] Renamed initiateMpesaStk to initiateMpesaExpress --- .../kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt index 9551173e..ab026298 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt @@ -120,7 +120,7 @@ class DarajaApiServiceTest { // when val actualResult = - darajaApiService.initiateMpesaStk(mpesaExpressRequest = mpesaExpressRequest) + darajaApiService.initiateMpesaExpress(mpesaExpressRequest = mpesaExpressRequest) val expectedResult = DarajaResult.Success( MpesaExpressResponse( merchantRequestID = "6093-85819535-1", From ab6f592203fa0afcaba7239503a3d4b0f51e8e46 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 14 Sep 2023 23:12:25 +0300 Subject: [PATCH 20/73] Added C2BResponseType enum --- .../kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt index 65815bba..da5a3823 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt @@ -23,6 +23,7 @@ internal object DarajaEndpoints { const val REQUEST_ACCESS_TOKEN = "oauth/v1/generate?grant_type=client_credentials" const val INITIATE_MPESA_EXPRESS = "mpesa/stkpush/v1/processrequest" const val QUERY_MPESA_TRANSACTION = "mpesa/stkpushquery/v1/query" + const val INITIATE_C2B = "mpesa/c2b/v1/registerurl" } enum class DarajaTransactionType { @@ -32,3 +33,7 @@ enum class DarajaTransactionType { enum class DarajaEnvironment { PRODUCTION_ENVIRONMENT, SANDBOX_ENVIRONMENT } + +enum class C2BResponseType(name: String) { + CANCELED("canceled"), COMPLETED("completed") +} From c37c7a8dfaf3447d48444904a039fe7bd67a9023 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 14 Sep 2023 23:12:30 +0300 Subject: [PATCH 21/73] Create C2BResponse.kt --- .../darajakmp/network/models/C2BResponse.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BResponse.kt diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BResponse.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BResponse.kt new file mode 100644 index 00000000..41271a33 --- /dev/null +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BResponse.kt @@ -0,0 +1,22 @@ +package com.vickbt.darajakmp.network.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlin.native.ObjCName + +@ObjCName(swiftName = "C2BResponse") +@Serializable +/***/ +data class C2BResponse( + /**This is a global unique identifier for the transaction request returned by the API proxy upon successful request submission.*/ + @SerialName("OriginatorCoversationID") + val originatorCoversationId: String, + + /**It indicates whether Mobile Money accepts the request or not.*/ + @SerialName("ResponseCode") + val responseCode: String, + + /**This is the status of the request.*/ + @SerialName("ResponseDescription") + val responseDescription: String +) From 452d59b5c0ebf288fcf2353a4e1914255302a180 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 14 Sep 2023 23:12:34 +0300 Subject: [PATCH 22/73] Create C2BRequest.kt --- .../darajakmp/network/models/C2BRequest.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt new file mode 100644 index 00000000..878fb9fd --- /dev/null +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt @@ -0,0 +1,26 @@ +package com.vickbt.darajakmp.network.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlin.native.ObjCName + +@ObjCName(swiftName = "MpesaExpressRequest") +@Serializable +/***/ +data class C2BRequest( + /**This is the URL that receives the confirmation request from API upon payment completion.*/ + @SerialName("ConfirmationURL") + val confirmationURL: String, + + /**This is the URL that receives the validation request from the API upon payment submission. The validation URL is only called if the external validation on the registered shortcode is enabled. (By default External Validation is disabled).*/ + @SerialName("ValidationURL") + val validationURL: String, + + /**This parameter specifies what is to happen if for any reason the validation URL is not reachable. Note that, this is the default action value that determines what M-PESA will do in the scenario that your endpoint is unreachable or is unable to respond on time. Only two values are allowed: Completed or Cancelled. Completed means M-PESA will automatically complete your transaction, whereas Cancelled means M-PESA will automatically cancel the transaction, in the event M-PESA is unable to reach your Validation URL.*/ + @SerialName("ResponseType") + val responseType: String, + + /**A unique number is tagged to an M-PESA pay bill/till number of the organization.*/ + @SerialName("ShortCode") + val shortCode: Int, +) From d9f83349daf39dad0c8ed2da9b1e63d098038066 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 14 Sep 2023 23:12:55 +0300 Subject: [PATCH 23/73] Added initiateC2B api call --- .../darajakmp/network/DarajaApiService.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt index 7da577bc..649982e3 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt @@ -16,6 +16,8 @@ package com.vickbt.darajakmp.network +import com.vickbt.darajakmp.network.models.C2BRequest +import com.vickbt.darajakmp.network.models.C2BResponse import com.vickbt.darajakmp.network.models.MpesaExpressRequest import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.network.models.DarajaToken @@ -69,7 +71,7 @@ internal class DarajaApiService constructor( } /**Initiate API call using the [httpClient] provided by Ktor to trigger Mpesa Express payment on Daraja API */ - internal suspend fun initiateMpesaStk(mpesaExpressRequest: MpesaExpressRequest): DarajaResult = + internal suspend fun initiateMpesaExpress(mpesaExpressRequest: MpesaExpressRequest): DarajaResult = darajaSafeApiCall { val accessToken = inMemoryCache.get(1) { fetchAccessToken().getOrThrow() @@ -92,4 +94,18 @@ internal class DarajaApiService constructor( setBody(darajaTransactionRequest) }.body() } + + internal suspend fun initiateC2B(c2BRequest: C2BRequest):DarajaResult = + darajaSafeApiCall { + val accessToken = inMemoryCache.get(1) { + fetchAccessToken().getOrThrow() + } + + return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.INITIATE_C2B){ + headers { append(HttpHeaders.Authorization, "Bearer ${accessToken.accessToken}") } + setBody(c2BRequest) + }.body() + } + } + From 5e2a5be872ddbb3e960c75f48bbc5fa1102bada3 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 14 Sep 2023 23:14:47 +0300 Subject: [PATCH 24/73] Added c2b to daraja interface --- .../kotlin/com/vickbt/darajakmp/Daraja.kt | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index 3b06fe4c..4ecf0bc3 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -18,12 +18,15 @@ package com.vickbt.darajakmp import com.vickbt.darajakmp.network.DarajaApiService import com.vickbt.darajakmp.network.DarajaHttpClientFactory +import com.vickbt.darajakmp.network.models.C2BRequest +import com.vickbt.darajakmp.network.models.C2BResponse import com.vickbt.darajakmp.network.models.DarajaException import com.vickbt.darajakmp.network.models.MpesaExpressRequest import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.network.models.DarajaToken import com.vickbt.darajakmp.network.models.DarajaTransactionResponse import com.vickbt.darajakmp.network.models.DarajaTransactionRequest +import com.vickbt.darajakmp.utils.C2BResponseType import com.vickbt.darajakmp.utils.DarajaEnvironment import com.vickbt.darajakmp.utils.DarajaResult import com.vickbt.darajakmp.utils.DarajaTransactionType @@ -178,7 +181,7 @@ class Daraja constructor( ) withContext(ioCoroutineContext) { - return@withContext darajaApiService.initiateMpesaStk(mpesaExpressRequest = mpesaExpressRequest) + return@withContext darajaApiService.initiateMpesaExpress(mpesaExpressRequest = mpesaExpressRequest) } } @@ -213,4 +216,31 @@ class Daraja constructor( return@withContext darajaApiService.queryTransaction(darajaTransactionRequest) } } + + /**Transact between a phone number registered on M-Pesa to an M-Pesa shortcode + * + * @param [shortCode] A unique number is tagged to an M-PESA pay bill/till number of the organization. + * @param [confirmationURL] This is the URL that receives the confirmation request from API upon payment completion. + * @param [validationURL] This is the URL that receives the validation request from the API upon payment submission. The validation URL is only called if the external validation on the registered shortcode is enabled. (By default External Validation is disabled). + * @param [responseType] This parameter specifies what is to happen if for any reason the validation URL is not reachable. Note that, this is the default action value that determines what M-PESA will do in the scenario that your endpoint is unreachable or is unable to respond on time. Only two values are allowed: Completed or Cancelled. Completed means M-PESA will automatically complete your transaction, whereas Cancelled means M-PESA will automatically cancel the transaction, in the event M-PESA is unable to reach your Validation URL. + * + * @return [C2BResponse] + * */ + fun c2b( + shortCode: Int, + confirmationURL: String, + validationURL: String, + responseType: C2BResponseType = C2BResponseType.COMPLETED + ): DarajaResult = runBlocking { + val c2bRequest = C2BRequest( + confirmationURL = confirmationURL, + validationURL = validationURL, + responseType = responseType.name, + shortCode = shortCode + ) + + withContext(ioCoroutineContext) { + return@withContext darajaApiService.initiateC2B(c2BRequest = c2bRequest) + } + } } From e61e8e18eb2bc6a09af1b3a0f87c8d8d6a0d4c3d Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 14 Sep 2023 23:19:34 +0300 Subject: [PATCH 25/73] Update C2BRequest --- .../commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt | 11 ++++------- .../com/vickbt/darajakmp/network/models/C2BRequest.kt | 3 ++- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index 4ecf0bc3..7ce70c97 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -124,7 +124,6 @@ class Daraja constructor( /**Request access token that is used to authenticate to Daraja APIs * - * @throws DarajaException * @return [DarajaToken] * */ @ObjCName(swiftName = "authorization") @@ -145,7 +144,6 @@ class Daraja constructor( * @param [callbackUrl] This is a valid secure URL that is used to receive notifications from M-Pesa API. It is the endpoint to which the results will be sent by M-Pesa API. * @param [accountReference] This is an alpha-numeric parameter that is defined by your system as an Identifier of the transaction for CustomerPayBillOnline transaction type. * - * @throws DarajaException * @return [MpesaExpressResponse] * */ @ObjCName(swiftName = "mpesaExpress") @@ -190,7 +188,6 @@ class Daraja constructor( * @param [businessShortCode] This is organizations shortcode (Paybill or Buygoods - A 5 to 7 digit account number) used to identify an organization and receive the transaction. * @param [checkoutRequestID] This is a global unique identifier of the processed checkout transaction request. * - * @throws DarajaException * @return [DarajaTransactionResponse] * */ @ObjCName(swiftName = "transactionStatus") @@ -229,13 +226,13 @@ class Daraja constructor( fun c2b( shortCode: Int, confirmationURL: String, - validationURL: String, - responseType: C2BResponseType = C2BResponseType.COMPLETED + validationURL: String?, + responseType: C2BResponseType? = C2BResponseType.COMPLETED ): DarajaResult = runBlocking { val c2bRequest = C2BRequest( confirmationURL = confirmationURL, - validationURL = validationURL, - responseType = responseType.name, + validationURL = validationURL ?: "", + responseType = responseType?.name, shortCode = shortCode ) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt index 878fb9fd..b9cd2dc7 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt @@ -1,5 +1,6 @@ package com.vickbt.darajakmp.network.models +import com.vickbt.darajakmp.utils.C2BResponseType import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlin.native.ObjCName @@ -18,7 +19,7 @@ data class C2BRequest( /**This parameter specifies what is to happen if for any reason the validation URL is not reachable. Note that, this is the default action value that determines what M-PESA will do in the scenario that your endpoint is unreachable or is unable to respond on time. Only two values are allowed: Completed or Cancelled. Completed means M-PESA will automatically complete your transaction, whereas Cancelled means M-PESA will automatically cancel the transaction, in the event M-PESA is unable to reach your Validation URL.*/ @SerialName("ResponseType") - val responseType: String, + val responseType: String? = C2BResponseType.COMPLETED.name, /**A unique number is tagged to an M-PESA pay bill/till number of the organization.*/ @SerialName("ShortCode") From 9b4fbca3dd2dd40822ff6fe80c839267341cbd4e Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 15 Sep 2023 17:47:38 +0300 Subject: [PATCH 26/73] Update with correct c2b urls and func names --- .../kotlin/com/vickbt/darajakmp/Daraja.kt | 21 +++++++-------- .../darajakmp/network/DarajaApiService.kt | 12 +++++---- .../network/models/C2BRegistrationRequest.kt | 27 +++++++++++++++++++ .../network/models/C2BRegistrationResponse.kt | 22 +++++++++++++++ .../vickbt/darajakmp/utils/DarajaConfigs.kt | 3 ++- 5 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt create mode 100644 daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationResponse.kt diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index 7ce70c97..ca86f3c1 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -18,9 +18,8 @@ package com.vickbt.darajakmp import com.vickbt.darajakmp.network.DarajaApiService import com.vickbt.darajakmp.network.DarajaHttpClientFactory -import com.vickbt.darajakmp.network.models.C2BRequest -import com.vickbt.darajakmp.network.models.C2BResponse -import com.vickbt.darajakmp.network.models.DarajaException +import com.vickbt.darajakmp.network.models.C2BRegistrationRequest +import com.vickbt.darajakmp.network.models.C2BRegistrationResponse import com.vickbt.darajakmp.network.models.MpesaExpressRequest import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.network.models.DarajaToken @@ -216,28 +215,28 @@ class Daraja constructor( /**Transact between a phone number registered on M-Pesa to an M-Pesa shortcode * - * @param [shortCode] A unique number is tagged to an M-PESA pay bill/till number of the organization. + * @param [businessShortCode] A unique number is tagged to an M-PESA pay bill/till number of the organization. * @param [confirmationURL] This is the URL that receives the confirmation request from API upon payment completion. * @param [validationURL] This is the URL that receives the validation request from the API upon payment submission. The validation URL is only called if the external validation on the registered shortcode is enabled. (By default External Validation is disabled). * @param [responseType] This parameter specifies what is to happen if for any reason the validation URL is not reachable. Note that, this is the default action value that determines what M-PESA will do in the scenario that your endpoint is unreachable or is unable to respond on time. Only two values are allowed: Completed or Cancelled. Completed means M-PESA will automatically complete your transaction, whereas Cancelled means M-PESA will automatically cancel the transaction, in the event M-PESA is unable to reach your Validation URL. * - * @return [C2BResponse] + * @return [C2BRegistrationResponse] * */ - fun c2b( - shortCode: Int, + fun c2bRegistration( + businessShortCode: Int, confirmationURL: String, validationURL: String?, responseType: C2BResponseType? = C2BResponseType.COMPLETED - ): DarajaResult = runBlocking { - val c2bRequest = C2BRequest( + ): DarajaResult = runBlocking { + val c2BRegistrationRequest = C2BRegistrationRequest( confirmationURL = confirmationURL, validationURL = validationURL ?: "", responseType = responseType?.name, - shortCode = shortCode + shortCode = businessShortCode ) withContext(ioCoroutineContext) { - return@withContext darajaApiService.initiateC2B(c2BRequest = c2bRequest) + return@withContext darajaApiService.c2bRegistration(c2BRegistrationRequest = c2BRegistrationRequest) } } } diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt index 649982e3..d7bcb935 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt @@ -16,8 +16,8 @@ package com.vickbt.darajakmp.network -import com.vickbt.darajakmp.network.models.C2BRequest -import com.vickbt.darajakmp.network.models.C2BResponse +import com.vickbt.darajakmp.network.models.C2BRegistrationRequest +import com.vickbt.darajakmp.network.models.C2BRegistrationResponse import com.vickbt.darajakmp.network.models.MpesaExpressRequest import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.network.models.DarajaToken @@ -95,17 +95,19 @@ internal class DarajaApiService constructor( }.body() } - internal suspend fun initiateC2B(c2BRequest: C2BRequest):DarajaResult = + internal suspend fun c2bRegistration(c2BRegistrationRequest: C2BRegistrationRequest):DarajaResult = darajaSafeApiCall { val accessToken = inMemoryCache.get(1) { fetchAccessToken().getOrThrow() } - return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.INITIATE_C2B){ + return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.C2B_REGISTRATION_URL){ headers { append(HttpHeaders.Authorization, "Bearer ${accessToken.accessToken}") } - setBody(c2BRequest) + setBody(c2BRegistrationRequest) }.body() } + internal suspend fun c2b(){} + } diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt new file mode 100644 index 00000000..1fb4d8ed --- /dev/null +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt @@ -0,0 +1,27 @@ +package com.vickbt.darajakmp.network.models + +import com.vickbt.darajakmp.utils.C2BResponseType +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlin.native.ObjCName + +@ObjCName(swiftName = "C2BRegistrationRequest") +@Serializable +/***/ +data class C2BRegistrationRequest( + /**This is the URL that receives the confirmation request from API upon payment completion.*/ + @SerialName("ConfirmationURL") + val confirmationURL: String, + + /**This is the URL that receives the validation request from the API upon payment submission. The validation URL is only called if the external validation on the registered shortcode is enabled. (By default External Validation is disabled).*/ + @SerialName("ValidationURL") + val validationURL: String, + + /**This parameter specifies what is to happen if for any reason the validation URL is not reachable. Note that, this is the default action value that determines what M-PESA will do in the scenario that your endpoint is unreachable or is unable to respond on time. Only two values are allowed: Completed or Cancelled. Completed means M-PESA will automatically complete your transaction, whereas Cancelled means M-PESA will automatically cancel the transaction, in the event M-PESA is unable to reach your Validation URL.*/ + @SerialName("ResponseType") + val responseType: String? = C2BResponseType.COMPLETED.name, + + /**A unique number is tagged to an M-PESA pay bill/till number of the organization.*/ + @SerialName("ShortCode") + val shortCode: Int, +) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationResponse.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationResponse.kt new file mode 100644 index 00000000..f93e70c1 --- /dev/null +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationResponse.kt @@ -0,0 +1,22 @@ +package com.vickbt.darajakmp.network.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlin.native.ObjCName + +@ObjCName(swiftName = "C2BRegistrationResponse") +@Serializable +/***/ +data class C2BRegistrationResponse( + /**This is a global unique identifier for the transaction request returned by the API proxy upon successful request submission.*/ + @SerialName("OriginatorCoversationID") + val originatorCoversationId: String, + + /**It indicates whether Mobile Money accepts the request or not.*/ + @SerialName("ResponseCode") + val responseCode: String, + + /**This is the status of the request.*/ + @SerialName("ResponseDescription") + val responseDescription: String +) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt index da5a3823..445c6cdb 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt @@ -23,7 +23,8 @@ internal object DarajaEndpoints { const val REQUEST_ACCESS_TOKEN = "oauth/v1/generate?grant_type=client_credentials" const val INITIATE_MPESA_EXPRESS = "mpesa/stkpush/v1/processrequest" const val QUERY_MPESA_TRANSACTION = "mpesa/stkpushquery/v1/query" - const val INITIATE_C2B = "mpesa/c2b/v1/registerurl" + const val C2B_REGISTRATION_URL = "mpesa/c2b/v1/registerurl" + const val INITIATE_C2B = "mpesa/c2b/v1/simulate" } enum class DarajaTransactionType { From 52af900839e8af221768f9ea6ba560d58fdd029f Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 15 Sep 2023 18:23:08 +0300 Subject: [PATCH 27/73] Added c2b query --- .../kotlin/com/vickbt/darajakmp/Daraja.kt | 39 ++++++++++++++----- .../darajakmp/network/DarajaApiService.kt | 27 +++++++++---- .../network/models/C2BRegistrationRequest.kt | 4 +- .../network/models/C2BRegistrationResponse.kt | 22 ----------- .../darajakmp/network/models/C2BRequest.kt | 28 +++++++------ .../models/DarajaTransactionRequest.kt | 2 +- .../network/models/MpesaExpressRequest.kt | 4 +- .../vickbt/darajakmp/utils/DarajaConfigs.kt | 2 + 8 files changed, 69 insertions(+), 59 deletions(-) delete mode 100644 daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationResponse.kt diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index ca86f3c1..f115abb5 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -19,12 +19,13 @@ package com.vickbt.darajakmp import com.vickbt.darajakmp.network.DarajaApiService import com.vickbt.darajakmp.network.DarajaHttpClientFactory import com.vickbt.darajakmp.network.models.C2BRegistrationRequest -import com.vickbt.darajakmp.network.models.C2BRegistrationResponse -import com.vickbt.darajakmp.network.models.MpesaExpressRequest -import com.vickbt.darajakmp.network.models.MpesaExpressResponse +import com.vickbt.darajakmp.network.models.C2BRequest +import com.vickbt.darajakmp.network.models.C2BResponse import com.vickbt.darajakmp.network.models.DarajaToken -import com.vickbt.darajakmp.network.models.DarajaTransactionResponse import com.vickbt.darajakmp.network.models.DarajaTransactionRequest +import com.vickbt.darajakmp.network.models.DarajaTransactionResponse +import com.vickbt.darajakmp.network.models.MpesaExpressRequest +import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.utils.C2BResponseType import com.vickbt.darajakmp.utils.DarajaEnvironment import com.vickbt.darajakmp.utils.DarajaResult @@ -220,23 +221,43 @@ class Daraja constructor( * @param [validationURL] This is the URL that receives the validation request from the API upon payment submission. The validation URL is only called if the external validation on the registered shortcode is enabled. (By default External Validation is disabled). * @param [responseType] This parameter specifies what is to happen if for any reason the validation URL is not reachable. Note that, this is the default action value that determines what M-PESA will do in the scenario that your endpoint is unreachable or is unable to respond on time. Only two values are allowed: Completed or Cancelled. Completed means M-PESA will automatically complete your transaction, whereas Cancelled means M-PESA will automatically cancel the transaction, in the event M-PESA is unable to reach your Validation URL. * - * @return [C2BRegistrationResponse] + * @return [C2BResponse] * */ fun c2bRegistration( businessShortCode: Int, confirmationURL: String, - validationURL: String?, + validationURL: String? = null, responseType: C2BResponseType? = C2BResponseType.COMPLETED - ): DarajaResult = runBlocking { + ): DarajaResult = runBlocking { val c2BRegistrationRequest = C2BRegistrationRequest( confirmationURL = confirmationURL, - validationURL = validationURL ?: "", + validationURL = validationURL, responseType = responseType?.name, shortCode = businessShortCode ) withContext(ioCoroutineContext) { - return@withContext darajaApiService.c2bRegistration(c2BRegistrationRequest = c2BRegistrationRequest) + return@withContext darajaApiService.c2bRegistration(c2bRegistrationRequest = c2BRegistrationRequest) + } + } + + fun c2b( + amount: Int, + billReferenceNumber: String, + commandID: DarajaTransactionType, + msisdn: String, + businessShortCode: String + ): DarajaResult = runBlocking { + val c2bRequest = C2BRequest( + amount = amount, + billReferenceNumber = billReferenceNumber, + commandID = commandID.name, + phoneNumber = msisdn.getDarajaPhoneNumber().toLong(), + shortCode = if (commandID.name == DarajaTransactionType.CustomerPayBillOnline.name) businessShortCode else null + ) + + withContext(ioCoroutineContext) { + return@withContext darajaApiService.c2b(c2bRequest = c2bRequest) } } } diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt index d7bcb935..4f64c587 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt @@ -17,12 +17,13 @@ package com.vickbt.darajakmp.network import com.vickbt.darajakmp.network.models.C2BRegistrationRequest -import com.vickbt.darajakmp.network.models.C2BRegistrationResponse -import com.vickbt.darajakmp.network.models.MpesaExpressRequest -import com.vickbt.darajakmp.network.models.MpesaExpressResponse +import com.vickbt.darajakmp.network.models.C2BRequest +import com.vickbt.darajakmp.network.models.C2BResponse import com.vickbt.darajakmp.network.models.DarajaToken -import com.vickbt.darajakmp.network.models.DarajaTransactionResponse import com.vickbt.darajakmp.network.models.DarajaTransactionRequest +import com.vickbt.darajakmp.network.models.DarajaTransactionResponse +import com.vickbt.darajakmp.network.models.MpesaExpressRequest +import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.utils.DarajaEndpoints import com.vickbt.darajakmp.utils.DarajaResult import com.vickbt.darajakmp.utils.getOrThrow @@ -95,19 +96,29 @@ internal class DarajaApiService constructor( }.body() } - internal suspend fun c2bRegistration(c2BRegistrationRequest: C2BRegistrationRequest):DarajaResult = + internal suspend fun c2bRegistration(c2bRegistrationRequest: C2BRegistrationRequest): DarajaResult = darajaSafeApiCall { val accessToken = inMemoryCache.get(1) { fetchAccessToken().getOrThrow() } - return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.C2B_REGISTRATION_URL){ + return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.C2B_REGISTRATION_URL) { headers { append(HttpHeaders.Authorization, "Bearer ${accessToken.accessToken}") } - setBody(c2BRegistrationRequest) + setBody(c2bRegistrationRequest) }.body() } - internal suspend fun c2b(){} + internal suspend fun c2b(c2bRequest: C2BRequest): DarajaResult = + darajaSafeApiCall { + val accessToken = inMemoryCache.get(1) { + fetchAccessToken().getOrThrow() + } + + return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.INITIATE_C2B) { + headers { append(HttpHeaders.Authorization, "Bearer ${accessToken.accessToken}") } + setBody(c2bRequest) + }.body() + } } diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt index 1fb4d8ed..249e47ad 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt @@ -8,14 +8,14 @@ import kotlin.native.ObjCName @ObjCName(swiftName = "C2BRegistrationRequest") @Serializable /***/ -data class C2BRegistrationRequest( +internal data class C2BRegistrationRequest( /**This is the URL that receives the confirmation request from API upon payment completion.*/ @SerialName("ConfirmationURL") val confirmationURL: String, /**This is the URL that receives the validation request from the API upon payment submission. The validation URL is only called if the external validation on the registered shortcode is enabled. (By default External Validation is disabled).*/ @SerialName("ValidationURL") - val validationURL: String, + val validationURL: String?, /**This parameter specifies what is to happen if for any reason the validation URL is not reachable. Note that, this is the default action value that determines what M-PESA will do in the scenario that your endpoint is unreachable or is unable to respond on time. Only two values are allowed: Completed or Cancelled. Completed means M-PESA will automatically complete your transaction, whereas Cancelled means M-PESA will automatically cancel the transaction, in the event M-PESA is unable to reach your Validation URL.*/ @SerialName("ResponseType") diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationResponse.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationResponse.kt deleted file mode 100644 index f93e70c1..00000000 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationResponse.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.vickbt.darajakmp.network.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlin.native.ObjCName - -@ObjCName(swiftName = "C2BRegistrationResponse") -@Serializable -/***/ -data class C2BRegistrationResponse( - /**This is a global unique identifier for the transaction request returned by the API proxy upon successful request submission.*/ - @SerialName("OriginatorCoversationID") - val originatorCoversationId: String, - - /**It indicates whether Mobile Money accepts the request or not.*/ - @SerialName("ResponseCode") - val responseCode: String, - - /**This is the status of the request.*/ - @SerialName("ResponseDescription") - val responseDescription: String -) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt index b9cd2dc7..2538cadb 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt @@ -1,27 +1,25 @@ package com.vickbt.darajakmp.network.models -import com.vickbt.darajakmp.utils.C2BResponseType import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlin.native.ObjCName -@ObjCName(swiftName = "MpesaExpressRequest") +@ObjCName(swiftName = "C2BRequest") @Serializable -/***/ -data class C2BRequest( - /**This is the URL that receives the confirmation request from API upon payment completion.*/ - @SerialName("ConfirmationURL") - val confirmationURL: String, +/**Request C2B M-Pesa payment*/ +internal data class C2BRequest( + @SerialName("Amount") + val amount: Int, - /**This is the URL that receives the validation request from the API upon payment submission. The validation URL is only called if the external validation on the registered shortcode is enabled. (By default External Validation is disabled).*/ - @SerialName("ValidationURL") - val validationURL: String, + @SerialName("BillRefNumber") + val billReferenceNumber: String, - /**This parameter specifies what is to happen if for any reason the validation URL is not reachable. Note that, this is the default action value that determines what M-PESA will do in the scenario that your endpoint is unreachable or is unable to respond on time. Only two values are allowed: Completed or Cancelled. Completed means M-PESA will automatically complete your transaction, whereas Cancelled means M-PESA will automatically cancel the transaction, in the event M-PESA is unable to reach your Validation URL.*/ - @SerialName("ResponseType") - val responseType: String? = C2BResponseType.COMPLETED.name, + @SerialName("CommandID") + val commandID: String, + + @SerialName("Msisdn") + val phoneNumber: Long, - /**A unique number is tagged to an M-PESA pay bill/till number of the organization.*/ @SerialName("ShortCode") - val shortCode: Int, + val shortCode: String? ) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaTransactionRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaTransactionRequest.kt index 16966821..cd58acfe 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaTransactionRequest.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaTransactionRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable @ObjCName(swiftName = "DarajaTransactionRequest") @Serializable -data class DarajaTransactionRequest( +internal data class DarajaTransactionRequest( @SerialName("BusinessShortCode") val businessShortCode: String, diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressRequest.kt index b8501d3c..d3ac5d1e 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressRequest.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressRequest.kt @@ -16,16 +16,16 @@ package com.vickbt.darajakmp.network.models -import kotlin.native.ObjCName import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlin.native.ObjCName @ObjCName(swiftName = "MpesaExpressRequest") @Serializable /** * Request body sent to Daraja API to request Mpesa Express payment. * */ -data class MpesaExpressRequest( +internal data class MpesaExpressRequest( /**This is organizations shortcode (Paybill or Buygoods - A 5 to 7 digit account number) used to identify an organization and receive the transaction.*/ @SerialName("BusinessShortCode") diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt index 445c6cdb..459f3aa5 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt @@ -16,6 +16,8 @@ package com.vickbt.darajakmp.utils +// ToDo: Add documentation + internal object DarajaEndpoints { const val PROD_BASE_URL = "api.safaricom.co.ke" const val SANDBOX_BASE_URL = "sandbox.safaricom.co.ke" From 1f4e2fb50a97599a0505991c7ace9c157e64e0b1 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 15 Sep 2023 19:12:46 +0300 Subject: [PATCH 28/73] Completed setting up c2b --- .../kotlin/com/vickbt/darajakmp/Daraja.kt | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index f115abb5..722e8386 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -159,9 +159,7 @@ class Daraja constructor( val timestamp = Clock.System.now().getDarajaTimestamp() val darajaPassword = getDarajaPassword( - shortCode = businessShortCode, - passkey = passKey ?: "", - timestamp = timestamp + shortCode = businessShortCode, passkey = passKey ?: "", timestamp = timestamp ) val mpesaExpressRequest = MpesaExpressRequest( @@ -192,14 +190,11 @@ class Daraja constructor( * */ @ObjCName(swiftName = "transactionStatus") fun transactionStatus( - businessShortCode: String, - checkoutRequestID: String + businessShortCode: String, checkoutRequestID: String ): DarajaResult = runBlocking { val timestamp = Clock.System.now().getDarajaTimestamp() val darajaPassword = getDarajaPassword( - shortCode = businessShortCode, - passkey = passKey ?: "", - timestamp = timestamp + shortCode = businessShortCode, passkey = passKey ?: "", timestamp = timestamp ) val darajaTransactionRequest = DarajaTransactionRequest( @@ -244,16 +239,16 @@ class Daraja constructor( fun c2b( amount: Int, billReferenceNumber: String, - commandID: DarajaTransactionType, - msisdn: String, + transactionType: DarajaTransactionType, + phoneNumber: String, businessShortCode: String ): DarajaResult = runBlocking { val c2bRequest = C2BRequest( amount = amount, billReferenceNumber = billReferenceNumber, - commandID = commandID.name, - phoneNumber = msisdn.getDarajaPhoneNumber().toLong(), - shortCode = if (commandID.name == DarajaTransactionType.CustomerPayBillOnline.name) businessShortCode else null + commandID = transactionType.name, + phoneNumber = phoneNumber.getDarajaPhoneNumber().toLong(), + shortCode = if (transactionType.name == DarajaTransactionType.CustomerPayBillOnline.name) businessShortCode else billReferenceNumber ) withContext(ioCoroutineContext) { From 4b1f900ff887b4a5754c23a7f3461cbab34f86c2 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 15 Sep 2023 19:22:56 +0300 Subject: [PATCH 29/73] Resolved daraja c2b registration response type bug --- daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt | 2 +- .../kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index 722e8386..b4d710e1 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -227,7 +227,7 @@ class Daraja constructor( val c2BRegistrationRequest = C2BRegistrationRequest( confirmationURL = confirmationURL, validationURL = validationURL, - responseType = responseType?.name, + responseType = responseType?.name?.lowercase(), shortCode = businessShortCode ) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt index 459f3aa5..11eb0b55 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt @@ -37,6 +37,6 @@ enum class DarajaEnvironment { PRODUCTION_ENVIRONMENT, SANDBOX_ENVIRONMENT } -enum class C2BResponseType(name: String) { - CANCELED("canceled"), COMPLETED("completed") +enum class C2BResponseType { + CANCELED, COMPLETED } From 0978f652d3e2ce9f8c489d3ad12c2c0ea5a6081b Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 15 Sep 2023 20:33:57 +0300 Subject: [PATCH 30/73] Update README.md --- README.md | 50 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 9e600d03..ae21ec80 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ The SDK offers the following functionalities from the Daraja API: - [x] Authorization - Gives you a time bound access token to call allowed APIs. - [x] M-Pesa Express - Merchant initiated online payments. -- [ ] Customer To Business (C2B) +- [x] Customer To Business (C2B) - [ ] Business To Customer (B2C) - Transact between an M-Pesa short code to a phone number registered on M-Pesa. - [x] Transaction Status - Check the status of a transaction. - [ ] Account Balanace - Enquire the balance on an M-Pesa BuyGoods (Till Number) @@ -101,10 +101,10 @@ val daraja: Daraja = Daraja.Builder() ### Request Access Token -- To request an access token from Daraja API, invoke the `requestAccessToken` function: +- To request an access token from Daraja API, invoke the `authorization` function: ```Kotlin -val accessTokenResult: DarajaResult = daraja.requestAuthToken() +val accessTokenResult: DarajaResult = daraja.authorization() accessTokenResult .onSuccess { accessToken -> @@ -117,10 +117,10 @@ accessTokenResult ### Initiate M-Pesa Express STK Request -- To initiate M-Pesa Express(Lipa na M-Pesa Online) STK request, invoke the `initiateDarajaStk` function: +- To initiate M-Pesa Express(Lipa na M-Pesa Online) STK request, invoke the `mpesaExpress` function: ```Kotlin -val darajaPaymentResponse: DarajaResult = daraja.initiateDarajaStk( +val darajaPaymentResponse: DarajaResult = daraja.mpesaExpress( businessShortCode = "174379", amount = 1, phoneNumber = "07xxxxxxxx", @@ -132,18 +132,17 @@ val darajaPaymentResponse: DarajaResult = daraja.initiate darajaPaymentResponse .onSuccess { paymentResponse -> // Successfully requested M-Pesa STK request - } - .onFailure { error -> + }.onFailure { error -> // Failed to request M-Pesa STK } ``` ### Query M-Pesa Transaction -- To check the status of an M-pesa transaction, invoke the `queryMpesaTransaction` function: +- To check the status of an M-pesa transaction, invoke the `transactionStatus` function: ```Kotlin -val darajaTransactionResponse: DarajaResult = daraja.queryMpesaTransaction( +val darajaTransactionResponse: DarajaResult = daraja.transactionStatus( businessShortCode = "174379", checkoutRequestID = "ws_CO_20122022180112029708374149" ) @@ -151,12 +150,29 @@ val darajaTransactionResponse: DarajaResult = daraja. darajaTransactionResponse .onSuccess { transactionResponse -> // Successfully fetched M-pesa transaction status - } - .onFailure { error -> + }.onFailure { error -> // Failure fetching M-pesa transaction status } ``` +### Customer To Business(C2B) +- To register the C2B validation and confirmation URL, invoke the `c2bRegistration` function: +```kotlin +val darajaC2BRegistrationResponse:DarajaResult = daraja.c2bRegistration( + confirmationURL = "https://mydomain.com/confirmation", + responseType = C2BResponseType.COMPLETED, // C2BResponseType.CANCELLED + businessShortCode = 600981, + validationURL = "https://mydomain.com/validation" + ) + +darajaC2BRegistrationResponse.onSuccess { + // Successfull registered confirmation and validation URL +}.onFailure{ + // Failure registering confirmation and validation URL +} + +``` + # iOS - Swift ### Setting Up @@ -185,10 +201,10 @@ var daraja=Daraja( > Network logs are strictly disabled in production mode ie. DarajaEnvironment.productionEnvironment ### Request Access Token -- To request an access token from Daraja API, invoke the `requestAccessToken` function: +- To request an access token from Daraja API, invoke the `authorization` function: ```swift -var accessTokenResult = daraja.requestAccessToken() +var accessTokenResult = daraja.authorization() accessTokenResult.onSuccess(action: { accessToken in // Successfully fetched daraja access token @@ -201,10 +217,10 @@ accessTokenResult.onSuccess(action: { accessToken in ### Initiate M-Pesa Express STK Request -- To initiate M-Pesa Express(Lipa na M-Pesa Online) STK request, invoke the `initiateDarajaStk` function: +- To initiate M-Pesa Express(Lipa na M-Pesa Online) STK request, invoke the `mpesaExpress` function: ```swift -var darajaResponse = daraja.initiateMpesaExpressPayment( +var darajaResponse = daraja.mpesaExpress( businessShortCode: "174379", amount: 1, phoneNumber: "07xxxxxxxx", @@ -225,10 +241,10 @@ var darajaResponse = daraja.initiateMpesaExpressPayment( ### Query M-Pesa Transaction -- To check the status of an M-pesa transaction, invoke the `queryMpesaTransaction` function: +- To check the status of an M-pesa transaction, invoke the `transactionStatus` function: ```swift -var darajaTransactionResponse = daraja.queryMpesaTransaction( +var darajaTransactionResponse = daraja.transactionStatus( businessShortCode: "174379", checkoutRequestID: "ws_CO_20122022180112029708374149") darajaTransactionResponse.onSuccess(action: { data in From 67268c9a53d1d0168e4f6b240969c9e627d8b186 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 15 Sep 2023 22:00:50 +0300 Subject: [PATCH 31/73] Update README.md --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ae21ec80..be1507d0 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,14 @@ - [Request Access Token](#request-access-token) - [Initiate M-Pesa Express STK Request](#initiate-m-pesa-express-stk-request) - [Query M-Pesa Transaction](#query-m-pesa-transaction) + - [Customer To Business(C2B)](#customer-to-businessc2b) - [iOS - Swift](#ios---swift) - [Setting Up](#setting-up-1) - [Request Access Token](#request-access-token) - [Initiate M-Pesa Express STK Request](#initiate-m-pesa-express-stk-request-1) - [Query M-Pesa Transaction](#query-m-pesa-transaction-1) + - [Customer To Business(C2B)](#customer-to-businessc2b) + ## Prerequisite @@ -166,11 +169,27 @@ val darajaC2BRegistrationResponse:DarajaResult = daraja.c2bRegistra ) darajaC2BRegistrationResponse.onSuccess { - // Successfull registered confirmation and validation URL + // Successfully registered confirmation and validation URL }.onFailure{ // Failure registering confirmation and validation URL } +``` +- To initiate a Customer to Business paybill, invoke the `c2b` function: +```kotlin +val c2bResponse: DarajaResult = daraja.c2b( + amount = 1, + billReferenceNumber = "600977", + transactionType = DarajaTransactionType.CustomerBuyGoodsOnline, // DarajaTransactionType.CustomerPayBillOnline + phoneNumber = "0708374149", + businessShortCode = "600977" //Optional when using CustomerBuyGoodsOnline + ) + +c2bResponse.onSuccess { + // Successfully invoked C2B request +}.onFailure{ + // Failure invoking C2B request +} ``` # iOS - Swift From 935dfd6bd44c0ebbe50ecbe23a47a3f0d4eb5d89 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Wed, 20 Sep 2023 21:50:58 +0300 Subject: [PATCH 32/73] SpotlessApply --- .../network/models/C2BRegistrationRequest.kt | 16 ++++++++++++++++ .../darajakmp/network/models/C2BRequest.kt | 16 ++++++++++++++++ .../darajakmp/network/models/C2BResponse.kt | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt index 249e47ad..1dfcc9f3 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Daraja Multiplatform + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.vickbt.darajakmp.network.models import com.vickbt.darajakmp.utils.C2BResponseType diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt index 2538cadb..6519395e 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRequest.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Daraja Multiplatform + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.vickbt.darajakmp.network.models import kotlinx.serialization.SerialName diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BResponse.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BResponse.kt index 41271a33..eba0e3ae 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BResponse.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BResponse.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Daraja Multiplatform + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.vickbt.darajakmp.network.models import kotlinx.serialization.SerialName From 0d692afd1c8b2468d343e0ff1a7d1ccb00ac8e7c Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Wed, 20 Sep 2023 21:51:50 +0300 Subject: [PATCH 33/73] Reduce code coverage requirement --- daraja/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daraja/build.gradle.kts b/daraja/build.gradle.kts index fc793f77..47e670fc 100644 --- a/daraja/build.gradle.kts +++ b/daraja/build.gradle.kts @@ -152,7 +152,7 @@ kover { verify { rule { name = "Minimal line coverage rate in percents" - bound { minValue = 60 } + bound { minValue = 50 } } } } From ddd363e57417ccea26c3c64e5b996cc3f47d174a Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 21 Sep 2023 19:51:42 +0300 Subject: [PATCH 34/73] Update daraja safe api call --- .../kotlin/com/vickbt/darajakmp/network/DarajaSafeApiCall.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaSafeApiCall.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaSafeApiCall.kt index b06e9501..9a4b6bdd 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaSafeApiCall.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaSafeApiCall.kt @@ -60,5 +60,5 @@ internal suspend fun parseNetworkError( exception: Exception? = null ): DarajaException { return errorResponse?.body() - ?: DarajaException(requestId = "0", errorCode = "0", errorMessage = exception?.message) + ?: DarajaException(errorMessage = exception?.message) } From a9557f7b4015756dd37f040079fe3c92414e2cf1 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 21 Sep 2023 19:51:53 +0300 Subject: [PATCH 35/73] Update doc --- .../kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt index 4f64c587..27c56a53 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt @@ -53,8 +53,7 @@ internal class DarajaApiService constructor( .expireAfterWrite(3600.toDuration(DurationUnit.SECONDS)).build() ) { - /** ToDo: Handle daraja auth errors correctly - * Initiate API call using the [httpClient] provided by Ktor to fetch Daraja API access token + /** Initiate API call using the [httpClient] provided by Ktor to fetch Daraja API access token * of type [DarajaToken]*/ internal suspend fun fetchAccessToken(): DarajaResult = darajaSafeApiCall { val key = "$consumerKey:$consumerSecret" From 0eb3232b98f3f7a7302a58c5170f079020fbfabd Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 21 Sep 2023 22:02:15 +0300 Subject: [PATCH 36/73] Update lib versions --- daraja/build.gradle.kts | 4 ++-- gradle/libs.versions.toml | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/daraja/build.gradle.kts b/daraja/build.gradle.kts index 47e670fc..91f51c96 100644 --- a/daraja/build.gradle.kts +++ b/daraja/build.gradle.kts @@ -148,10 +148,10 @@ val javadocJar = tasks.register("javadocJar") { from(dokkaOutputDir) } -kover { +koverReport { verify { rule { - name = "Minimal line coverage rate in percents" + isEnabled = true bound { minValue = 50 } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f25967e3..84412452 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,21 +5,21 @@ ktLint = "10.3.0" detekt = "1.19.0" spotless = "6.2.2" nativeCocoapod = "1.9.0" -dokka = "1.8.20" -kover = "0.6.1" +dokka = "1.9.0" +kover = "0.7.3" mulitplatformSwiftPackage = "2.0.3" -gradleVersionUpdate = "0.45.0" +gradleVersionUpdate = "0.48.0" # Kotlin Multiplatform Version kotlinxCoroutines = "1.7.3" -kotlinxSerializationJson = "1.5.1" -kotlinxDateTime = "0.4.0" +kotlinxSerializationJson = "1.6.0" +kotlinxDateTime = "0.4.1" napier = "2.6.1" -ktor = "2.3.2" +ktor = "2.3.4" kotlinxTestResources = "0.2.2" composeMultiplatform = "1.5.0-beta01" -cache4k = "0.9.0" -mockative = "1.3.1" +cache4k = "0.11.0" +mockative = "2.0.1" [plugins] ktLint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktLint" } From bc6206e47444577288b84475a513debffca1b1bf Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 21 Sep 2023 22:28:53 +0300 Subject: [PATCH 37/73] Update in memory cache --- .../kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt index 27c56a53..9d139fd5 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt @@ -49,7 +49,7 @@ internal class DarajaApiService constructor( private val httpClient: HttpClient, private val consumerKey: String, private val consumerSecret: String, - private val inMemoryCache: Cache = Cache.Builder() + private val inMemoryCache: Cache = Cache.Builder() .expireAfterWrite(3600.toDuration(DurationUnit.SECONDS)).build() ) { From 5c5bc782eab2e89a680d5ae5573dae3aee82ff18 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 21 Sep 2023 22:33:47 +0300 Subject: [PATCH 38/73] Update libs.versions.toml --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 84412452..9eaa048f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ gradleVersionUpdate = "0.48.0" # Kotlin Multiplatform Version kotlinxCoroutines = "1.7.3" -kotlinxSerializationJson = "1.6.0" +kotlinxSerializationJson = "1.5.1" kotlinxDateTime = "0.4.1" napier = "2.6.1" ktor = "2.3.4" From e3f73dd09183664c93dbfe42c258ba926be03e21 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 21 Sep 2023 22:36:43 +0300 Subject: [PATCH 39/73] Update iOS sample app --- app-iOS/app-iOS/ContentView.swift | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/app-iOS/app-iOS/ContentView.swift b/app-iOS/app-iOS/ContentView.swift index d83c3fe1..bff2fcad 100644 --- a/app-iOS/app-iOS/ContentView.swift +++ b/app-iOS/app-iOS/ContentView.swift @@ -81,20 +81,15 @@ func initiateMpesaPayment(daraja:Daraja, transactionDesc: String, callbackUrl: String, accountReference: String){ - do{ - let response=try daraja.initiateMpesaExpressPayment(businessShortCode: businessShortCode, amount: amount, phoneNumber: phoneNumber,transactionType: DarajaTransactionType.customerpaybillonline, transactionDesc: "M-Pesa payment", callbackUrl: "https://mydomain.com/path", accountReference: "Daraja KMP iOS") - - response.onSuccess(action: {data in - print(data.self) - }) - .onFailure(action: {error in - print(error) - }) - - print(response) - }catch{ - print("An error occured") - } + + let response=daraja.initiateMpesaExpressPayment(businessShortCode: businessShortCode, amount: amount, phoneNumber: phoneNumber,transactionType: DarajaTransactionType.customerpaybillonline, transactionDesc: "M-Pesa payment", callbackUrl: "https://mydomain.com/path", accountReference: "Daraja KMP iOS") + + response.onSuccess(action: {data in + print(data.self) + }) + .onFailure(action: {error in + print(error) + }) } struct ContentView_Previews: PreviewProvider { From ccc41583368beea7af59a0a44ce76c08ae24af6a Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 21 Sep 2023 22:44:00 +0300 Subject: [PATCH 40/73] Fixed failing test --- .../vickbt/darajakmp/network/DarajaApiServiceTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt index ab026298..b3d507df 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt @@ -16,22 +16,22 @@ package com.vickbt.darajakmp.network -import com.vickbt.darajakmp.network.models.MpesaExpressRequest -import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.network.models.DarajaToken -import com.vickbt.darajakmp.network.models.DarajaTransactionResponse import com.vickbt.darajakmp.network.models.DarajaTransactionRequest +import com.vickbt.darajakmp.network.models.DarajaTransactionResponse +import com.vickbt.darajakmp.network.models.MpesaExpressRequest +import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.utils.DarajaResult import com.vickbt.darajakmp.utils.DarajaTransactionType import io.github.reactivecircus.cache4k.Cache import io.ktor.client.HttpClient +import kotlinx.coroutines.test.runTest import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull -import kotlinx.coroutines.test.runTest class DarajaApiServiceTest { @@ -72,7 +72,7 @@ class DarajaApiServiceTest { fun setup() { mockKtorHttpClient = mockDarajaHttpClient.mockDarajaHttpClient - mockInMemoryCache = Cache.Builder().build() + mockInMemoryCache = Cache.Builder().build() darajaApiService = DarajaApiService( httpClient = mockKtorHttpClient, From 86419dc40e042b14737edcd2641bb5c51a611337 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 21 Sep 2023 23:40:15 +0300 Subject: [PATCH 41/73] Removed kover bound --- daraja/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daraja/build.gradle.kts b/daraja/build.gradle.kts index 91f51c96..4d60a5da 100644 --- a/daraja/build.gradle.kts +++ b/daraja/build.gradle.kts @@ -148,14 +148,14 @@ val javadocJar = tasks.register("javadocJar") { from(dokkaOutputDir) } -koverReport { +/*koverReport { verify { rule { isEnabled = true bound { minValue = 50 } } } -} +}*/ publishing { From 73a4208bcc321d2f1f7cf266a7d647a3b064bac1 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 22 Sep 2023 00:16:23 +0300 Subject: [PATCH 42/73] Update build.gradle.kts --- daraja/build.gradle.kts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/daraja/build.gradle.kts b/daraja/build.gradle.kts index 4d60a5da..8398aceb 100644 --- a/daraja/build.gradle.kts +++ b/daraja/build.gradle.kts @@ -148,14 +148,14 @@ val javadocJar = tasks.register("javadocJar") { from(dokkaOutputDir) } -/*koverReport { +koverReport { verify { rule { - isEnabled = true - bound { minValue = 50 } + isEnabled = false + bound { minValue = 20 } } } -}*/ +} publishing { From c8175c43502ecb213259bb14fd3cddce3f358442 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Wed, 1 Nov 2023 18:28:46 +0300 Subject: [PATCH 43/73] Upgrade kotlin from v1.8.20 to v1.9.20 --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9eaa048f..f5b8aef9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "1.8.20" +kotlin = "1.9.20" gradle = "7.4.1" ktLint = "10.3.0" detekt = "1.19.0" @@ -17,7 +17,7 @@ kotlinxDateTime = "0.4.1" napier = "2.6.1" ktor = "2.3.4" kotlinxTestResources = "0.2.2" -composeMultiplatform = "1.5.0-beta01" +composeMultiplatform = "1.5.10" cache4k = "0.11.0" mockative = "2.0.1" From 0afe52dbe5865efcde7fbec0206b5666c9097700 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Wed, 1 Nov 2023 18:53:46 +0300 Subject: [PATCH 44/73] Removed new MM rule from gradle properties --- gradle.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index f73ebd02..6710a926 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,6 @@ android.nonTransitiveRClass=true #MPP kotlin.mpp.enableCInteropCommonization=true -kotlin.native.binary.memoryModel=experimental kotlin.native.ignoreDisabledTargets=true kotlin.mpp.stability.nowarn=true kotlin.mpp.androidSourceSetLayoutVersion1.nowarn=true From 6aab8a053bc2232e82af58b2185044518ded4e4f Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Wed, 1 Nov 2023 18:54:18 +0300 Subject: [PATCH 45/73] Update gradle script with kotlin 1.9.20 standards --- daraja/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daraja/build.gradle.kts b/daraja/build.gradle.kts index 8398aceb..86c71971 100644 --- a/daraja/build.gradle.kts +++ b/daraja/build.gradle.kts @@ -35,7 +35,7 @@ plugins { kotlin { targetHierarchy.default() - android { + androidTarget { publishLibraryVariants("release", "debug") } @@ -83,7 +83,7 @@ kotlin { sourceSets["androidMain"].dependencies { implementation(libs.ktor.android) } - sourceSets["androidTest"].dependencies {} + sourceSets["androidUnitTest"].dependencies {} sourceSets["iosMain"].dependencies { implementation(libs.ktor.darwin) From 4d2f6b2aa66fbec43502be720dd9540c8b5c603c Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 12:25:42 +0300 Subject: [PATCH 46/73] Update ktLint from v10.3.0 to v11.6.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f5b8aef9..ee5cf7e3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "1.9.20" gradle = "7.4.1" -ktLint = "10.3.0" +ktLint = "11.6.0" detekt = "1.19.0" spotless = "6.2.2" nativeCocoapod = "1.9.0" From 22cf936d4fabf6b6fd2c230046352f46a9a08eb3 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 12:25:53 +0300 Subject: [PATCH 47/73] Added ktLint to project --- build.gradle.kts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f673d7e7..1656a8b1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,13 +6,13 @@ plugins { alias(libs.plugins.jvm) apply false alias(libs.plugins.nativeCocoapod) apply false - // alias(libs.plugins.ktLint) + alias(libs.plugins.ktLint) alias(libs.plugins.detekt) alias(libs.plugins.spotless) } subprojects { - /*apply(plugin = "org.jlleitschuh.gradle.ktlint") + apply(plugin = "org.jlleitschuh.gradle.ktlint") ktlint { debug.set(true) verbose.set(true) @@ -22,8 +22,9 @@ subprojects { filter { enableExperimentalRules.set(true) exclude { projectDir.toURI().relativize(it.file.toURI()).path.contains("/generated/") } + include("**/kotlin/**") } - }*/ + } apply(plugin = "io.gitlab.arturbosch.detekt") detekt { From 9f7615033bfa943d5fded33c1784667e2966312d Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 12:27:19 +0300 Subject: [PATCH 48/73] Linting daraja module --- daraja/build.gradle.kts | 13 ++++++++---- .../kotlin/com/vickbt/darajakmp/Daraja.kt | 11 +++++++--- .../darajakmp/network/DarajaApiService.kt | 2 -- .../network/DarajaHttpClientFactory.kt | 1 - .../network/models/C2BRegistrationRequest.kt | 2 +- .../vickbt/darajakmp/utils/DarajaResult.kt | 21 +++++++++++++------ .../darajakmp/network/DarajaApiServiceTest.kt | 3 ++- .../darajakmp/network/MockDarajaHttpClient.kt | 8 +++++-- .../darajakmp/utils/DarajaResultTest.kt | 1 - 9 files changed, 41 insertions(+), 21 deletions(-) diff --git a/daraja/build.gradle.kts b/daraja/build.gradle.kts index 86c71971..2e9d29e2 100644 --- a/daraja/build.gradle.kts +++ b/daraja/build.gradle.kts @@ -8,7 +8,7 @@ val snapshotsRepoUrl = uri("https://s01.oss.sonatype.org/content/repositories/sn fun Project.get(key: String, defaultValue: String = "Invalid value $key") = gradleLocalProperties(rootDir).getProperty(key)?.toString() ?: System.getenv(key)?.toString() - ?: defaultValue + ?: defaultValue fun isNonStable(version: String): Boolean { val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.toUpperCase().contains(it) } @@ -162,8 +162,11 @@ publishing { repositories { maven { name = "Sonatype" - url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl - else releasesRepoUrl + url = if (version.toString().endsWith("SNAPSHOT")) { + snapshotsRepoUrl + } else { + releasesRepoUrl + } credentials { username = project.get("OSSRH_USERNAME") @@ -219,7 +222,9 @@ publishing { val signingKey = project.get("SIGNING_PASSWORD") useInMemoryPgpKeys( - signingKeyId, signingKeyPassword, signingKey + signingKeyId, + signingKeyPassword, + signingKey ) sign(publishing.publications) } diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index b4d710e1..e7cff256 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -159,7 +159,9 @@ class Daraja constructor( val timestamp = Clock.System.now().getDarajaTimestamp() val darajaPassword = getDarajaPassword( - shortCode = businessShortCode, passkey = passKey ?: "", timestamp = timestamp + shortCode = businessShortCode, + passkey = passKey ?: "", + timestamp = timestamp ) val mpesaExpressRequest = MpesaExpressRequest( @@ -190,11 +192,14 @@ class Daraja constructor( * */ @ObjCName(swiftName = "transactionStatus") fun transactionStatus( - businessShortCode: String, checkoutRequestID: String + businessShortCode: String, + checkoutRequestID: String ): DarajaResult = runBlocking { val timestamp = Clock.System.now().getDarajaTimestamp() val darajaPassword = getDarajaPassword( - shortCode = businessShortCode, passkey = passKey ?: "", timestamp = timestamp + shortCode = businessShortCode, + passkey = passKey ?: "", + timestamp = timestamp ) val darajaTransactionRequest = DarajaTransactionRequest( diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt index 9d139fd5..a9071c18 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt @@ -118,6 +118,4 @@ internal class DarajaApiService constructor( setBody(c2bRequest) }.body() } - } - diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt index 61a5959d..e57ee3ee 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt @@ -22,7 +22,6 @@ import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.Napier import io.ktor.client.HttpClient import io.ktor.client.plugins.addDefaultResponseValidation -import io.ktor.client.plugins.cache.HttpCache import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.defaultRequest import io.ktor.client.plugins.logging.LogLevel diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt index 1dfcc9f3..4f470319 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/C2BRegistrationRequest.kt @@ -39,5 +39,5 @@ internal data class C2BRegistrationRequest( /**A unique number is tagged to an M-PESA pay bill/till number of the organization.*/ @SerialName("ShortCode") - val shortCode: Int, + val shortCode: Int ) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaResult.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaResult.kt index 73461f2a..5e69f446 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaResult.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaResult.kt @@ -38,8 +38,11 @@ sealed class DarajaResult { * @receiver [DarajaResult] * */ internal fun DarajaResult.getOrNull(): T? { - return if (this is DarajaResult.Success) this.data - else null + return if (this is DarajaResult.Success) { + this.data + } else { + null + } } /**Returns exception of type [DarajaException] on failure @@ -48,8 +51,11 @@ internal fun DarajaResult.getOrNull(): T? { * @receiver [DarajaResult] * */ internal fun DarajaResult.throwOnFailure(): DarajaException { - return if (this is DarajaResult.Failure) this.exception - else throw DarajaException() + return if (this is DarajaResult.Failure) { + this.exception + } else { + throw DarajaException() + } } /**Returns result of type [T] on success or exception of type [DarajaException] on failure @@ -57,8 +63,11 @@ internal fun DarajaResult.throwOnFailure(): DarajaException { * @receiver [DarajaResult] * */ internal fun DarajaResult.getOrThrow(): T { - return if (this is DarajaResult.Success) this.data - else throw this.throwOnFailure() + return if (this is DarajaResult.Success) { + this.data + } else { + throw this.throwOnFailure() + } } /* ToDo diff --git a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt index b3d507df..3323e827 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt @@ -44,7 +44,8 @@ class DarajaApiServiceTest { private lateinit var darajaApiService: DarajaApiService private val darajaToken = DarajaToken( - accessToken = "wWAHdtiE4GCSGv2ocfzQ0WHefwAJ", expiresIn = "3599" + accessToken = "wWAHdtiE4GCSGv2ocfzQ0WHefwAJ", + expiresIn = "3599" ) private val mpesaExpressRequest = MpesaExpressRequest( diff --git a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/MockDarajaHttpClient.kt b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/MockDarajaHttpClient.kt index 44f368fe..def8e2cd 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/MockDarajaHttpClient.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/MockDarajaHttpClient.kt @@ -55,12 +55,16 @@ internal class MockDarajaHttpClient { when (request.url.fullPath) { "/${DarajaEndpoints.REQUEST_ACCESS_TOKEN}" -> { respond( - responseContent ?: AccessToken200JSON, httpStatusCode, responseHeaders + responseContent ?: AccessToken200JSON, + httpStatusCode, + responseHeaders ) } "/${DarajaEndpoints.INITIATE_MPESA_EXPRESS}" -> { respond( - responseContent ?: MpesaExpress200JSON, httpStatusCode, responseHeaders + responseContent ?: MpesaExpress200JSON, + httpStatusCode, + responseHeaders ) } "/${DarajaEndpoints.QUERY_MPESA_TRANSACTION}" -> { diff --git a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/DarajaResultTest.kt b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/DarajaResultTest.kt index 8dee2a39..2c7ee7c5 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/DarajaResultTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/DarajaResultTest.kt @@ -19,7 +19,6 @@ package com.vickbt.darajakmp.utils import com.vickbt.darajakmp.network.models.DarajaException import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertFailsWith import kotlin.test.assertNotNull import kotlin.test.assertNull From 625448f6f0ebd0fb370ab6078e0aeebe83e975b0 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 12:34:26 +0300 Subject: [PATCH 49/73] Disable package-name rule in ktLint --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 219dccb8..7a0773af 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,3 @@ [*.{kt,kts}] -disabled_rules = import-ordering, experimental:argument-list-wrapping,experimental:package-name +disabled_rules = import-ordering, experimental:argument-list-wrapping, package-name insert_final_newline = true \ No newline at end of file From 959a6951ad115ca64659fbf341fee61563240f49 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 12:34:40 +0300 Subject: [PATCH 50/73] Linting app-android module --- .../src/main/java/com/vickbt/app_android/ui/theme/Theme.kt | 2 +- .../src/main/java/com/vickbt/app_android/ui/theme/Type.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app-android/src/main/java/com/vickbt/app_android/ui/theme/Theme.kt b/app-android/src/main/java/com/vickbt/app_android/ui/theme/Theme.kt index cda0bd7f..0438a959 100644 --- a/app-android/src/main/java/com/vickbt/app_android/ui/theme/Theme.kt +++ b/app-android/src/main/java/com/vickbt/app_android/ui/theme/Theme.kt @@ -50,7 +50,7 @@ private val LightColorScheme = lightColorScheme( onTertiary = Color.White, onBackground = Color(0xFF1C1B1F), onSurface = Color(0xFF1C1B1F), - */ + */ ) @Composable diff --git a/app-android/src/main/java/com/vickbt/app_android/ui/theme/Type.kt b/app-android/src/main/java/com/vickbt/app_android/ui/theme/Type.kt index a8a12b31..ff58a6e5 100644 --- a/app-android/src/main/java/com/vickbt/app_android/ui/theme/Type.kt +++ b/app-android/src/main/java/com/vickbt/app_android/ui/theme/Type.kt @@ -46,5 +46,5 @@ val Typography = Typography( lineHeight = 16.sp, letterSpacing = 0.5.sp ) - */ + */ ) From eb1764fe0cffb4ab49bb5a2265b8637e918e4494 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 12:35:14 +0300 Subject: [PATCH 51/73] Linting app-desktop module --- .../src/main/java/com/vickikbt/app_desktop/ui/theme/Theme.kt | 2 +- .../src/main/java/com/vickikbt/app_desktop/ui/theme/Type.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Theme.kt b/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Theme.kt index a84e01ff..a5111015 100644 --- a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Theme.kt +++ b/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Theme.kt @@ -39,7 +39,7 @@ private val LightColorScheme = lightColors( onTertiary = Color.White, onBackground = Color(0xFF1C1B1F), onSurface = Color(0xFF1C1B1F), - */ + */ ) @Composable diff --git a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Type.kt b/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Type.kt index 90dc4a52..a4705db9 100644 --- a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Type.kt +++ b/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Type.kt @@ -46,5 +46,5 @@ val Typography = Typography( lineHeight = 16.sp, letterSpacing = 0.5.sp ) - */ + */ ) From 485d294a314c76538a4070af79da52e3b1a04871 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 12:45:16 +0300 Subject: [PATCH 52/73] Update compose compiler from v1.4.6 to v1.5.4 --- app-android/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-android/build.gradle.kts b/app-android/build.gradle.kts index 7be858a5..d487c936 100644 --- a/app-android/build.gradle.kts +++ b/app-android/build.gradle.kts @@ -42,7 +42,7 @@ android { compose = true } composeOptions { - kotlinCompilerExtensionVersion = "1.4.6" + kotlinCompilerExtensionVersion = "1.5.4" } packagingOptions { resources { From 4858bb90c1350f9a3c189d7a98f716c9e9cec473 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 13:14:32 +0300 Subject: [PATCH 53/73] Update maintainance workflow with correct report dir --- .github/workflows/maintenance.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml index 24215146..e02b5df3 100644 --- a/.github/workflows/maintenance.yml +++ b/.github/workflows/maintenance.yml @@ -24,7 +24,7 @@ jobs: run: chmod +x ./gradlew - name: Check for release dependencies - run: ./gradlew :daraja:dependencyUpdates -Drevision=release -DoutputFormatter=html -DreportfileName=dependencies_report -DoutputDir=build/reports --stacktrace + run: ./gradlew :daraja:dependencyUpdates -Drevision=release -DoutputFormatter=html -DreportfileName=dependencies_report --stacktrace - name: Upload dependencies report artifact uses: actions/upload-artifact@v3 From e8ab0e55500f7d8d3560c50d9f91781a2bfce51a Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 13:20:49 +0300 Subject: [PATCH 54/73] Update ktor from v2.3.4 to v2.3.6 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ee5cf7e3..64aa3099 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ kotlinxCoroutines = "1.7.3" kotlinxSerializationJson = "1.5.1" kotlinxDateTime = "0.4.1" napier = "2.6.1" -ktor = "2.3.4" +ktor = "2.3.6" kotlinxTestResources = "0.2.2" composeMultiplatform = "1.5.10" cache4k = "0.11.0" From 318af18394151087a602a4a4f781ac11b21db198 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 13:29:46 +0300 Subject: [PATCH 55/73] Update cache4k from v0.11.0 to v0.12.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 64aa3099..d053ca05 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ napier = "2.6.1" ktor = "2.3.6" kotlinxTestResources = "0.2.2" composeMultiplatform = "1.5.10" -cache4k = "0.11.0" +cache4k = "0.12.0" mockative = "2.0.1" [plugins] From 53d43c057ac9f1632f2f73ec329b81000b47637d Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 13:33:32 +0300 Subject: [PATCH 56/73] Update kotlinxSerializationJson from v1.5.1 to v1.6.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d053ca05..8fe2f5be 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ gradleVersionUpdate = "0.48.0" # Kotlin Multiplatform Version kotlinxCoroutines = "1.7.3" -kotlinxSerializationJson = "1.5.1" +kotlinxSerializationJson = "1.6.0" kotlinxDateTime = "0.4.1" napier = "2.6.1" ktor = "2.3.6" From ce93b247498b4444803e9fc1803b389a11342361 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 13:36:29 +0300 Subject: [PATCH 57/73] Added workflow dispatch to maintainance workflow --- .github/workflows/maintenance.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml index e02b5df3..b9bda016 100644 --- a/.github/workflows/maintenance.yml +++ b/.github/workflows/maintenance.yml @@ -1,6 +1,7 @@ name: Maintenance on: + workflow_dispatch: schedule: - cron: 00 00 30 * * # At 00:00 on 30th every month From 329d1a14f9eca3df1ff92a789f0ca29f874e1c02 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 15:12:01 +0300 Subject: [PATCH 58/73] Revert cache4k from v0.11.0 to v0.12.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8fe2f5be..917abdbc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ napier = "2.6.1" ktor = "2.3.6" kotlinxTestResources = "0.2.2" composeMultiplatform = "1.5.10" -cache4k = "0.12.0" +cache4k = "0.11.0" # 0.12.0 breaks build mockative = "2.0.1" [plugins] From b0c5ca5c41e4ccb97595339b436f9ab30fe3d1c9 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Fri, 10 Nov 2023 15:58:47 +0300 Subject: [PATCH 59/73] Update kover from v0.7.3 to v0.7.4 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 917abdbc..ce4e9233 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ detekt = "1.19.0" spotless = "6.2.2" nativeCocoapod = "1.9.0" dokka = "1.9.0" -kover = "0.7.3" +kover = "0.7.4" mulitplatformSwiftPackage = "2.0.3" gradleVersionUpdate = "0.48.0" From 43aaca3c185fdf4c45fbaf994e8f54e42a95c156 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Tue, 21 Nov 2023 14:26:39 +0300 Subject: [PATCH 60/73] Update dokka from v1.9.0 to v1.9.10 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ce4e9233..ba5bfa42 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ ktLint = "11.6.0" detekt = "1.19.0" spotless = "6.2.2" nativeCocoapod = "1.9.0" -dokka = "1.9.0" +dokka = "1.9.10" kover = "0.7.4" mulitplatformSwiftPackage = "2.0.3" gradleVersionUpdate = "0.48.0" From 40c04964ba1b64614cedde2fcbde47ee3fad1dcc Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Tue, 21 Nov 2023 15:31:06 +0300 Subject: [PATCH 61/73] Resolve ide warning in daraja gradle script --- daraja/build.gradle.kts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/daraja/build.gradle.kts b/daraja/build.gradle.kts index 2e9d29e2..0feab851 100644 --- a/daraja/build.gradle.kts +++ b/daraja/build.gradle.kts @@ -1,5 +1,6 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask +import java.util.Locale val dokkaOutputDir = buildDir.resolve("reports/dokka") @@ -11,7 +12,9 @@ fun Project.get(key: String, defaultValue: String = "Invalid value $key") = ?: defaultValue fun isNonStable(version: String): Boolean { - val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.toUpperCase().contains(it) } + val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { + version.uppercase(Locale.getDefault()).contains(it) + } val regex = "^[0-9,.v-]+(-r)?$".toRegex() val isStable = stableKeyword || regex.matches(version) return isStable.not() @@ -33,7 +36,7 @@ plugins { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { - targetHierarchy.default() + kotlin.applyDefaultHierarchyTemplate() androidTarget { publishLibraryVariants("release", "debug") From 7dcec035388a32387e9cc52800594cc9ca5317de Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 4 Jan 2024 12:29:32 +0300 Subject: [PATCH 62/73] Update kover from v0.7.4 to v0.7.5 --- daraja/daraja.podspec | 11 +++++++++++ gradle/libs.versions.toml | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/daraja/daraja.podspec b/daraja/daraja.podspec index 7d3ec92c..0e10d72d 100644 --- a/daraja/daraja.podspec +++ b/daraja/daraja.podspec @@ -11,6 +11,17 @@ Pod::Spec.new do |spec| spec.ios.deployment_target = '14.1' + if !Dir.exist?('build/cocoapods/framework/DarajaMultiplatform.framework') || Dir.empty?('build/cocoapods/framework/DarajaMultiplatform.framework') + raise " + + Kotlin framework 'DarajaMultiplatform' doesn't exist yet, so a proper Xcode project can't be generated. + 'pod install' should be executed after running ':generateDummyFramework' Gradle task: + + ./gradlew :daraja:generateDummyFramework + + Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" + end + spec.pod_target_xcconfig = { 'KOTLIN_PROJECT_PATH' => ':daraja', 'PRODUCT_MODULE_NAME' => 'DarajaMultiplatform', diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ba5bfa42..6ec90892 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ detekt = "1.19.0" spotless = "6.2.2" nativeCocoapod = "1.9.0" dokka = "1.9.10" -kover = "0.7.4" +kover = "0.7.5" mulitplatformSwiftPackage = "2.0.3" gradleVersionUpdate = "0.48.0" From a49382c889651713f7d081a1b80ccddaef6497f2 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 4 Jan 2024 12:33:02 +0300 Subject: [PATCH 63/73] Update dependency update plugin from v0.48.0 to v0.50.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6ec90892..a19c85a7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ nativeCocoapod = "1.9.0" dokka = "1.9.10" kover = "0.7.5" mulitplatformSwiftPackage = "2.0.3" -gradleVersionUpdate = "0.48.0" +gradleVersionUpdate = "0.50.0" # Kotlin Multiplatform Version kotlinxCoroutines = "1.7.3" From 8b12306d3d3b251d348a5726aec93a0368fd0949 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 4 Jan 2024 12:56:06 +0300 Subject: [PATCH 64/73] Update ktor from v2.3.6 to v2.3.7 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a19c85a7..d0d4b7c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ kotlinxCoroutines = "1.7.3" kotlinxSerializationJson = "1.6.0" kotlinxDateTime = "0.4.1" napier = "2.6.1" -ktor = "2.3.6" +ktor = "2.3.7" kotlinxTestResources = "0.2.2" composeMultiplatform = "1.5.10" cache4k = "0.11.0" # 0.12.0 breaks build From 8b754dc02620a1c88d3176a1ab29e90784f92c7c Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 4 Jan 2024 12:59:17 +0300 Subject: [PATCH 65/73] Update kotlinxDateTime from v0.4.1 to v0.5.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d0d4b7c5..f47810f4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ gradleVersionUpdate = "0.50.0" # Kotlin Multiplatform Version kotlinxCoroutines = "1.7.3" kotlinxSerializationJson = "1.6.0" -kotlinxDateTime = "0.4.1" +kotlinxDateTime = "0.5.0" napier = "2.6.1" ktor = "2.3.7" kotlinxTestResources = "0.2.2" From 3bb9e21d2e06af317f0b741f77ce38734458dc5f Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 4 Jan 2024 13:00:03 +0300 Subject: [PATCH 66/73] Update kotlinxSerializationJson from v1.6.0 to v1.6.2 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f47810f4..0ba9b3d9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ gradleVersionUpdate = "0.50.0" # Kotlin Multiplatform Version kotlinxCoroutines = "1.7.3" -kotlinxSerializationJson = "1.6.0" +kotlinxSerializationJson = "1.6.2" kotlinxDateTime = "0.5.0" napier = "2.6.1" ktor = "2.3.7" From ba144d58339db2a886537c49c4ee4ddf6b86a787 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 4 Jan 2024 13:00:38 +0300 Subject: [PATCH 67/73] Update kotlin from v1.9.20 to v1.9.22 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0ba9b3d9..cc2117b2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "1.9.20" +kotlin = "1.9.22" gradle = "7.4.1" ktLint = "11.6.0" detekt = "1.19.0" From f4f7d779b8a41efd1690de618c865ad31c06bb8b Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 4 Jan 2024 14:17:57 +0300 Subject: [PATCH 68/73] Downgrade kotlin from v1.9.22 to 1.9.21 and update compose multiplatform from v1.5.10 to 1.5.11 --- app-android/build.gradle.kts | 2 +- gradle/libs.versions.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app-android/build.gradle.kts b/app-android/build.gradle.kts index d487c936..b1254c85 100644 --- a/app-android/build.gradle.kts +++ b/app-android/build.gradle.kts @@ -42,7 +42,7 @@ android { compose = true } composeOptions { - kotlinCompilerExtensionVersion = "1.5.4" + kotlinCompilerExtensionVersion = "1.5.7" } packagingOptions { resources { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cc2117b2..e9cf3c40 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "1.9.22" +kotlin = "1.9.21" gradle = "7.4.1" ktLint = "11.6.0" detekt = "1.19.0" @@ -17,7 +17,7 @@ kotlinxDateTime = "0.5.0" napier = "2.6.1" ktor = "2.3.7" kotlinxTestResources = "0.2.2" -composeMultiplatform = "1.5.10" +composeMultiplatform = "1.5.11" cache4k = "0.11.0" # 0.12.0 breaks build mockative = "2.0.1" From 9763f1d3de082c9294462b17b0d2d9a069eabaaf Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 4 Jan 2024 15:50:53 +0300 Subject: [PATCH 69/73] Update gradle.properties --- gradle.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle.properties b/gradle.properties index 6710a926..39b7dcee 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,7 @@ kotlin.code.style=official #Android android.useAndroidX=true android.nonTransitiveRClass=true +android.disableAutomaticComponentCreation=true #MPP kotlin.mpp.enableCInteropCommonization=true From 9aae4e6d612638df73c8998e43d266a31348f820 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 4 Jan 2024 15:51:12 +0300 Subject: [PATCH 70/73] Update initiateMpesaExpressPayment to mpesaExpress --- .../java/com/vickikbt/app_desktop/ui/screens/home/HomeScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/home/HomeScreen.kt b/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/home/HomeScreen.kt index f9ea6bfa..2b5db3b7 100644 --- a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/home/HomeScreen.kt +++ b/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/home/HomeScreen.kt @@ -128,7 +128,7 @@ fun HomeScreen() { } fun initiateMpesaStk(daraja: Daraja, tillNumber: String, amount: Int, phoneNumber: String) { - daraja.initiateMpesaExpressPayment( + daraja.mpesaExpress( businessShortCode = tillNumber, amount = amount, phoneNumber = phoneNumber, From 479d064549d073fa2a6271536ab4d6e8438af76a Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 4 Jan 2024 16:14:10 +0300 Subject: [PATCH 71/73] Added a pull request template --- .github/pull_request_template.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..f5f04fb9 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,25 @@ +### Description + + +### Issue + + +### Changes Made + + +### Screenshots / GIFs + + +### Checklist + +- [] I have run the gradle command: `./gradlew clean build` +- [] I have added or updated relevant tests. +- [] I have updated the documentation. +- [] I have ensured code style consistency by running the gradle command: `./gradlew :daraja:ktlintCheck :daraja:detekt` . +- [] I have addressed any potential compatibility issues. +- [] I have considered backward compatibility. + +### Additional Notes + + + From 08b78b2e9384b1fbbd8078e581b2217220065097 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 4 Jan 2024 16:21:50 +0300 Subject: [PATCH 72/73] Update dependency update report storage path --- daraja/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daraja/build.gradle.kts b/daraja/build.gradle.kts index 0feab851..f5e9dcda 100644 --- a/daraja/build.gradle.kts +++ b/daraja/build.gradle.kts @@ -133,7 +133,7 @@ tasks.withType { gradleReleaseChannel = "current" outputFormatter = "html" - outputDir = "${project.rootDir}/build/reports" + outputDir = "${project.rootDir}/daraja/build/reports" reportfileName = "dependencies_report" } From e01b438e013a13680c2b4ca55eede5dcc1371426 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Thu, 2 May 2024 22:21:25 +0300 Subject: [PATCH 73/73] Code clean up --- .../kotlin/com/vickbt/darajakmp/Daraja.kt | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index e7cff256..9374a5ed 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -38,7 +38,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext import kotlinx.datetime.Clock import kotlin.native.ObjCName @@ -50,7 +49,7 @@ import kotlin.native.ObjCName * @param environment Environment that Daraja API should use ie. Either [DarajaEnvironment.SANDBOX_ENVIRONMENT] (Sandbox Mode) or [DarajaEnvironment.PRODUCTION_ENVIRONMENT] (Production Mode) * */ @ObjCName(swiftName = "Daraja") -class Daraja constructor( +class Daraja( private val consumerKey: String?, private val consumerSecret: String?, private val passKey: String?, @@ -127,10 +126,8 @@ class Daraja constructor( * @return [DarajaToken] * */ @ObjCName(swiftName = "authorization") - fun authorization(): DarajaResult = runBlocking { - withContext(ioCoroutineContext) { - return@withContext darajaApiService.fetchAccessToken() - } + fun authorization(): DarajaResult = runBlocking(Dispatchers.IO) { + darajaApiService.fetchAccessToken() } /**Initiate Mpesa Express payment of value provided in [amount] to the [businessShortCode] from the the [phoneNumber]. @@ -155,7 +152,7 @@ class Daraja constructor( transactionDesc: String, callbackUrl: String, accountReference: String? = null - ): DarajaResult = runBlocking { + ): DarajaResult = runBlocking(Dispatchers.IO) { val timestamp = Clock.System.now().getDarajaTimestamp() val darajaPassword = getDarajaPassword( @@ -178,9 +175,7 @@ class Daraja constructor( partyB = businessShortCode ) - withContext(ioCoroutineContext) { - return@withContext darajaApiService.initiateMpesaExpress(mpesaExpressRequest = mpesaExpressRequest) - } + darajaApiService.initiateMpesaExpress(mpesaExpressRequest = mpesaExpressRequest) } /**Request the status of an Mpesa payment transaction @@ -194,7 +189,7 @@ class Daraja constructor( fun transactionStatus( businessShortCode: String, checkoutRequestID: String - ): DarajaResult = runBlocking { + ): DarajaResult = runBlocking(Dispatchers.IO) { val timestamp = Clock.System.now().getDarajaTimestamp() val darajaPassword = getDarajaPassword( shortCode = businessShortCode, @@ -209,9 +204,7 @@ class Daraja constructor( checkoutRequestID = checkoutRequestID ) - withContext(ioCoroutineContext) { - return@withContext darajaApiService.queryTransaction(darajaTransactionRequest) - } + darajaApiService.queryTransaction(darajaTransactionRequest) } /**Transact between a phone number registered on M-Pesa to an M-Pesa shortcode @@ -228,7 +221,7 @@ class Daraja constructor( confirmationURL: String, validationURL: String? = null, responseType: C2BResponseType? = C2BResponseType.COMPLETED - ): DarajaResult = runBlocking { + ): DarajaResult = runBlocking(Dispatchers.IO) { val c2BRegistrationRequest = C2BRegistrationRequest( confirmationURL = confirmationURL, validationURL = validationURL, @@ -236,9 +229,7 @@ class Daraja constructor( shortCode = businessShortCode ) - withContext(ioCoroutineContext) { - return@withContext darajaApiService.c2bRegistration(c2bRegistrationRequest = c2BRegistrationRequest) - } + darajaApiService.c2bRegistration(c2bRegistrationRequest = c2BRegistrationRequest) } fun c2b( @@ -247,7 +238,7 @@ class Daraja constructor( transactionType: DarajaTransactionType, phoneNumber: String, businessShortCode: String - ): DarajaResult = runBlocking { + ): DarajaResult = runBlocking(Dispatchers.IO) { val c2bRequest = C2BRequest( amount = amount, billReferenceNumber = billReferenceNumber, @@ -256,8 +247,6 @@ class Daraja constructor( shortCode = if (transactionType.name == DarajaTransactionType.CustomerPayBillOnline.name) businessShortCode else billReferenceNumber ) - withContext(ioCoroutineContext) { - return@withContext darajaApiService.c2b(c2bRequest = c2bRequest) - } + darajaApiService.c2b(c2bRequest = c2bRequest) } }