From 087e7c76a51802979174e220bfe29ef2dfed0829 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 29 Jul 2024 19:20:34 +0300 Subject: [PATCH 1/8] Added query mpesa express request and response bodies --- .../models/QueryMpesaExpressRequest.kt | 19 +++++++++++ .../models/QueryMpesaExpressResponse.kt | 33 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressRequest.kt create mode 100644 daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressResponse.kt diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressRequest.kt new file mode 100644 index 00000000..dd3668af --- /dev/null +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressRequest.kt @@ -0,0 +1,19 @@ +package com.vickbt.darajakmp.network.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +internal data class QueryMpesaExpressRequest( + @SerialName("BusinessShortCode") + val businessShortCode: String, + + @SerialName("Password") + val password: String, + + @SerialName("Timestamp") + val timestamp: String, + + @SerialName("CheckoutRequestID") + val checkoutRequestID: String +) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressResponse.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressResponse.kt new file mode 100644 index 00000000..67dd88e2 --- /dev/null +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressResponse.kt @@ -0,0 +1,33 @@ +package com.vickbt.darajakmp.network.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * @param [responseCode] - This is a numeric status code that indicates the status of the transaction submission. 0 means successful submission and any other code means an error occurred. + * @param [responseDescription] - Response description is an acknowledgment message from the API that gives the status of the request submission usually maps to a specific ResponseCode value. It can be a "Success" submission message or an error description.Response description is an acknowledgment message from the API that gives the status of the request submission usually maps to a specific ResponseCode value. It can be a "Success" submission message or an error description. + * @param [merchantRequestID] - This is a global unique Identifier for any submitted payment request. + * @param [checkoutRequestID] - This is a global unique identifier of the processed checkout transaction request. + * @param [resultCode] - This is a numeric status code that indicates the status of the transaction processing. 0 means successful processing and any other code means an error occurred or the transaction failed. + * @param [resultDescription] - Response description is an acknowledgment message from the API that gives the status of the request submission usually maps to a specific ResponseCode value. It can be a "Success" submission message or an error description. + * */ +@Serializable +data class QueryMpesaExpressResponse( + @SerialName("ResponseCode") + val responseCode: String, + + @SerialName("ResponseDescription") + val responseDescription: String, + + @SerialName("MerchantRequestID") + val merchantRequestID: String, + + @SerialName("CheckoutRequestID") + val checkoutRequestID: String, + + @SerialName("ResultCode") + val resultCode: String, + + @SerialName("ResultDesc") + val resultDescription: String +) From d636b9526c06b339d1a1013188b5d12499ea184a Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 29 Jul 2024 19:21:17 +0300 Subject: [PATCH 2/8] Added query mpesa express transaction endpoint --- .../vickbt/darajakmp/network/DarajaApiService.kt | 14 ++++++++++++++ .../com/vickbt/darajakmp/utils/DarajaConfigs.kt | 1 + 2 files changed, 15 insertions(+) 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 ee7a5845..c15d3ff5 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt @@ -28,6 +28,8 @@ import com.vickbt.darajakmp.network.models.DynamicQrRequest import com.vickbt.darajakmp.network.models.DynamicQrResponse import com.vickbt.darajakmp.network.models.MpesaExpressRequest import com.vickbt.darajakmp.network.models.MpesaExpressResponse +import com.vickbt.darajakmp.network.models.QueryMpesaExpressRequest +import com.vickbt.darajakmp.network.models.QueryMpesaExpressResponse import com.vickbt.darajakmp.utils.DarajaEndpoints import com.vickbt.darajakmp.utils.DarajaResult import com.vickbt.darajakmp.utils.getOrThrow @@ -87,6 +89,18 @@ internal class DarajaApiService( }.body() } + internal suspend fun queryMpesaExpress(queryMpesaExpressRequest: QueryMpesaExpressRequest): DarajaResult = + darajaSafeApiCall { + val accessToken = inMemoryCache.get(1) { + fetchAccessToken().getOrThrow() + } + + return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.QUERY_MPESA_EXPRESS) { + headers { append(HttpHeaders.Authorization, "Bearer ${accessToken.accessToken}") } + setBody(queryMpesaExpressRequest) + }.body() + } + internal suspend fun generateDynamicQr(dynamicQrRequest: DynamicQrRequest): DarajaResult = darajaSafeApiCall { val accessToken = inMemoryCache.get(1) { 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 7a9e4bc2..45fbcecf 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt @@ -22,6 +22,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_EXPRESS="mpesa/stkpushquery/v1/query" const val QUERY_MPESA_TRANSACTION = "mpesa/stkpushquery/v1/query" const val C2B_REGISTRATION_URL = "mpesa/c2b/v1/registerurl" const val INITIATE_C2B = "mpesa/c2b/v1/simulate" From 2c0a32814454e4f41f04a029426118e692ada7cf Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 29 Jul 2024 19:21:47 +0300 Subject: [PATCH 3/8] Added mpesaExpressQuery to daraja --- .../kotlin/com/vickbt/darajakmp/Daraja.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index 8c4d988e..d54b2437 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -30,6 +30,7 @@ import com.vickbt.darajakmp.network.models.DynamicQrRequest import com.vickbt.darajakmp.network.models.DynamicQrResponse import com.vickbt.darajakmp.network.models.MpesaExpressRequest import com.vickbt.darajakmp.network.models.MpesaExpressResponse +import com.vickbt.darajakmp.network.models.QueryMpesaExpressRequest import com.vickbt.darajakmp.utils.C2BResponseType import com.vickbt.darajakmp.utils.DarajaEnvironment import com.vickbt.darajakmp.utils.DarajaIdentifierType @@ -182,6 +183,32 @@ class Daraja( darajaApiService.initiateMpesaExpress(mpesaExpressRequest = mpesaExpressRequest) } + /** + * @param [businessShortCode] - This is the organization's shortcode (Paybill or Buy Goods) used to identify an organization and receive the transaction. + * @param [timestamp] - This is the timestamp of the transaction, normally in the format of Year+Month+Date+Hour+Minute+Second(YYYYMMDDHHmmss) + * @param [checkoutRequestID] - This is a global unique identifier of the processed checkout transaction request. + * */ + fun mpesaExpressQuery( + businessShortCode: String, + timestamp: String, + checkoutRequestID: String + ) = runBlocking(Dispatchers.IO) { + val darajaPassword = getDarajaPassword( + shortCode = businessShortCode, + passkey = passKey ?: "", + timestamp = timestamp + ) + + val queryMpesaExpressRequest = QueryMpesaExpressRequest( + businessShortCode = businessShortCode, + password = darajaPassword, + timestamp = timestamp, + checkoutRequestID = checkoutRequestID + ) + + darajaApiService.queryMpesaExpress(queryMpesaExpressRequest = queryMpesaExpressRequest) + } + /**Generate a dynamic qr code to initiate payment * * @param [merchantName] Name of the company/M-Pesa merchant name From 24ee54b86219856495651aa851e852131c56ea51 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 29 Jul 2024 19:38:50 +0300 Subject: [PATCH 4/8] Linting --- .../network/models/QueryMpesaExpressRequest.kt | 16 ++++++++++++++++ .../network/models/QueryMpesaExpressResponse.kt | 16 ++++++++++++++++ .../com/vickbt/darajakmp/utils/DarajaConfigs.kt | 2 +- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressRequest.kt index dd3668af..a80dc52b 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressRequest.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressRequest.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2024 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/QueryMpesaExpressResponse.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressResponse.kt index 67dd88e2..4253c4b9 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressResponse.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/QueryMpesaExpressResponse.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2024 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/utils/DarajaConfigs.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt index 45fbcecf..1200b917 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt @@ -22,7 +22,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_EXPRESS="mpesa/stkpushquery/v1/query" + const val QUERY_MPESA_EXPRESS = "mpesa/stkpushquery/v1/query" const val QUERY_MPESA_TRANSACTION = "mpesa/stkpushquery/v1/query" const val C2B_REGISTRATION_URL = "mpesa/c2b/v1/registerurl" const val INITIATE_C2B = "mpesa/c2b/v1/simulate" From 0a957d605ad7eb33fc1fc5a255a9e5c297cf3460 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 29 Jul 2024 19:57:10 +0300 Subject: [PATCH 5/8] Update project documentation --- README.md | 76 ++++++++++++++----- .../kotlin/com/vickbt/darajakmp/Daraja.kt | 3 +- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index cb8121ee..b5241066 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# __Daraja Multiplatform__ +# **Daraja Multiplatform**

@@ -12,6 +12,7 @@ [Kotlin multiplatform](https://kotlinlang.org/docs/multiplatform.html) wrapper for Mpesa API dubbed [_Daraja API_](https://developer.safaricom.co.ke/) (Daraja means bridge in Swahili) that supports integration with your Android(Kotlin/Java), iOS(Swift) and JVM applications. + > M-PESA is a mobile money transfer service in Kenya that allows users to store and transfer money > through their mobile phones. @@ -43,7 +44,7 @@ To get started, you’ll need to create an account on the Daraja API portal to u API. [How to get started with Daraja API](https://developer.safaricom.co.ke/Documentation). 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 +you’ll need to add your **_consumer key_**, **_consumer secret_** and **_pass key_** obtained from the Daraja API portal to your project. ## Features @@ -52,6 +53,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. +- [x] M-Pesa Express Query - Check the status of a Lipa Na M-Pesa Online Payment(M-Pesa Express). - [x] Dynamic QR - Generates a dynamic M-PESA QR code. - [x] Customer To Business (C2B) - [ ] Business To Customer (B2C) - Transact between an M-Pesa short code to a phone number @@ -67,7 +69,7 @@ The SDK offers the following functionalities from the Daraja API: ## Usage -# Android - Kotlin +# Android - Kotlin ### Setting Up @@ -91,7 +93,7 @@ The SDK offers the following functionalities from the Daraja API: dependencies { implementation 'io.github.victorkabata:daraja-multiplatform:0.9.3' } - ``` +``` @@ -141,8 +143,8 @@ accessTokenResult - To initiate M-Pesa Express(Lipa na M-Pesa Online) STK request, invoke the `mpesaExpress` function: -```Kotlin -val darajaPaymentResponse: DarajaResult = daraja.mpesaExpress( +```kotlin +val darajaPaymentResponse: DarajaResult = daraja.mpesaExpress( businessShortCode = "174379", amount = 1, phoneNumber = "07xxxxxxxx", @@ -151,12 +153,29 @@ val darajaPaymentResponse: DarajaResult = daraja.mpesaExp accountReference = "CompanyName" ) -darajaPaymentResponse - .onSuccess { paymentResponse -> - // Successfully requested M-Pesa STK request - }.onFailure { error -> - // Failed to request M-Pesa STK - } +darajaPaymentResponse.onSuccess { paymentResponse -> + // Successfully requested M-Pesa STK request +}.onFailure { error -> + // Failed to request M-Pesa STK +} +``` + +### Query M-Pesa Express STK + +- To check the status of M-Pesa Express(Lipa na M-Pesa Online) STK request, invoke the `mpesaExpressQuery` function: + +```kotlin +val darajaMpesaExpressQuery:DarajaResult = daraja.mpesaExpressQuery( + businessShortCode = "174379", + timeStamp = "20160216165627", + checkOutRequestID = "ws_CO_260520211133524545" +) + +darajaMpesaExpressQuery.onSuccess{ mpesaExpressQuery-> + // Successfully request M-Pesa STK request status +}.onFailure{ error-> + // Failed to request M-Pesa STK status +} ``` ### Generate Dynamic QR Code @@ -258,13 +277,13 @@ accountBalanceResponse.onSuccess { } ``` -# iOS - Swift +# iOS - Swift ### Setting Up -- To add ___DarajaMultiplatform___ package to your Xcode Project, open your Xcode project, navigate - to the File tab within the macOS bar and click __Select Packages__ then __Add Package Dependency - __. Enter the package name ie. DarajaMultiplatform or the URL package GitHub URL: +- To add **_DarajaMultiplatform_** package to your Xcode Project, open your Xcode project, navigate + to the File tab within the macOS bar and click **Select Packages** then **Add Package Dependency + **. Enter the package name ie. DarajaMultiplatform or the URL package GitHub URL: ```curl https://github.com/VictorKabata/DarajaSwiftPackage.git @@ -326,6 +345,25 @@ var darajaResponse = daraja.mpesaExpress( ``` +### Query M-Pesa Express STK + +- To check the status of M-Pesa Express(Lipa na M-Pesa Online) STK request, invoke the `mpesaExpressQuery` function: + +```swift +var mpesaExpressQuery = daraja.mpesaExpressQuery( + businessShortCode: "174379", + timestamp: "20160216165627", + checkoutRequestID: "ws_CO_260520211133524545" +) + +mpesaExpressQuery.onSuccess(action:{ data in +// Successfully requested M-Pesa STK request status +}) +.onFailure(action: {error in +// Failed to request M-Pesa STK status +}) +``` + ### Generate Dynamic QR Code - Generate a dynamic m-pesa qr code @@ -340,10 +378,10 @@ var darajaQrCode= daraja.generateDynamicQr( size = 400 ) -darajaQrCode.onSuccess(action:{data in +darajaQrCode.onSuccess(action:{data in // Successfully generated a QR code in base64 string format }) -.onFailure(action: {error in +.onFailure(action: {error in // Failed to generate a QR code }) ``` @@ -383,4 +421,4 @@ accountBalanceResponse.onSuccess(action: { data in }).onFailure(action: { error in // Failed to request account balance }) -``` +``` \ No newline at end of file diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index d54b2437..21835087 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -31,6 +31,7 @@ import com.vickbt.darajakmp.network.models.DynamicQrResponse import com.vickbt.darajakmp.network.models.MpesaExpressRequest import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.network.models.QueryMpesaExpressRequest +import com.vickbt.darajakmp.network.models.QueryMpesaExpressResponse import com.vickbt.darajakmp.utils.C2BResponseType import com.vickbt.darajakmp.utils.DarajaEnvironment import com.vickbt.darajakmp.utils.DarajaIdentifierType @@ -192,7 +193,7 @@ class Daraja( businessShortCode: String, timestamp: String, checkoutRequestID: String - ) = runBlocking(Dispatchers.IO) { + ):DarajaResult = runBlocking(Dispatchers.IO) { val darajaPassword = getDarajaPassword( shortCode = businessShortCode, passkey = passKey ?: "", From 47ee704e6ff1309070a11e710c404a45f23f7b05 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 29 Jul 2024 19:57:40 +0300 Subject: [PATCH 6/8] Linting --- 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 21835087..0fdf8ceb 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -193,7 +193,7 @@ class Daraja( businessShortCode: String, timestamp: String, checkoutRequestID: String - ):DarajaResult = runBlocking(Dispatchers.IO) { + ): DarajaResult = runBlocking(Dispatchers.IO) { val darajaPassword = getDarajaPassword( shortCode = businessShortCode, passkey = passKey ?: "", From 85e85cf91e8623f124f6d94386dd6c14ebb260fa Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 29 Jul 2024 19:59:24 +0300 Subject: [PATCH 7/8] Fixed android sample issue --- app-android/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app-android/build.gradle.kts b/app-android/build.gradle.kts index c93c6891..423bd2d4 100644 --- a/app-android/build.gradle.kts +++ b/app-android/build.gradle.kts @@ -8,13 +8,13 @@ plugins { android { namespace = "com.vickikbt.app_android" - compileSdk = 33 + compileSdk = 34 defaultConfig { applicationId = "com.vickikbt.app_android" minSdk = 21 - targetSdk = 33 + targetSdk = 34 versionCode = 1 versionName = "1.0" From 89ae741e85c95dd8abdac6fa4d3f1d6b916b9130 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 29 Jul 2024 22:41:17 +0300 Subject: [PATCH 8/8] Update kover minBound --- 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 b69f86f6..83a77e91 100644 --- a/daraja/build.gradle.kts +++ b/daraja/build.gradle.kts @@ -155,7 +155,7 @@ kover { reports { verify { rule { - minBound(30) + minBound(20) } }