From 495a31784dab674c1dc6c377c3aa4f09573fa50e Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 12 Aug 2024 12:48:24 +0300 Subject: [PATCH 01/14] Linting --- .github/workflows/publish_android.yml | 3 --- daraja/build.gradle.kts | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/publish_android.yml b/.github/workflows/publish_android.yml index 6fa21c67..9d9e91ae 100644 --- a/.github/workflows/publish_android.yml +++ b/.github/workflows/publish_android.yml @@ -66,9 +66,6 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - name: Clean & Build - run: ./gradlew :daraja:clean :daraja:build --stacktrace - - name: Sign and Publish Android Library run: ./gradlew :daraja:publishAndroidReleasePublicationToSonatypeRepository --max-workers 1 --stacktrace env: diff --git a/daraja/build.gradle.kts b/daraja/build.gradle.kts index 31cab01e..071e669e 100644 --- a/daraja/build.gradle.kts +++ b/daraja/build.gradle.kts @@ -31,6 +31,7 @@ plugins { alias(libs.plugins.gradleVersionUpdate) id("maven-publish") + // id("com.vanniktech.maven.publish") version "0.29.0" id("signing") alias(libs.plugins.multiplatformSwiftPackage) } From a8fa829a5815648d3572807042c041f99791c005 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 12 Aug 2024 13:11:47 +0300 Subject: [PATCH 02/14] Update agp to version 8.1.4 from 7.4.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 b9d2aedb..098266ad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "2.0.0" -agp = "7.4.2" +agp = "8.1.4" ktLint = "11.6.0" detekt = "1.19.0" spotless = "6.2.2" From 1e316460ecad3efc4ee3a02591fee840b471796d Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 12 Aug 2024 13:12:15 +0300 Subject: [PATCH 03/14] Update gradle wrapper from 8.0 to 8.1 --- gradle.properties | 4 +++- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 39b7dcee..0ede7111 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,6 @@ kotlin.native.ignoreDisabledTargets=true kotlin.mpp.stability.nowarn=true kotlin.mpp.androidSourceSetLayoutVersion1.nowarn=true -org.gradle.caching=true \ No newline at end of file +org.gradle.caching=true +android.defaults.buildfeatures.buildconfig=true +android.nonFinalResIds=false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bf314dd7..85f3f009 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Aug 04 15:02:45 EAT 2023 +#Mon Aug 12 13:06:02 EAT 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From fba9b099fe0ac642459978fcb48358a11c8c9b2a Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 12 Aug 2024 14:58:16 +0300 Subject: [PATCH 04/14] Fix build failure issue --- daraja/build.gradle.kts | 7 ++----- gradle.properties | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/daraja/build.gradle.kts b/daraja/build.gradle.kts index 071e669e..3a06a466 100644 --- a/daraja/build.gradle.kts +++ b/daraja/build.gradle.kts @@ -178,11 +178,8 @@ 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") diff --git a/gradle.properties b/gradle.properties index 0ede7111..1b8236cd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,6 @@ kotlin.code.style=official #Android android.useAndroidX=true android.nonTransitiveRClass=true -android.disableAutomaticComponentCreation=true #MPP kotlin.mpp.enableCInteropCommonization=true From aa658f14b84a93a7fa04055f0d71796578308cc5 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 12 Aug 2024 15:29:57 +0300 Subject: [PATCH 05/14] Fixed failing lint check in request dto models --- .editorconfig | 8 +- app-android/build.gradle.kts | 2 +- .../app_android/DarajaKmpApplication.kt | 1 - .../app_android/di/PresentationModule.kt | 23 +- .../app_android/ui/activity/MainActivity.kt | 2 +- .../app_android/ui/screens/home/HomeScreen.kt | 84 ++--- .../ui/screens/home/HomeViewModel.kt | 20 +- .../com/vickbt/app_android/ui/theme/Theme.kt | 42 +-- .../com/vickbt/app_android/ui/theme/Type.kt | 20 +- .../app_desktop/ui/screens/home/HomeScreen.kt | 60 ++-- .../app_desktop/ui/screens/main/MainScreen.kt | 13 +- .../vickikbt/app_desktop/ui/theme/Theme.kt | 32 +- .../com/vickikbt/app_desktop/ui/theme/Type.kt | 20 +- build.gradle.kts | 2 +- daraja/build.gradle.kts | 36 ++- .../kotlin/com/vickbt/darajakmp/Daraja.kt | 297 ++++++++++-------- .../darajakmp/network/DarajaApiService.kt | 76 +++-- .../network/DarajaHttpClientFactory.kt | 59 ++-- .../darajakmp/network/DarajaSafeApiCall.kt | 39 +-- .../network/models/AccountBalanceRequest.kt | 9 +- .../network/models/AccountBalanceResponse.kt | 5 +- .../network/models/C2BRegistrationRequest.kt | 5 +- .../darajakmp/network/models/C2BRequest.kt | 6 +- .../darajakmp/network/models/C2BResponse.kt | 4 +- .../network/models/DarajaException.kt | 6 +- .../darajakmp/network/models/DarajaToken.kt | 6 +- .../models/DarajaTransactionRequest.kt | 7 +- .../models/DarajaTransactionResponse.kt | 9 +- .../network/models/DynamicQrRequest.kt | 7 +- .../network/models/DynamicQrResponse.kt | 5 +- .../network/models/MpesaExpressRequest.kt | 29 +- .../network/models/MpesaExpressResponse.kt | 8 +- .../models/QueryMpesaExpressRequest.kt | 5 +- .../models/QueryMpesaExpressResponse.kt | 7 +- .../vickbt/darajakmp/utils/DarajaConfigs.kt | 18 +- .../vickbt/darajakmp/utils/DarajaResult.kt | 8 +- .../com/vickbt/darajakmp/utils/Utils.kt | 16 +- .../darajakmp/network/DarajaApiServiceTest.kt | 185 +++++------ .../darajakmp/network/MockDarajaHttpClient.kt | 101 +++--- .../darajakmp/utils/DarajaResultTest.kt | 12 +- .../com/vickbt/darajakmp/utils/UtilsTest.kt | 11 +- gradle/libs.versions.toml | 2 +- 42 files changed, 664 insertions(+), 643 deletions(-) diff --git a/.editorconfig b/.editorconfig index 7a0773af..612e3fae 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,5 @@ -[*.{kt,kts}] -disabled_rules = import-ordering, experimental:argument-list-wrapping, package-name -insert_final_newline = true \ No newline at end of file +root = true + +[*] +ktlint_standard_final-newline = disabled +ktlint_function_naming_ignore_when_annotated_with = Composable \ No newline at end of file diff --git a/app-android/build.gradle.kts b/app-android/build.gradle.kts index 423bd2d4..026b490e 100644 --- a/app-android/build.gradle.kts +++ b/app-android/build.gradle.kts @@ -29,7 +29,7 @@ android { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) } } diff --git a/app-android/src/main/java/com/vickbt/app_android/DarajaKmpApplication.kt b/app-android/src/main/java/com/vickbt/app_android/DarajaKmpApplication.kt index 33a59088..92a1fff6 100644 --- a/app-android/src/main/java/com/vickbt/app_android/DarajaKmpApplication.kt +++ b/app-android/src/main/java/com/vickbt/app_android/DarajaKmpApplication.kt @@ -24,7 +24,6 @@ import org.koin.core.context.startKoin import org.koin.core.logger.Level class DarajaKmpApplication : Application() { - override fun onCreate() { super.onCreate() diff --git a/app-android/src/main/java/com/vickbt/app_android/di/PresentationModule.kt b/app-android/src/main/java/com/vickbt/app_android/di/PresentationModule.kt index 721a356f..bf9244e2 100644 --- a/app-android/src/main/java/com/vickbt/app_android/di/PresentationModule.kt +++ b/app-android/src/main/java/com/vickbt/app_android/di/PresentationModule.kt @@ -21,16 +21,17 @@ import com.vickbt.darajakmp.Daraja import org.koin.androidx.viewmodel.dsl.viewModelOf import org.koin.dsl.module -val presentationModule = module { +val presentationModule = + module { - single { - Daraja.Builder() - .setConsumerKey("zg1m1CbMGx8E2BqVThHIJHFMWSnVJ4XA") - .setConsumerSecret("z4CAY2TUw6rprEvy") - .setPassKey("bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919") - .isSandbox() - .build() - } + single { + Daraja.Builder() + .setConsumerKey("zg1m1CbMGx8E2BqVThHIJHFMWSnVJ4XA") + .setConsumerSecret("z4CAY2TUw6rprEvy") + .setPassKey("bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919") + .isSandbox() + .build() + } - viewModelOf(::HomeViewModel) -} + viewModelOf(::HomeViewModel) + } diff --git a/app-android/src/main/java/com/vickbt/app_android/ui/activity/MainActivity.kt b/app-android/src/main/java/com/vickbt/app_android/ui/activity/MainActivity.kt index 7c74efad..7fbc073b 100644 --- a/app-android/src/main/java/com/vickbt/app_android/ui/activity/MainActivity.kt +++ b/app-android/src/main/java/com/vickbt/app_android/ui/activity/MainActivity.kt @@ -36,7 +36,7 @@ class MainActivity : ComponentActivity() { DarajaKmpTheme { Surface( modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background + color = MaterialTheme.colorScheme.background, ) { HomeScreen() } 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 e5c9d558..0b3e1890 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 @@ -76,26 +76,29 @@ fun HomeScreen(viewModel: HomeViewModel = get()) { val (card, button) = createRefs() ElevatedCard( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(.35f) - .padding(horizontal = 24.dp) - .constrainAs(card) { - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - start.linkTo(parent.start) - end.linkTo(parent.end) - }, - shape = RoundedCornerShape(6.dp) + modifier = + Modifier + .fillMaxWidth() + .fillMaxHeight(.35f) + .padding(horizontal = 24.dp) + .constrainAs(card) { + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + start.linkTo(parent.start) + end.linkTo(parent.end) + }, + shape = RoundedCornerShape(6.dp), ) { Column( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 20.dp), - verticalArrangement = Arrangement.spacedBy( - space = 24.dp, - alignment = Alignment.CenterVertically - ) + modifier = + Modifier + .fillMaxSize() + .padding(horizontal = 20.dp), + verticalArrangement = + Arrangement.spacedBy( + space = 24.dp, + alignment = Alignment.CenterVertically, + ), ) { OutlinedTextField( modifier = Modifier.fillMaxWidth(), @@ -103,13 +106,14 @@ fun HomeScreen(viewModel: HomeViewModel = get()) { onValueChange = { amount = it.toInt() }, singleLine = true, maxLines = 1, - textStyle = TextStyle( - fontSize = 20.sp, - color = MaterialTheme.colorScheme.onBackground - ), + textStyle = + TextStyle( + fontSize = 20.sp, + color = MaterialTheme.colorScheme.onBackground, + ), label = { Text(text = "Amount") }, colors = TextFieldDefaults.outlinedTextFieldColors(focusedBorderColor = MaterialTheme.colorScheme.primary), - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number) + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), ) OutlinedTextField( @@ -118,26 +122,28 @@ fun HomeScreen(viewModel: HomeViewModel = get()) { onValueChange = { it.let { phoneNumber = it } }, singleLine = true, maxLines = 1, - textStyle = TextStyle( - fontSize = 20.sp, - color = MaterialTheme.colorScheme.onBackground - ), + textStyle = + TextStyle( + fontSize = 20.sp, + color = MaterialTheme.colorScheme.onBackground, + ), label = { Text(text = "Phone Number") }, colors = TextFieldDefaults.outlinedTextFieldColors(focusedBorderColor = MaterialTheme.colorScheme.primary), - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Phone) + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Phone), ) } } FloatingActionButton( - modifier = Modifier - .size(64.dp) - .constrainAs(button) { - top.linkTo(card.bottom) - bottom.linkTo(card.bottom) - start.linkTo(card.start) - end.linkTo(card.end) - }, + modifier = + Modifier + .size(64.dp) + .constrainAs(button) { + top.linkTo(card.bottom) + bottom.linkTo(card.bottom) + start.linkTo(card.start) + end.linkTo(card.end) + }, shape = CircleShape, containerColor = colorResource(id = R.color.theme_color), contentColor = Color.White, @@ -149,14 +155,14 @@ fun HomeScreen(viewModel: HomeViewModel = get()) { phoneNumber = phoneNumber, transactionDesc = "Mpesa payment", callbackUrl = "https://mydomain.com/path", - accountReference = "Daraja KMP Android" + accountReference = "Daraja KMP Android", ) - } + }, ) { Icon( modifier = Modifier.size(28.dp), painter = painterResource(id = R.drawable.ic_payment), - contentDescription = "Pay" + contentDescription = "Pay", ) } } 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 59bf955b..b7e36c33 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 @@ -26,7 +26,6 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch class HomeViewModel constructor(private val daraja: Daraja) : ViewModel() { - private val _mpesaResponse = MutableStateFlow?>(null) val mpesaResponse get() = _mpesaResponse.asStateFlow() @@ -36,16 +35,17 @@ class HomeViewModel constructor(private val daraja: Daraja) : ViewModel() { phoneNumber: String, transactionDesc: String, callbackUrl: String, - accountReference: String + accountReference: String, ) = viewModelScope.launch { - val response = daraja.mpesaExpress( - businessShortCode = businessShortCode.trim(), - amount = amount, - phoneNumber = phoneNumber.trim(), - transactionDesc = transactionDesc, - callbackUrl = callbackUrl.trim(), - accountReference = accountReference.trim() - ) + val response = + daraja.mpesaExpress( + businessShortCode = businessShortCode.trim(), + amount = amount, + phoneNumber = phoneNumber.trim(), + transactionDesc = transactionDesc, + callbackUrl = callbackUrl.trim(), + accountReference = accountReference.trim(), + ) _mpesaResponse.value = response } 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 0438a959..5dc98855 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 @@ -31,17 +31,18 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.ViewCompat -private val DarkColorScheme = darkColorScheme( - primary = Purple80, - secondary = PurpleGrey80, - tertiary = Pink80 -) - -private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40 +private val DarkColorScheme = + darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80, + ) +private val LightColorScheme = + lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40, /* Other default colors to override background = Color(0xFFFFFBFE), surface = Color(0xFFFFFBFE), @@ -51,22 +52,23 @@ private val LightColorScheme = lightColorScheme( onBackground = Color(0xFF1C1B1F), onSurface = Color(0xFF1C1B1F), */ -) + ) @Composable fun DarajaKmpTheme( darkTheme: Boolean = isSystemInDarkTheme(), dynamicColor: Boolean = true, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { - val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - val context = LocalContext.current - if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + val colorScheme = + when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + darkTheme -> DarkColorScheme + else -> LightColorScheme } - darkTheme -> DarkColorScheme - else -> LightColorScheme - } val view = LocalView.current if (!view.isInEditMode) { SideEffect { @@ -78,6 +80,6 @@ fun DarajaKmpTheme( MaterialTheme( colorScheme = colorScheme, typography = Typography, - content = content + content = content, ) } 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 ff58a6e5..f7085633 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 @@ -23,14 +23,16 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp // Set of Material typography styles to start with -val Typography = Typography( - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp - ) +val Typography = + Typography( + bodyLarge = + TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp, + ), /* Other default text styles to override titleLarge = TextStyle( fontFamily = FontFamily.Default, @@ -47,4 +49,4 @@ val Typography = Typography( letterSpacing = 0.5.sp ) */ -) + ) 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 2b5db3b7..09f39fcd 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 @@ -60,32 +60,35 @@ fun HomeScreen() { .setConsumerSecret("z4CAY2TUw6rprEvy") .setPassKey("bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919") .isSandbox() - .build() + .build(), ) } Box( - modifier = Modifier - .fillMaxSize() - .padding(vertical = 16.dp, horizontal = 8.dp) + modifier = + Modifier + .fillMaxSize() + .padding(vertical = 16.dp, horizontal = 8.dp), ) { Text( - modifier = Modifier - .align(Alignment.TopCenter) - .padding(horizontal = 2.dp), + modifier = + Modifier + .align(Alignment.TopCenter) + .padding(horizontal = 2.dp), text = "Daraja Multiplatform Desktop", fontWeight = FontWeight.ExtraBold, fontSize = 32.sp, - textAlign = TextAlign.Center + textAlign = TextAlign.Center, ) Column( modifier = Modifier.align(Alignment.Center), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy( - space = 42.dp, - alignment = Alignment.CenterVertically - ) + verticalArrangement = + Arrangement.spacedBy( + space = 42.dp, + alignment = Alignment.CenterVertically, + ), ) { OutlinedTextField( modifier = Modifier.fillMaxWidth(.8f), @@ -93,13 +96,14 @@ fun HomeScreen() { onValueChange = { amount = it.toInt() }, singleLine = true, maxLines = 1, - textStyle = TextStyle( - fontSize = 20.sp, - color = MaterialTheme.colors.onBackground - ), + textStyle = + TextStyle( + fontSize = 20.sp, + color = MaterialTheme.colors.onBackground, + ), label = { Text(text = "Amount") }, colors = TextFieldDefaults.outlinedTextFieldColors(focusedBorderColor = MaterialTheme.colors.primary), - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number) + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), ) OutlinedTextField( @@ -108,33 +112,39 @@ fun HomeScreen() { onValueChange = { phoneNumber = it }, singleLine = true, maxLines = 1, - textStyle = TextStyle( - fontSize = 20.sp, - color = MaterialTheme.colors.onBackground - ), + textStyle = + TextStyle( + fontSize = 20.sp, + color = MaterialTheme.colors.onBackground, + ), label = { Text(text = "Phone Number") }, colors = TextFieldDefaults.outlinedTextFieldColors(focusedBorderColor = MaterialTheme.colors.primary), - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Phone) + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Phone), ) } Button( modifier = Modifier.align(Alignment.BottomCenter), - onClick = { initiateMpesaStk(daraja, tillNumber, amount, phoneNumber) } + onClick = { initiateMpesaStk(daraja, tillNumber, amount, phoneNumber) }, ) { Text(text = "Make Payment", fontSize = 20.sp) } } } -fun initiateMpesaStk(daraja: Daraja, tillNumber: String, amount: Int, phoneNumber: String) { +fun initiateMpesaStk( + daraja: Daraja, + tillNumber: String, + amount: Int, + phoneNumber: String, +) { daraja.mpesaExpress( businessShortCode = tillNumber, amount = amount, phoneNumber = phoneNumber, transactionDesc = "Mpesa payment", callbackUrl = "https://mydomain.com/path", - accountReference = "Daraja KMP Android" + accountReference = "Daraja KMP Android", ).onSuccess { println(message = "On success block called: $it") }.onFailure { diff --git a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/main/MainScreen.kt b/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/main/MainScreen.kt index 179a7b3d..43ce3fee 100644 --- a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/main/MainScreen.kt +++ b/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/main/MainScreen.kt @@ -25,19 +25,20 @@ import androidx.compose.ui.window.ApplicationScope import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.rememberWindowState -import com.vickikbt.app_desktop.ui.screens.home.HomeScreen import com.vickikbt.app_android.ui.theme.DarajaKmpTheme +import com.vickikbt.app_desktop.ui.screens.home.HomeScreen @Composable fun MainScreen(applicationScope: ApplicationScope) { Window( onCloseRequest = { applicationScope.exitApplication() }, title = "Daraja Multiplatform Desktop", - state = rememberWindowState( - position = WindowPosition.Aligned(Alignment.Center), - width = Dp.Unspecified, - height = Dp.Unspecified - ) + state = + rememberWindowState( + position = WindowPosition.Aligned(Alignment.Center), + width = Dp.Unspecified, + height = Dp.Unspecified, + ), ) { DarajaKmpTheme(darkTheme = true) { Surface(color = MaterialTheme.colors.surface) { 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 a5111015..6a610f30 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 @@ -22,15 +22,16 @@ import androidx.compose.material.darkColors import androidx.compose.material.lightColors import androidx.compose.runtime.Composable -private val DarkColorScheme = darkColors( - primary = Purple80, - secondary = PurpleGrey80 -) - -private val LightColorScheme = lightColors( - primary = Purple40, - secondary = PurpleGrey40 +private val DarkColorScheme = + darkColors( + primary = Purple80, + secondary = PurpleGrey80, + ) +private val LightColorScheme = + lightColors( + primary = Purple40, + secondary = PurpleGrey40, /* Other default colors to override background = Color(0xFFFFFBFE), surface = Color(0xFFFFFBFE), @@ -40,21 +41,22 @@ private val LightColorScheme = lightColors( onBackground = Color(0xFF1C1B1F), onSurface = Color(0xFF1C1B1F), */ -) + ) @Composable fun DarajaKmpTheme( darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { - val colorScheme = when { - darkTheme -> DarkColorScheme - else -> LightColorScheme - } + val colorScheme = + when { + darkTheme -> DarkColorScheme + else -> LightColorScheme + } MaterialTheme( colors = colorScheme, typography = Typography, - content = content + content = content, ) } 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 a4705db9..8bb67f83 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 @@ -23,14 +23,16 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp // Set of Material typography styles to start with -val Typography = Typography( - h4 = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp - ) +val Typography = + Typography( + h4 = + TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp, + ), /* Other default text styles to override titleLarge = TextStyle( fontFamily = FontFamily.Default, @@ -47,4 +49,4 @@ val Typography = Typography( letterSpacing = 0.5.sp ) */ -) + ) diff --git a/build.gradle.kts b/build.gradle.kts index eeb43f99..fdd253b2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,7 +39,7 @@ subprojects { target("**/*.kt") licenseHeaderFile( rootProject.file("${project.rootDir}/spotless/copyright.kt"), - "^(package|object|import|interface)" + "^(package|object|import|interface)", ) } } diff --git a/daraja/build.gradle.kts b/daraja/build.gradle.kts index 3a06a466..64a4c41e 100644 --- a/daraja/build.gradle.kts +++ b/daraja/build.gradle.kts @@ -8,14 +8,17 @@ val dokkaOutputDir = buildDir.resolve("reports/dokka") val releasesRepoUrl = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") val snapshotsRepoUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") -fun Project.get(key: String, defaultValue: String = "Invalid value $key") = - gradleLocalProperties(rootDir).getProperty(key)?.toString() ?: System.getenv(key)?.toString() - ?: defaultValue +fun Project.get( + key: String, + defaultValue: String = "Invalid value $key", +) = gradleLocalProperties(rootDir).getProperty(key)?.toString() ?: System.getenv(key)?.toString() + ?: defaultValue fun isNonStable(version: String): Boolean { - val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { - version.uppercase(Locale.getDefault()).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() @@ -151,11 +154,12 @@ val deleteDokkaOutputDir by tasks.register("deleteDokkaOutputDirectory") delete(dokkaOutputDir) } -val javadocJar = tasks.register("javadocJar") { - dependsOn(deleteDokkaOutputDir, tasks.dokkaHtml) - archiveClassifier.set("javadoc") - from(dokkaOutputDir) -} +val javadocJar = + tasks.register("javadocJar") { + dependsOn(deleteDokkaOutputDir, tasks.dokkaHtml) + archiveClassifier.set("javadoc") + from(dokkaOutputDir) + } kover { reports { @@ -178,8 +182,12 @@ 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") @@ -237,7 +245,7 @@ publishing { useInMemoryPgpKeys( signingKeyId, signingKeyPassword, - signingKey + 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 27ab3a54..a0b8f8eb 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -62,12 +62,12 @@ class Daraja( private val consumerKey: String?, private val consumerSecret: String?, private val passKey: String?, - private val environment: DarajaEnvironment? = DarajaEnvironment.SANDBOX_ENVIRONMENT + private val environment: DarajaEnvironment? = DarajaEnvironment.SANDBOX_ENVIRONMENT, ) { - - private val darajaHttpClientFactory: HttpClient = DarajaHttpClientFactory( - environment = environment ?: DarajaEnvironment.SANDBOX_ENVIRONMENT - ).createDarajaHttpClient() + private val darajaHttpClientFactory: HttpClient = + DarajaHttpClientFactory( + environment = environment ?: DarajaEnvironment.SANDBOX_ENVIRONMENT, + ).createDarajaHttpClient() /**Creates instance of [Daraja] * @@ -80,9 +80,8 @@ class Daraja( @ObjCName(swiftName = "consumerKey") private var consumerKey: String? = null, @ObjCName(swiftName = "consumerSecret") private var consumerSecret: String? = null, @ObjCName(swiftName = "passKey") private var passKey: String? = null, - @ObjCName(swiftName = "darajaEnvironment") private var environment: DarajaEnvironment? = null + @ObjCName(swiftName = "darajaEnvironment") private var environment: DarajaEnvironment? = null, ) { - /**Provides [consumerKey] provided by Daraja API * * @param consumerKey Daraja API consumer key @@ -95,8 +94,7 @@ class Daraja( * @param consumerSecret Daraja API consumer secret * */ @ObjCName(swiftName = "withConsumerSecret") - fun setConsumerSecret(consumerSecret: String) = - apply { this.consumerSecret = consumerSecret } + fun setConsumerSecret(consumerSecret: String) = apply { this.consumerSecret = consumerSecret } /**Provides [passKey] provided by Daraja API * @@ -113,30 +111,34 @@ class Daraja( /**Create an instance of [Daraja] object with [consumerKey], [consumerSecret] and [passKey] provided*/ @ObjCName(swiftName = "init") - fun build(): Daraja = Daraja( - consumerKey = consumerKey, - consumerSecret = consumerSecret, - passKey = passKey, - environment = environment - ) + fun build(): Daraja = + Daraja( + consumerKey = consumerKey, + consumerSecret = consumerSecret, + passKey = passKey, + environment = environment, + ) } /**Create instance of [DarajaApiService]*/ - private val darajaApiService: DarajaApiService = DarajaApiService( - httpClient = darajaHttpClientFactory, - consumerKey = consumerKey ?: throw DarajaException(errorMessage = "Consumer key is null"), - consumerSecret = consumerSecret - ?: throw DarajaException(errorMessage = "Consumer secret is null") - ) + private val darajaApiService: DarajaApiService = + DarajaApiService( + httpClient = darajaHttpClientFactory, + consumerKey = consumerKey ?: throw DarajaException(errorMessage = "Consumer key is null"), + consumerSecret = + consumerSecret + ?: throw DarajaException(errorMessage = "Consumer secret is null"), + ) /**Request access token that is used to authenticate to Daraja APIs * * @return [DarajaToken] * */ @ObjCName(swiftName = "authorization") - fun authorization(): DarajaResult = runBlocking(Dispatchers.IO) { - 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]. * The response of the payment status will be sent to the [callbackUrl] provided. @@ -159,32 +161,35 @@ class Daraja( transactionType: DarajaTransactionType = DarajaTransactionType.CustomerPayBillOnline, transactionDesc: String, callbackUrl: String, - accountReference: String? = null - ): DarajaResult = runBlocking(Dispatchers.IO) { - val timestamp = Clock.System.now().getDarajaTimestamp() - - val darajaPassword = getDarajaPassword( - shortCode = businessShortCode, - passkey = passKey ?: throw DarajaException(errorMessage = "Pass key is null"), - timestamp = timestamp - ) - - val mpesaExpressRequest = MpesaExpressRequest( - businessShortCode = businessShortCode, - password = darajaPassword, - timestamp = timestamp, - transactionDesc = transactionDesc, - amount = amount.toString(), - transactionType = transactionType.name, - phoneNumber = phoneNumber.getDarajaPhoneNumber(), - callBackUrl = callbackUrl, - accountReference = accountReference ?: businessShortCode, - partyA = phoneNumber, - partyB = businessShortCode - ) - - darajaApiService.initiateMpesaExpress(mpesaExpressRequest = mpesaExpressRequest) - } + accountReference: String? = null, + ): DarajaResult = + runBlocking(Dispatchers.IO) { + val timestamp = Clock.System.now().getDarajaTimestamp() + + val darajaPassword = + getDarajaPassword( + shortCode = businessShortCode, + passkey = passKey ?: throw DarajaException(errorMessage = "Pass key is null"), + timestamp = timestamp, + ) + + val mpesaExpressRequest = + MpesaExpressRequest( + businessShortCode = businessShortCode, + password = darajaPassword, + timestamp = timestamp, + transactionDesc = transactionDesc, + amount = amount.toString(), + transactionType = transactionType.name, + phoneNumber = phoneNumber.getDarajaPhoneNumber(), + callBackUrl = callbackUrl, + accountReference = accountReference ?: businessShortCode, + partyA = phoneNumber, + partyB = businessShortCode, + ) + + 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. @@ -194,23 +199,26 @@ class Daraja( fun mpesaExpressQuery( businessShortCode: String, timestamp: String, - checkoutRequestID: String - ): DarajaResult = 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) - } + checkoutRequestID: String, + ): DarajaResult = + 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 * @@ -238,19 +246,21 @@ class Daraja( amount: Int, transactionCode: DarajaTransactionCode, cpi: String, - size: Int - ): DarajaResult = runBlocking(Dispatchers.IO) { - val dynamicQrRequest = DynamicQrRequest( - merchantName = merchantName, - referenceNumber = referenceNumber, - amount = amount, - transactionCode = transactionCode.name, - cpi = cpi, - size = size.toString() - ) - - darajaApiService.generateDynamicQr(dynamicQrRequest = dynamicQrRequest) - } + size: Int, + ): DarajaResult = + runBlocking(Dispatchers.IO) { + val dynamicQrRequest = + DynamicQrRequest( + merchantName = merchantName, + referenceNumber = referenceNumber, + amount = amount, + transactionCode = transactionCode.name, + cpi = cpi, + size = size.toString(), + ) + + darajaApiService.generateDynamicQr(dynamicQrRequest = dynamicQrRequest) + } /**Request the status of an Mpesa payment transaction * @@ -262,24 +272,27 @@ class Daraja( @ObjCName(swiftName = "transactionStatus") internal fun transactionStatus( businessShortCode: String, - checkoutRequestID: String - ): DarajaResult = runBlocking(Dispatchers.IO) { - val timestamp = Clock.System.now().getDarajaTimestamp() - val darajaPassword = getDarajaPassword( - shortCode = businessShortCode, - passkey = passKey ?: throw DarajaException(errorMessage = "Pass key is null"), - timestamp = timestamp - ) - - val darajaTransactionRequest = DarajaTransactionRequest( - businessShortCode = businessShortCode, - password = darajaPassword, - timestamp = timestamp, - checkoutRequestID = checkoutRequestID - ) - - darajaApiService.queryTransaction(darajaTransactionRequest) - } + checkoutRequestID: String, + ): DarajaResult = + runBlocking(Dispatchers.IO) { + val timestamp = Clock.System.now().getDarajaTimestamp() + val darajaPassword = + getDarajaPassword( + shortCode = businessShortCode, + passkey = passKey ?: throw DarajaException(errorMessage = "Pass key is null"), + timestamp = timestamp, + ) + + val darajaTransactionRequest = + DarajaTransactionRequest( + businessShortCode = businessShortCode, + password = darajaPassword, + timestamp = timestamp, + checkoutRequestID = checkoutRequestID, + ) + + darajaApiService.queryTransaction(darajaTransactionRequest) + } /**Transact between a phone number registered on M-Pesa to an M-Pesa shortcode * @@ -294,35 +307,39 @@ class Daraja( businessShortCode: Int, confirmationURL: String, validationURL: String? = null, - responseType: C2BResponseType? = C2BResponseType.COMPLETED - ): DarajaResult = runBlocking(Dispatchers.IO) { - val c2BRegistrationRequest = C2BRegistrationRequest( - confirmationURL = confirmationURL, - validationURL = validationURL, - responseType = responseType?.name?.lowercase(), - shortCode = businessShortCode - ) - - darajaApiService.c2bRegistration(c2bRegistrationRequest = c2BRegistrationRequest) - } + responseType: C2BResponseType? = C2BResponseType.COMPLETED, + ): DarajaResult = + runBlocking(Dispatchers.IO) { + val c2BRegistrationRequest = + C2BRegistrationRequest( + confirmationURL = confirmationURL, + validationURL = validationURL, + responseType = responseType?.name?.lowercase(), + shortCode = businessShortCode, + ) + + darajaApiService.c2bRegistration(c2bRegistrationRequest = c2BRegistrationRequest) + } internal fun c2b( amount: Int, billReferenceNumber: String, transactionType: DarajaTransactionType, phoneNumber: String, - businessShortCode: String - ): DarajaResult = runBlocking(Dispatchers.IO) { - val c2bRequest = C2BRequest( - amount = amount, - billReferenceNumber = billReferenceNumber, - commandID = transactionType.name, - phoneNumber = phoneNumber.getDarajaPhoneNumber().toLong(), - shortCode = if (transactionType.name == DarajaTransactionType.CustomerPayBillOnline.name) businessShortCode else billReferenceNumber - ) - - darajaApiService.c2b(c2bRequest = c2bRequest) - } + businessShortCode: String, + ): DarajaResult = + runBlocking(Dispatchers.IO) { + val c2bRequest = + C2BRequest( + amount = amount, + billReferenceNumber = billReferenceNumber, + commandID = transactionType.name, + phoneNumber = phoneNumber.getDarajaPhoneNumber().toLong(), + shortCode = if (transactionType.name == DarajaTransactionType.CustomerPayBillOnline.name) businessShortCode else billReferenceNumber, + ) + + darajaApiService.c2b(c2bRequest = c2bRequest) + } /**Request the account balance of a short code. This can be used for both B2C, buy goods and pay bill accounts. * @@ -345,22 +362,24 @@ class Daraja( identifierType: DarajaIdentifierType, remarks: String = "Account balance request", queueTimeOutURL: String, - resultURL: String - ): DarajaResult = runBlocking(Dispatchers.IO) { - val key = initiator + initiatorPassword - val securityCredential = key.encodeBase64() - - val accountBalanceRequest = AccountBalanceRequest( - initiator = initiator, - securityCredential = securityCredential, - commandId = commandId, - partyA = partyA, - identifierType = if (identifierType == DarajaIdentifierType.TILL_NUMBER) 2 else 4, - remarks = remarks, - queueTimeOutURL = queueTimeOutURL, - resultURL = resultURL - ) - - darajaApiService.accountBalance(accountBalanceRequest = accountBalanceRequest) - } + resultURL: String, + ): DarajaResult = + runBlocking(Dispatchers.IO) { + val key = initiator + initiatorPassword + val securityCredential = key.encodeBase64() + + val accountBalanceRequest = + AccountBalanceRequest( + initiator = initiator, + securityCredential = securityCredential, + commandId = commandId, + partyA = partyA, + identifierType = if (identifierType == DarajaIdentifierType.TILL_NUMBER) 2 else 4, + remarks = remarks, + queueTimeOutURL = queueTimeOutURL, + resultURL = resultURL, + ) + + darajaApiService.accountBalance(accountBalanceRequest = accountBalanceRequest) + } } 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 c15d3ff5..5801de7c 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt @@ -55,33 +55,36 @@ internal class DarajaApiService( private val httpClient: HttpClient, private val consumerKey: String, private val consumerSecret: String, - private val inMemoryCache: Cache = Cache.Builder() - .expireAfterWrite(3600.toDuration(DurationUnit.SECONDS)).build() + private val inMemoryCache: Cache = + Cache.Builder() + .expireAfterWrite(3600.toDuration(DurationUnit.SECONDS)).build(), ) { - /** 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" - val base64EncodedKey = key.encodeBase64() - - val accessToken = httpClient.get(urlString = DarajaEndpoints.REQUEST_ACCESS_TOKEN) { - headers { - append(HttpHeaders.Authorization, "Basic $base64EncodedKey") - } - }.body().also { darajaToken -> - inMemoryCache.put(key = 1, value = darajaToken) - } + internal suspend fun fetchAccessToken(): DarajaResult = + darajaSafeApiCall { + val key = "$consumerKey:$consumerSecret" + val base64EncodedKey = key.encodeBase64() + + val accessToken = + httpClient.get(urlString = DarajaEndpoints.REQUEST_ACCESS_TOKEN) { + headers { + append(HttpHeaders.Authorization, "Basic $base64EncodedKey") + } + }.body().also { darajaToken -> + inMemoryCache.put(key = 1, value = darajaToken) + } - return@darajaSafeApiCall accessToken - } + return@darajaSafeApiCall accessToken + } /**Initiate API call using the [httpClient] provided by Ktor to trigger Mpesa Express payment on Daraja API */ internal suspend fun initiateMpesaExpress(mpesaExpressRequest: MpesaExpressRequest): DarajaResult = darajaSafeApiCall { - val accessToken = inMemoryCache.get(1) { - fetchAccessToken().getOrThrow() - } + val accessToken = + inMemoryCache.get(1) { + fetchAccessToken().getOrThrow() + } return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.INITIATE_MPESA_EXPRESS) { headers { append(HttpHeaders.Authorization, "Bearer ${accessToken.accessToken}") } @@ -91,9 +94,10 @@ internal class DarajaApiService( internal suspend fun queryMpesaExpress(queryMpesaExpressRequest: QueryMpesaExpressRequest): DarajaResult = darajaSafeApiCall { - val accessToken = inMemoryCache.get(1) { - fetchAccessToken().getOrThrow() - } + val accessToken = + inMemoryCache.get(1) { + fetchAccessToken().getOrThrow() + } return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.QUERY_MPESA_EXPRESS) { headers { append(HttpHeaders.Authorization, "Bearer ${accessToken.accessToken}") } @@ -103,9 +107,10 @@ internal class DarajaApiService( internal suspend fun generateDynamicQr(dynamicQrRequest: DynamicQrRequest): DarajaResult = darajaSafeApiCall { - val accessToken = inMemoryCache.get(1) { - fetchAccessToken().getOrThrow() - } + val accessToken = + inMemoryCache.get(1) { + fetchAccessToken().getOrThrow() + } return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.DYNAMIC_QR) { headers { append(HttpHeaders.Authorization, "Bearer ${accessToken.accessToken}") } @@ -127,9 +132,10 @@ internal class DarajaApiService( internal suspend fun c2bRegistration(c2bRegistrationRequest: C2BRegistrationRequest): DarajaResult = darajaSafeApiCall { - val accessToken = inMemoryCache.get(1) { - fetchAccessToken().getOrThrow() - } + val accessToken = + inMemoryCache.get(1) { + fetchAccessToken().getOrThrow() + } return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.C2B_REGISTRATION_URL) { headers { append(HttpHeaders.Authorization, "Bearer ${accessToken.accessToken}") } @@ -139,9 +145,10 @@ internal class DarajaApiService( internal suspend fun c2b(c2bRequest: C2BRequest): DarajaResult = darajaSafeApiCall { - val accessToken = inMemoryCache.get(1) { - fetchAccessToken().getOrThrow() - } + val accessToken = + inMemoryCache.get(1) { + fetchAccessToken().getOrThrow() + } return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.INITIATE_C2B) { headers { append(HttpHeaders.Authorization, "Bearer ${accessToken.accessToken}") } @@ -151,9 +158,10 @@ internal class DarajaApiService( internal suspend fun accountBalance(accountBalanceRequest: AccountBalanceRequest): DarajaResult = darajaSafeApiCall { - val accessToken = inMemoryCache.get(1) { - fetchAccessToken().getOrThrow() - } + val accessToken = + inMemoryCache.get(1) { + fetchAccessToken().getOrThrow() + } return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.ACCOUNT_BALANCE) { headers { append(HttpHeaders.Authorization, "Bearer ${accessToken.accessToken}") } 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 b86cc4b2..1097e528 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaHttpClientFactory.kt @@ -35,7 +35,6 @@ import kotlinx.serialization.json.Json /**Initialize Ktor Http Client responsible for handling network operations*/ internal class DarajaHttpClientFactory(private val environment: DarajaEnvironment) { - private val baseURL = if (environment == DarajaEnvironment.SANDBOX_ENVIRONMENT) { DarajaEndpoints.SANDBOX_BASE_URL @@ -44,40 +43,42 @@ internal class DarajaHttpClientFactory(private val environment: DarajaEnvironmen } /**Initialize Ktor Http Client responsible for handling network operations*/ - internal fun createDarajaHttpClient() = HttpClient { - expectSuccess = true - addDefaultResponseValidation() + internal fun createDarajaHttpClient() = + HttpClient { + expectSuccess = true + addDefaultResponseValidation() - defaultRequest { - contentType(ContentType.Application.Json) + defaultRequest { + contentType(ContentType.Application.Json) - url { - host = baseURL - url { protocol = URLProtocol.HTTPS } + url { + host = baseURL + url { protocol = URLProtocol.HTTPS } + } } - } - install(ContentNegotiation) { - json( - Json { - prettyPrint = true - ignoreUnknownKeys = true - isLenient = true - } - ) - } + install(ContentNegotiation) { + json( + Json { + prettyPrint = true + ignoreUnknownKeys = true + isLenient = true + }, + ) + } - if (environment == DarajaEnvironment.SANDBOX_ENVIRONMENT) { - install(Logging) { - level = LogLevel.ALL - logger = object : Logger { - override fun log(message: String) { - Napier.i(tag = "Http Client", message = message) - } + if (environment == DarajaEnvironment.SANDBOX_ENVIRONMENT) { + install(Logging) { + level = LogLevel.ALL + logger = + object : Logger { + override fun log(message: String) { + Napier.i(tag = "Http Client", message = message) + } + } + }.also { + Napier.base(DebugAntilog()) } - }.also { - Napier.base(DebugAntilog()) } } - } } 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 9a4b6bdd..d5fe96c1 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaSafeApiCall.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaSafeApiCall.kt @@ -32,24 +32,25 @@ import io.ktor.util.network.UnresolvedAddressException * @return [DarajaResult] Returns data of type [T] on success * @throws DarajaException Throws expception of type [DarajaException] on failure * */ -internal suspend fun darajaSafeApiCall(apiCall: suspend () -> T): DarajaResult = try { - DarajaResult.Success(apiCall.invoke()) -} catch (e: RedirectResponseException) { - val error = parseNetworkError(e.response.body()) - DarajaResult.Failure(exception = error) -} catch (e: ClientRequestException) { - val error = parseNetworkError(e.response.body()) - DarajaResult.Failure(exception = error) -} catch (e: ServerResponseException) { - val error = parseNetworkError(e.response.body()) - DarajaResult.Failure(exception = error) -} catch (e: UnresolvedAddressException) { - val error = parseNetworkError(exception = e) - DarajaResult.Failure(exception = error) -} catch (e: Exception) { - val error = parseNetworkError(exception = e) - DarajaResult.Failure(exception = error) -} +internal suspend fun darajaSafeApiCall(apiCall: suspend () -> T): DarajaResult = + try { + DarajaResult.Success(apiCall.invoke()) + } catch (e: RedirectResponseException) { + val error = parseNetworkError(e.response.body()) + DarajaResult.Failure(exception = error) + } catch (e: ClientRequestException) { + val error = parseNetworkError(e.response.body()) + DarajaResult.Failure(exception = error) + } catch (e: ServerResponseException) { + val error = parseNetworkError(e.response.body()) + DarajaResult.Failure(exception = error) + } catch (e: UnresolvedAddressException) { + val error = parseNetworkError(exception = e) + DarajaResult.Failure(exception = error) + } catch (e: Exception) { + val error = parseNetworkError(exception = e) + DarajaResult.Failure(exception = error) + } /**Generate [DarajaException] from network or system error when making network calls * @@ -57,7 +58,7 @@ internal suspend fun darajaSafeApiCall(apiCall: suspend () -> T): Dara * */ internal suspend fun parseNetworkError( errorResponse: HttpResponse? = null, - exception: Exception? = null + exception: Exception? = null, ): DarajaException { return errorResponse?.body() ?: DarajaException(errorMessage = exception?.message) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/AccountBalanceRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/AccountBalanceRequest.kt index 72a97333..cca340f6 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/AccountBalanceRequest.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/AccountBalanceRequest.kt @@ -23,25 +23,18 @@ import kotlinx.serialization.Serializable internal data class AccountBalanceRequest( @SerialName("Initiator") val initiator: String, - @SerialName("SecurityCredential") val securityCredential: String, - @SerialName("CommandID") val commandId: String, - @SerialName("PartyA") val partyA: Int, - @SerialName("IdentifierType") val identifierType: Int, - @SerialName("Remarks") val remarks: String, - @SerialName("QueueTimeOutURL") val queueTimeOutURL: String, - @SerialName("ResultURL") - val resultURL: String + val resultURL: String, ) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/AccountBalanceResponse.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/AccountBalanceResponse.kt index a75db78f..f0d47cfb 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/AccountBalanceResponse.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/AccountBalanceResponse.kt @@ -23,13 +23,10 @@ import kotlinx.serialization.Serializable data class AccountBalanceResponse( @SerialName("OriginatorConversationID") val originatorConversationId: String, - @SerialName("ConversationID") val conversationId: String, - @SerialName("ResponseCode") val responseCode: String, - @SerialName("ResponseDescription") - val responseDescription: String + val responseDescription: String, ) 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 4f470319..daba6814 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 @@ -28,16 +28,13 @@ 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?, - /**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 + val shortCode: Int, ) 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 6519395e..7b8386bc 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 @@ -26,16 +26,12 @@ import kotlin.native.ObjCName internal data class C2BRequest( @SerialName("Amount") val amount: Int, - @SerialName("BillRefNumber") val billReferenceNumber: String, - @SerialName("CommandID") val commandID: String, - @SerialName("Msisdn") val phoneNumber: Long, - @SerialName("ShortCode") - val shortCode: String? + val shortCode: String?, ) 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 eba0e3ae..c7db9806 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 @@ -27,12 +27,10 @@ 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 + val responseDescription: String, ) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaException.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaException.kt index 29f65b44..96f35e61 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaException.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaException.kt @@ -16,9 +16,9 @@ package com.vickbt.darajakmp.network.models -import kotlin.native.ObjCName import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlin.native.ObjCName @ObjCName(swiftName = "DarajaException") @Serializable @@ -29,12 +29,10 @@ data class DarajaException( /**This is a unique requestID for the payment request.*/ @SerialName("requestId") var requestId: String? = "0", - /**This is a predefined code that indicates the reason for request failure.*/ @SerialName("errorCode") var errorCode: String? = "0", - /**This is a short descriptive message of the failure reason.*/ @SerialName("errorMessage") - var errorMessage: String? = null + var errorMessage: String? = null, ) : Exception(errorMessage) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaToken.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaToken.kt index ea26cd0a..dc987e8d 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaToken.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaToken.kt @@ -16,9 +16,9 @@ package com.vickbt.darajakmp.network.models -import kotlin.native.ObjCName import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlin.native.ObjCName @ObjCName(swiftName = "DarajaToken") @Serializable @@ -26,12 +26,10 @@ import kotlinx.serialization.Serializable * Response returned by Daraja API on successful access token request. * */ data class DarajaToken( - /**Access token to access other Daraja APIs.*/ @SerialName("access_token") val accessToken: String, - /**Token expiry time in seconds.*/ @SerialName("expires_in") - val expiresIn: String + val expiresIn: 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 cd58acfe..d8221ece 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 @@ -16,22 +16,19 @@ package com.vickbt.darajakmp.network.models -import kotlin.native.ObjCName import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlin.native.ObjCName @ObjCName(swiftName = "DarajaTransactionRequest") @Serializable internal data class DarajaTransactionRequest( @SerialName("BusinessShortCode") val businessShortCode: String, - @SerialName("Password") val password: String, - @SerialName("Timestamp") val timestamp: String, - @SerialName("CheckoutRequestID") - val checkoutRequestID: String + val checkoutRequestID: String, ) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaTransactionResponse.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaTransactionResponse.kt index c82421bc..bda199ae 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaTransactionResponse.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DarajaTransactionResponse.kt @@ -16,28 +16,23 @@ package com.vickbt.darajakmp.network.models -import kotlin.native.ObjCName import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlin.native.ObjCName @ObjCName(swiftName = "DarajaTransactionResponse") @Serializable data class DarajaTransactionResponse( @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 + val resultDescription: String, ) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DynamicQrRequest.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DynamicQrRequest.kt index 80da23e7..5532a27e 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DynamicQrRequest.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DynamicQrRequest.kt @@ -43,19 +43,14 @@ import kotlin.native.ObjCName internal data class DynamicQrRequest( @SerialName("MerchantName") val merchantName: String, - @SerialName("RefNo") val referenceNumber: String, - @SerialName("Amount") val amount: Int, - @SerialName("TrxCode") val transactionCode: String, - @SerialName("CPI") val cpi: String, - @SerialName("Size") - val size: String + val size: String, ) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DynamicQrResponse.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DynamicQrResponse.kt index 647be76a..c3cc9f3f 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DynamicQrResponse.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/DynamicQrResponse.kt @@ -32,13 +32,10 @@ import kotlin.native.ObjCName data class DynamicQrResponse( @SerialName("ResponseCode") val responseCode: String, - @SerialName("RequestID") val requestId: String, - @SerialName("ResponseDescription") val responseDescription: String, - @SerialName("QRCode") - val qrCode: String + val qrCode: 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 d3ac5d1e..5b7f7c9c 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 @@ -22,52 +22,27 @@ import kotlin.native.ObjCName @ObjCName(swiftName = "MpesaExpressRequest") @Serializable -/** - * Request body sent to Daraja API to request Mpesa Express payment. - * */ 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") val businessShortCode: String, - - /**This is the password used for encrypting the request sent: A base64 encoded string. (The base64 string is a combination of [Shortcode]+[Passkey]+[Timestamp]).*/ @SerialName("Password") val password: String, - - /**The mobile number to receive the STK pin prompt. This is the same as [partyA].*/ @SerialName("PhoneNumber") val phoneNumber: String, - - /**This is the Timestamp of the transaction in the format of YEAR+MONTH+DATE+HOUR+MINUTE+SECOND (YYYYMMDDHHMMSS).*/ @SerialName("Timestamp") val timestamp: String, - - /**This is the transaction type that is used to identify the transaction when sending the request to M-Pesa.*/ @SerialName("TransactionType") val transactionType: String, - - /**Money that customer pays to the [businessShortCode].*/ @SerialName("Amount") val amount: String, - - /**The phone number sending money.*/ - @SerialName("PartyA") // 🥳 + @SerialName("PartyA") val partyA: String, - - /**The organization receiving the funds. This is the same as [businessShortCode].*/ @SerialName("PartyB") val partyB: String, - - /**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.*/ @SerialName("CallBackURL") val callBackUrl: String, - - /**This is an alpha-numeric parameter that is defined by your system as an Identifier of the transaction for CustomerPayBillOnline transaction type.*/ @SerialName("AccountReference") val accountReference: String, - - /**This is any additional information/comment that can be sent along with the request from your system. Maximum of 13 Characters.*/ @SerialName("TransactionDesc") - val transactionDesc: String + val transactionDesc: String, ) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressResponse.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressResponse.kt index a02f45ab..13314915 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressResponse.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/models/MpesaExpressResponse.kt @@ -16,9 +16,9 @@ package com.vickbt.darajakmp.network.models -import kotlin.native.ObjCName import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlin.native.ObjCName @ObjCName(swiftName = "MpesaExpressResponse") @Serializable @@ -29,20 +29,16 @@ data class MpesaExpressResponse( /**This is a global unique Identifier for any submitted payment request.*/ @SerialName("MerchantRequestID") var merchantRequestID: String, - /**This is a global unique identifier of the processed checkout transaction request*/ @SerialName("CheckoutRequestID") var checkoutRequestID: String, - /**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.*/ @SerialName("ResponseCode") var responseCode: String, - /**This is an acknowledgment message from the API that gives the status of the request submission.*/ @SerialName("ResponseDescription") var responseDescription: String, - /**This is a message that your system can display to the Customer as an acknowledgement of the payment request submission.*/ @SerialName("CustomerMessage") - var customerMessage: String + var customerMessage: String, ) 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 a80dc52b..2c3d2e01 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 @@ -23,13 +23,10 @@ import kotlinx.serialization.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 + 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 index 4253c4b9..8f3da3c7 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 @@ -31,19 +31,14 @@ import kotlinx.serialization.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 + val resultDescription: 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 1200b917..65a98450 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt @@ -31,15 +31,18 @@ internal object DarajaEndpoints { } enum class DarajaTransactionType { - CustomerPayBillOnline, CustomerBuyGoodsOnline + CustomerPayBillOnline, + CustomerBuyGoodsOnline, } enum class DarajaEnvironment { - PRODUCTION_ENVIRONMENT, SANDBOX_ENVIRONMENT + PRODUCTION_ENVIRONMENT, + SANDBOX_ENVIRONMENT, } enum class C2BResponseType { - CANCELED, COMPLETED + CANCELED, + COMPLETED, } /** @@ -53,9 +56,14 @@ enum class C2BResponseType { * * @param [SB]: Sent to Business. Business number CPI in MSISDN format.*/ enum class DarajaTransactionCode { - BG, WA, PB, SM, SB + BG, + WA, + PB, + SM, + SB, } enum class DarajaIdentifierType { - TILL_NUMBER, SHORT_CODE + TILL_NUMBER, + SHORT_CODE, } 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 5e69f446..0ad00bac 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,14 @@ import kotlin.native.ObjCName @ObjCName(swiftName = "DarajaResult") sealed class DarajaResult { @ObjCName(swiftName = "Success") - data class Success(@ObjCName(swiftName = "data") val data: T) : DarajaResult() + data class Success( + @ObjCName(swiftName = "data") val data: T, + ) : DarajaResult() @ObjCName(swiftName = "Error") - data class Failure(@ObjCName(swiftName = "error") val exception: DarajaException) : + data class Failure( + @ObjCName(swiftName = "error") val exception: DarajaException, + ) : DarajaResult() // object Loading : DarajaResult() ToDo 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..9b153cc0 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/Utils.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/Utils.kt @@ -40,14 +40,20 @@ internal fun Instant.getDarajaTimestamp(): String { * 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 -} +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 { +internal fun getDarajaPassword( + shortCode: String, + passkey: String, + timestamp: String, +): String { val password = shortCode + passkey + timestamp return password.encodeBase64() 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 3323e827..9fa9277b 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt @@ -34,7 +34,6 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull class DarajaApiServiceTest { - private val mockDarajaHttpClient = MockDarajaHttpClient() private lateinit var mockKtorHttpClient: HttpClient @@ -43,31 +42,34 @@ class DarajaApiServiceTest { // Subject under test private lateinit var darajaApiService: DarajaApiService - private val darajaToken = DarajaToken( - accessToken = "wWAHdtiE4GCSGv2ocfzQ0WHefwAJ", - expiresIn = "3599" - ) - - private val mpesaExpressRequest = MpesaExpressRequest( - businessShortCode = "654321", - password = "password", - phoneNumber = "254708374149", - timestamp = "timestamp", - transactionType = DarajaTransactionType.CustomerPayBillOnline.name, - transactionDesc = "Transaction description", - amount = "1", - partyA = "254708374149", - partyB = "654321", - callBackUrl = "https://mydomain.com/path", - accountReference = "Account reference" - ) - - private val darajaTransactionRequest = DarajaTransactionRequest( - businessShortCode = "654321", - password = "password", - timestamp = "timestamp", - checkoutRequestID = "ws_CO_07022023155508743714091304" - ) + private val darajaToken = + DarajaToken( + accessToken = "wWAHdtiE4GCSGv2ocfzQ0WHefwAJ", + expiresIn = "3599", + ) + + private val mpesaExpressRequest = + MpesaExpressRequest( + businessShortCode = "654321", + password = "password", + phoneNumber = "254708374149", + timestamp = "timestamp", + transactionType = DarajaTransactionType.CustomerPayBillOnline.name, + transactionDesc = "Transaction description", + amount = "1", + partyA = "254708374149", + partyB = "654321", + callBackUrl = "https://mydomain.com/path", + accountReference = "Account reference", + ) + + private val darajaTransactionRequest = + DarajaTransactionRequest( + businessShortCode = "654321", + password = "password", + timestamp = "timestamp", + checkoutRequestID = "ws_CO_07022023155508743714091304", + ) @BeforeTest fun setup() { @@ -75,12 +77,13 @@ class DarajaApiServiceTest { mockInMemoryCache = Cache.Builder().build() - darajaApiService = DarajaApiService( - httpClient = mockKtorHttpClient, - consumerKey = "consumerKey", - consumerSecret = "consumerSecret", - inMemoryCache = mockInMemoryCache - ) + darajaApiService = + DarajaApiService( + httpClient = mockKtorHttpClient, + consumerKey = "consumerKey", + consumerSecret = "consumerSecret", + inMemoryCache = mockInMemoryCache, + ) } @AfterTest @@ -90,69 +93,75 @@ class DarajaApiServiceTest { } @Test - fun fetchAccessToken_success_returns_darajaToken() = runTest { - // when - val actualResult = darajaApiService.fetchAccessToken() - - // then - assertEquals( - expected = DarajaResult.Success(darajaToken), - actual = actualResult - ) - } + fun fetchAccessToken_success_returns_darajaToken() = + runTest { + // when + val actualResult = darajaApiService.fetchAccessToken() + + // then + assertEquals( + expected = DarajaResult.Success(darajaToken), + actual = actualResult, + ) + } @Test - fun fetchAccessToken_success_caches_darajaToken() = runTest { - assertNull(mockInMemoryCache.get(1)) + fun fetchAccessToken_success_caches_darajaToken() = + runTest { + assertNull(mockInMemoryCache.get(1)) - // when - darajaApiService.fetchAccessToken() + // when + darajaApiService.fetchAccessToken() - // then - val cachedToken = mockInMemoryCache.get(1) + // then + val cachedToken = mockInMemoryCache.get(1) - assertNotNull(cachedToken) - assertEquals(expected = darajaToken, actual = cachedToken) - } + assertNotNull(cachedToken) + assertEquals(expected = darajaToken, actual = cachedToken) + } @Test - fun initiateMpesaExpress_success_returns_darajaPaymentResponse() = runTest { - assertNull(mockInMemoryCache.get(1)) - - // when - val actualResult = - darajaApiService.initiateMpesaExpress(mpesaExpressRequest = mpesaExpressRequest) - val expectedResult = DarajaResult.Success( - MpesaExpressResponse( - merchantRequestID = "6093-85819535-1", - checkoutRequestID = "ws_CO_16122022001707470708374149", - responseCode = "0", - responseDescription = "Success. Request accepted for processing", - customerMessage = "Success. Request accepted for processing" - ) - ) - - // then - assertEquals(expected = expectedResult, actual = actualResult) - assertNotNull(mockInMemoryCache.get(1)) - } + fun initiateMpesaExpress_success_returns_darajaPaymentResponse() = + runTest { + assertNull(mockInMemoryCache.get(1)) + + // when + val actualResult = + darajaApiService.initiateMpesaExpress(mpesaExpressRequest = mpesaExpressRequest) + val expectedResult = + DarajaResult.Success( + MpesaExpressResponse( + merchantRequestID = "6093-85819535-1", + checkoutRequestID = "ws_CO_16122022001707470708374149", + responseCode = "0", + responseDescription = "Success. Request accepted for processing", + customerMessage = "Success. Request accepted for processing", + ), + ) + + // then + assertEquals(expected = expectedResult, actual = actualResult) + assertNotNull(mockInMemoryCache.get(1)) + } @Test - fun queryTransaction_success_returns_darajaTransactionResponse() = runTest { - // when - val actualResult = - darajaApiService.queryTransaction(darajaTransactionRequest = darajaTransactionRequest) - val expectedResult = DarajaResult.Success( - DarajaTransactionResponse( - responseCode = "0", - responseDescription = "The service request has been accepted successsfully", - merchantRequestID = "15386-269505584-1", - checkoutRequestID = "ws_CO_07022023155508743714091304", - resultCode = "0", - resultDescription = "The service request is processed successfully." - ) - ) - - assertEquals(expected = expectedResult, actual = actualResult) - } + fun queryTransaction_success_returns_darajaTransactionResponse() = + runTest { + // when + val actualResult = + darajaApiService.queryTransaction(darajaTransactionRequest = darajaTransactionRequest) + val expectedResult = + DarajaResult.Success( + DarajaTransactionResponse( + responseCode = "0", + responseDescription = "The service request has been accepted successsfully", + merchantRequestID = "15386-269505584-1", + checkoutRequestID = "ws_CO_07022023155508743714091304", + resultCode = "0", + resultDescription = "The service request is processed successfully.", + ), + ) + + assertEquals(expected = expectedResult, actual = actualResult) + } } 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 def8e2cd..9e4b8f82 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/MockDarajaHttpClient.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/MockDarajaHttpClient.kt @@ -39,69 +39,74 @@ import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json internal class MockDarajaHttpClient { - private var httpStatusCode: HttpStatusCode = HttpStatusCode.OK private var responseContent: String? = null - fun throwError(httpStatus: HttpStatusCode, response: String) { + + fun throwError( + httpStatus: HttpStatusCode, + response: String, + ) { httpStatusCode = httpStatus responseContent = response } private val responseHeaders = headersOf(HttpHeaders.ContentType, "application/json") - val mockDarajaHttpClient = HttpClient(MockEngine) { - engine { - addHandler { request -> - when (request.url.fullPath) { - "/${DarajaEndpoints.REQUEST_ACCESS_TOKEN}" -> { - respond( - responseContent ?: AccessToken200JSON, - httpStatusCode, - responseHeaders - ) - } - "/${DarajaEndpoints.INITIATE_MPESA_EXPRESS}" -> { - respond( - responseContent ?: MpesaExpress200JSON, - httpStatusCode, - responseHeaders - ) - } - "/${DarajaEndpoints.QUERY_MPESA_TRANSACTION}" -> { - respond( - responseContent ?: QueryTransaction200JSON, - httpStatusCode, - responseHeaders - ) - } - else -> { - error("Unhandled ${request.url.encodedPathAndQuery}") + val mockDarajaHttpClient = + HttpClient(MockEngine) { + engine { + addHandler { request -> + when (request.url.fullPath) { + "/${DarajaEndpoints.REQUEST_ACCESS_TOKEN}" -> { + respond( + responseContent ?: AccessToken200JSON, + httpStatusCode, + responseHeaders, + ) + } + "/${DarajaEndpoints.INITIATE_MPESA_EXPRESS}" -> { + respond( + responseContent ?: MpesaExpress200JSON, + httpStatusCode, + responseHeaders, + ) + } + "/${DarajaEndpoints.QUERY_MPESA_TRANSACTION}" -> { + respond( + responseContent ?: QueryTransaction200JSON, + httpStatusCode, + responseHeaders, + ) + } + else -> { + error("Unhandled ${request.url.encodedPathAndQuery}") + } } } } - } - expectSuccess = true - addDefaultResponseValidation() + expectSuccess = true + addDefaultResponseValidation() - defaultRequest { contentType(ContentType.Application.Json) } + defaultRequest { contentType(ContentType.Application.Json) } - install(ContentNegotiation) { - json( - Json { - ignoreUnknownKeys = true - isLenient = true - } - ) - } + install(ContentNegotiation) { + json( + Json { + ignoreUnknownKeys = true + isLenient = true + }, + ) + } - install(Logging) { - level = LogLevel.ALL - logger = object : Logger { - override fun log(message: String) { - println("Http Logs: $message") - } + install(Logging) { + level = LogLevel.ALL + logger = + object : Logger { + override fun log(message: String) { + println("Http Logs: $message") + } + } } } - } } 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 2c7ee7c5..38c07801 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/DarajaResultTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/DarajaResultTest.kt @@ -23,12 +23,12 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull class DarajaResultTest { - - private val darajaException = DarajaException( - requestId = "43301-58413611-1", - errorCode = "400.008.01", - errorMessage = "Invalid Authentication passed" - ) + private val darajaException = + DarajaException( + requestId = "43301-58413611-1", + errorCode = "400.008.01", + errorMessage = "Invalid Authentication passed", + ) @Test fun darajaResult_getOrNull_returns_data_on_success() { diff --git a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/UtilsTest.kt b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/UtilsTest.kt index 474deaa2..0735ed49 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/UtilsTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/UtilsTest.kt @@ -25,11 +25,11 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith class UtilsTest { - @Test fun getDarajaTimeStamp_returns_correct_timestamp_on_single_digit_values() { - val currentDateTime = "2022-01-01T01:01:01.694394300".toLocalDateTime() - .toInstant(TimeZone.currentSystemDefault()) + val currentDateTime = + "2022-01-01T01:01:01.694394300".toLocalDateTime() + .toInstant(TimeZone.currentSystemDefault()) val expectedResult = "20220101010101" assertEquals(expected = expectedResult, actual = currentDateTime.getDarajaTimestamp()) @@ -37,8 +37,9 @@ class UtilsTest { @Test fun getDarajaTimeStamp_returns_correct_timestamp_on_double_digit_values() { - val currentDateTime = "2022-12-12T12:12:12.694394300".toLocalDateTime() - .toInstant(TimeZone.currentSystemDefault()) + val currentDateTime = + "2022-12-12T12:12:12.694394300".toLocalDateTime() + .toInstant(TimeZone.currentSystemDefault()) val expectedResult = "20221212121212" assertEquals(expected = expectedResult, actual = currentDateTime.getDarajaTimestamp()) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 098266ad..5977dc81 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.0.0" agp = "8.1.4" -ktLint = "11.6.0" +ktLint = "12.1.1" detekt = "1.19.0" spotless = "6.2.2" dokka = "1.9.20" From f0b7910b84060bdda7d05946105e7e9af774572f Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 12 Aug 2024 15:49:34 +0300 Subject: [PATCH 06/14] Fixed failing lint checks --- .../commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt | 11 +++++++++-- .../vickbt/darajakmp/network/DarajaApiServiceTest.kt | 2 +- .../vickbt/darajakmp/network/MockDarajaHttpClient.kt | 12 ++++++------ .../darajakmp/network/models/MockNetworkResponses.kt | 12 ++++++------ 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index a0b8f8eb..dd764317 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -124,7 +124,9 @@ class Daraja( private val darajaApiService: DarajaApiService = DarajaApiService( httpClient = darajaHttpClientFactory, - consumerKey = consumerKey ?: throw DarajaException(errorMessage = "Consumer key is null"), + consumerKey = + consumerKey + ?: throw DarajaException(errorMessage = "Consumer key is null"), consumerSecret = consumerSecret ?: throw DarajaException(errorMessage = "Consumer secret is null"), @@ -335,7 +337,12 @@ class Daraja( billReferenceNumber = billReferenceNumber, commandID = transactionType.name, phoneNumber = phoneNumber.getDarajaPhoneNumber().toLong(), - shortCode = if (transactionType.name == DarajaTransactionType.CustomerPayBillOnline.name) businessShortCode else billReferenceNumber, + shortCode = + if (transactionType.name == DarajaTransactionType.CustomerPayBillOnline.name) { + businessShortCode + } else { + billReferenceNumber + }, ) darajaApiService.c2b(c2bRequest = c2bRequest) 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 9fa9277b..b4065812 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt @@ -54,7 +54,7 @@ class DarajaApiServiceTest { password = "password", phoneNumber = "254708374149", timestamp = "timestamp", - transactionType = DarajaTransactionType.CustomerPayBillOnline.name, + transactionType = DarajaTransactionType.PayBill.name, transactionDesc = "Transaction description", amount = "1", partyA = "254708374149", 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 9e4b8f82..a7517578 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/MockDarajaHttpClient.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/MockDarajaHttpClient.kt @@ -16,9 +16,9 @@ package com.vickbt.darajakmp.network -import com.vickbt.darajakmp.network.models.AccessToken200JSON -import com.vickbt.darajakmp.network.models.MpesaExpress200JSON -import com.vickbt.darajakmp.network.models.QueryTransaction200JSON +import com.vickbt.darajakmp.network.models.ACCESS_TOKEN_200_JSON +import com.vickbt.darajakmp.network.models.MPESA_EXPRESS_200_JSON +import com.vickbt.darajakmp.network.models.QUERY_TRANSACTION_200_JSON import com.vickbt.darajakmp.utils.DarajaEndpoints import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine @@ -59,21 +59,21 @@ internal class MockDarajaHttpClient { when (request.url.fullPath) { "/${DarajaEndpoints.REQUEST_ACCESS_TOKEN}" -> { respond( - responseContent ?: AccessToken200JSON, + responseContent ?: ACCESS_TOKEN_200_JSON, httpStatusCode, responseHeaders, ) } "/${DarajaEndpoints.INITIATE_MPESA_EXPRESS}" -> { respond( - responseContent ?: MpesaExpress200JSON, + responseContent ?: MPESA_EXPRESS_200_JSON, httpStatusCode, responseHeaders, ) } "/${DarajaEndpoints.QUERY_MPESA_TRANSACTION}" -> { respond( - responseContent ?: QueryTransaction200JSON, + responseContent ?: QUERY_TRANSACTION_200_JSON, httpStatusCode, responseHeaders, ) diff --git a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/models/MockNetworkResponses.kt b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/models/MockNetworkResponses.kt index a2261134..ce4861be 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/models/MockNetworkResponses.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/models/MockNetworkResponses.kt @@ -16,14 +16,14 @@ package com.vickbt.darajakmp.network.models -const val AccessToken200JSON = """ +const val ACCESS_TOKEN_200_JSON = """ { "access_token": "wWAHdtiE4GCSGv2ocfzQ0WHefwAJ", "expires_in": "3599" } """ -const val AccessToken400JSON = """ +const val ACCESS_TOKEN_400_JSON = """ { "requestId": "43301-58413611-1", "errorCode": "400.008.01", @@ -31,7 +31,7 @@ const val AccessToken400JSON = """ } """ -const val MpesaExpress200JSON = """ +const val MPESA_EXPRESS_200_JSON = """ { "MerchantRequestID": "6093-85819535-1", "CheckoutRequestID": "ws_CO_16122022001707470708374149", @@ -41,7 +41,7 @@ const val MpesaExpress200JSON = """ } """ -const val MpesaExpress500JSON = """ +const val MPESA_EXPRESS_500_JSON = """ { "requestId": "119414-258858845-1", "errorCode": "500.001.1001", @@ -49,7 +49,7 @@ const val MpesaExpress500JSON = """ } """ -const val InvalidAccessTokenJSON = """ +const val INVALID_ACCESS_TOKEN_JSON = """ { "requestId": "16813-15-1", "errorCode": "404.001.04", @@ -57,7 +57,7 @@ const val InvalidAccessTokenJSON = """ } """ -const val QueryTransaction200JSON = """ +const val QUERY_TRANSACTION_200_JSON = """ { "ResponseCode": "0", "ResponseDescription": "The service request has been accepted successsfully", From 9d82cfa26f51ca139633b9d1a0e196c4b4a96b77 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 12 Aug 2024 16:17:52 +0300 Subject: [PATCH 07/14] Update detekt version --- .github/workflows/publish_android.yml | 2 +- gradle/libs.versions.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish_android.yml b/.github/workflows/publish_android.yml index 9d9e91ae..8961c905 100644 --- a/.github/workflows/publish_android.yml +++ b/.github/workflows/publish_android.yml @@ -46,7 +46,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute unit tests - run: ./gradlew :daraja:check --stacktrace + run: ./gradlew :daraja:allTests --stacktrace deploy-android: name: 🚀 Sign and Publish Android Library diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5977dc81..d2ebeff4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.0.0" agp = "8.1.4" ktLint = "12.1.1" -detekt = "1.19.0" +detekt = "1.23.6" spotless = "6.2.2" dokka = "1.9.20" kover = "0.8.1" From 7fc71bfcef0312047b8c97b3425b3ae25e30c858 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 12 Aug 2024 16:20:22 +0300 Subject: [PATCH 08/14] Linting --- .../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 b4065812..9fa9277b 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/network/DarajaApiServiceTest.kt @@ -54,7 +54,7 @@ class DarajaApiServiceTest { password = "password", phoneNumber = "254708374149", timestamp = "timestamp", - transactionType = DarajaTransactionType.PayBill.name, + transactionType = DarajaTransactionType.CustomerPayBillOnline.name, transactionDesc = "Transaction description", amount = "1", partyA = "254708374149", From dc4f13971d75679160c437ac64d0e4c592b6af1a Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 12 Aug 2024 16:22:45 +0300 Subject: [PATCH 09/14] Update kover version --- 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 d2ebeff4..22058d05 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ ktLint = "12.1.1" detekt = "1.23.6" spotless = "6.2.2" dokka = "1.9.20" -kover = "0.8.1" +kover = "0.8.3" mulitplatformSwiftPackage = "2.0.3" gradleVersionUpdate = "0.51.0" From afff3d63520d878bff900f7f8151b641fe991eb7 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 12 Aug 2024 16:27:43 +0300 Subject: [PATCH 10/14] Update ktor version --- gradle/libs.versions.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 22058d05..14c1226e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,8 +14,7 @@ kotlinxCoroutines = "1.9.0-RC" kotlinxSerializationJson = "1.7.0" kotlinxDateTime = "0.6.0" napier = "2.7.1" -ktor = "2.3.11" -kotlinxTestResources = "0.2.2" +ktor = "2.3.12" composeMultiplatform = "1.6.11" cache4k = "0.13.0" mockative = "2.2.2" @@ -57,6 +56,5 @@ kotlinX-dateTime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version. cache4k = { module = "io.github.reactivecircus.cache4k:cache4k", version.ref = "cache4k" } #Tests Lib Dependencies -# kotlinX-testResources = { module = "com.goncalossilva:resources", version.ref = "kotlinxTestResources" } kotlinX-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } mockative = { module = "io.mockative:mockative", version.ref = "mockative" } \ No newline at end of file From a2ae0dc8d79628a95229f2d3398aa0dc0a488209 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Mon, 12 Aug 2024 16:51:21 +0300 Subject: [PATCH 11/14] Added gradle action to build and maintenance workflows --- .github/workflows/build.yml | 22 +++++++++++----------- .github/workflows/maintenance.yml | 10 +++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ed5f89d5..1cf4b6da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,16 +12,16 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - name: Set up JDK - uses: actions/setup-java@v3 + - name: Setup JDK + uses: actions/setup-java@v4 with: distribution: 'adopt' java-version: '17' - - name: Make gradle executable - run: chmod +x ./gradlew + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 - name: Build with gradle run: ./gradlew :daraja:build --stacktrace @@ -32,16 +32,16 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - name: Set up JDK - uses: actions/setup-java@v3 + - name: Setup JDK + uses: actions/setup-java@v4 with: distribution: 'adopt' java-version: '17' - - name: Make gradle executable - run: chmod +x ./gradlew + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 - name: Execute unit tests run: ./gradlew :daraja:allTests --stacktrace @@ -50,7 +50,7 @@ jobs: run: ./gradlew :daraja:koverHtmlReport --stacktrace - name: Upload test report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: unit_tests_report.html path: daraja/build/reports/kover/html/ \ No newline at end of file diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml index b9bda016..9a3b9529 100644 --- a/.github/workflows/maintenance.yml +++ b/.github/workflows/maintenance.yml @@ -13,22 +13,22 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'adopt' java-version: '17' - - name: Make gradle executable - run: chmod +x ./gradlew + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 - name: Check for release dependencies run: ./gradlew :daraja:dependencyUpdates -Drevision=release -DoutputFormatter=html -DreportfileName=dependencies_report --stacktrace - name: Upload dependencies report artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Dependency Report path: build/reports/dependencies_report.html \ No newline at end of file From 9908f24338d1e2a8a428be146248d6e21863ebd6 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Tue, 13 Aug 2024 08:42:19 +0300 Subject: [PATCH 12/14] Update maintenance workflow with correct path --- .github/workflows/maintenance.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml index 9a3b9529..4b5d28d9 100644 --- a/.github/workflows/maintenance.yml +++ b/.github/workflows/maintenance.yml @@ -15,7 +15,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up JDK + - name: Setup JDK uses: actions/setup-java@v4 with: distribution: 'adopt' @@ -31,4 +31,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: Dependency Report - path: build/reports/dependencies_report.html \ No newline at end of file + path: daraja/build/reports/dependencies_report.html \ No newline at end of file From 7d8cb4061664f750c069f870b863f9bd0aecfb57 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Tue, 13 Aug 2024 09:20:28 +0300 Subject: [PATCH 13/14] Update publish_swift_package.yml --- .github/workflows/publish_swift_package.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish_swift_package.yml b/.github/workflows/publish_swift_package.yml index 930f3b43..0be89cf1 100644 --- a/.github/workflows/publish_swift_package.yml +++ b/.github/workflows/publish_swift_package.yml @@ -9,6 +9,7 @@ on: jobs: build: + name: 🔨 Build runs-on: macos-latest steps: - name: Checkout @@ -35,6 +36,7 @@ jobs: retention-days: 1 push: + name: 📤 Push to Swift Repo needs: build runs-on: ubuntu-latest steps: From 12182ffcb81c4eb7756b9291e7db153a9eae1473 Mon Sep 17 00:00:00 2001 From: Victor Kabata Date: Tue, 13 Aug 2024 09:20:58 +0300 Subject: [PATCH 14/14] Update kotlinxSerializationJson from v1.7.0 to v1.7.1 --- 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 14c1226e..d5797097 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ gradleVersionUpdate = "0.51.0" # Kotlin Multiplatform Version kotlinxCoroutines = "1.9.0-RC" -kotlinxSerializationJson = "1.7.0" +kotlinxSerializationJson = "1.7.1" kotlinxDateTime = "0.6.0" napier = "2.7.1" ktor = "2.3.12"