diff --git a/CHANGELOG.md b/CHANGELOG.md index 0189010a..5f5aec1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# NEXT RELEASE | WIP +## Breaking Changes + - Our internal implementation no longer depends on `kotlinx-datetime`, and now uses Java 8 time classes, +namely `java.time.Instant`. +As a result, if your app or library supports Android versions below API 26, you must enable +**[coreLibraryDesugaring](https://developer.android.com/studio/write/java8-support-table)** if it isn't already. In most cases, this will be evident at compile time, +as the build will fail with an error similar to the following: + +``` +Dependency XYZ requires core library desugaring to be enabled for :your-app-module. +``` + + - Add new action `ON_FILE_PAYOUT`. A payout allows a merchant to send money to a user's Cash App account. + # 2.6.0 ## Breaking Changes - Introduces new button styles and deprecates existing styles. This is also replaces diff --git a/build.gradle b/build.gradle index f861ce31..952211ff 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { google_truth_version = '1.4.5' startup_version = '1.2.0' okhttp_version = '5.3.2' - kotlinx_date_version = '0.6.2' + desugar_jdk_libs_version = '2.1.5' versions = [ 'minSdk': 21, diff --git a/core/api/core.api b/core/api/core.api index fd6f925c..af75f6b9 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -505,18 +505,18 @@ public final class app/cash/paykit/core/models/response/ApiErrorResponseJsonAdap } public final class app/cash/paykit/core/models/response/AuthFlowTriggers { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/datetime/Instant;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/time/Instant;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Lkotlinx/datetime/Instant; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/datetime/Instant;)Lapp/cash/paykit/core/models/response/AuthFlowTriggers; - public static synthetic fun copy$default (Lapp/cash/paykit/core/models/response/AuthFlowTriggers;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/datetime/Instant;ILjava/lang/Object;)Lapp/cash/paykit/core/models/response/AuthFlowTriggers; + public final fun component4 ()Ljava/time/Instant; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/time/Instant;)Lapp/cash/paykit/core/models/response/AuthFlowTriggers; + public static synthetic fun copy$default (Lapp/cash/paykit/core/models/response/AuthFlowTriggers;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/time/Instant;ILjava/lang/Object;)Lapp/cash/paykit/core/models/response/AuthFlowTriggers; public fun equals (Ljava/lang/Object;)Z public final fun getMobileUrl ()Ljava/lang/String; public final fun getQrCodeImageUrl ()Ljava/lang/String; public final fun getQrCodeSvgUrl ()Ljava/lang/String; - public final fun getRefreshesAt ()Lkotlinx/datetime/Instant; + public final fun getRefreshesAt ()Ljava/time/Instant; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -553,9 +553,9 @@ public final class app/cash/paykit/core/models/response/CustomerProfileJsonAdapt } public final class app/cash/paykit/core/models/response/CustomerResponseData { - public fun (Ljava/util/List;Lapp/cash/paykit/core/models/response/AuthFlowTriggers;Ljava/lang/String;Ljava/lang/String;Lapp/cash/paykit/core/models/response/Origin;Lapp/cash/paykit/core/models/response/RequesterProfile;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lapp/cash/paykit/core/models/response/CustomerProfile;Ljava/util/List;Lapp/cash/paykit/core/models/pii/PiiString;)V + public fun (Ljava/util/List;Lapp/cash/paykit/core/models/response/AuthFlowTriggers;Ljava/lang/String;Ljava/lang/String;Lapp/cash/paykit/core/models/response/Origin;Lapp/cash/paykit/core/models/response/RequesterProfile;Ljava/lang/String;Ljava/time/Instant;Ljava/time/Instant;Ljava/time/Instant;Lapp/cash/paykit/core/models/response/CustomerProfile;Ljava/util/List;Lapp/cash/paykit/core/models/pii/PiiString;)V public final fun component1 ()Ljava/util/List; - public final fun component10 ()Lkotlinx/datetime/Instant; + public final fun component10 ()Ljava/time/Instant; public final fun component11 ()Lapp/cash/paykit/core/models/response/CustomerProfile; public final fun component12 ()Ljava/util/List; public final fun component13 ()Lapp/cash/paykit/core/models/pii/PiiString; @@ -565,24 +565,24 @@ public final class app/cash/paykit/core/models/response/CustomerResponseData { public final fun component5 ()Lapp/cash/paykit/core/models/response/Origin; public final fun component6 ()Lapp/cash/paykit/core/models/response/RequesterProfile; public final fun component7 ()Ljava/lang/String; - public final fun component8 ()Lkotlinx/datetime/Instant; - public final fun component9 ()Lkotlinx/datetime/Instant; - public final fun copy (Ljava/util/List;Lapp/cash/paykit/core/models/response/AuthFlowTriggers;Ljava/lang/String;Ljava/lang/String;Lapp/cash/paykit/core/models/response/Origin;Lapp/cash/paykit/core/models/response/RequesterProfile;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lapp/cash/paykit/core/models/response/CustomerProfile;Ljava/util/List;Lapp/cash/paykit/core/models/pii/PiiString;)Lapp/cash/paykit/core/models/response/CustomerResponseData; - public static synthetic fun copy$default (Lapp/cash/paykit/core/models/response/CustomerResponseData;Ljava/util/List;Lapp/cash/paykit/core/models/response/AuthFlowTriggers;Ljava/lang/String;Ljava/lang/String;Lapp/cash/paykit/core/models/response/Origin;Lapp/cash/paykit/core/models/response/RequesterProfile;Ljava/lang/String;Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lapp/cash/paykit/core/models/response/CustomerProfile;Ljava/util/List;Lapp/cash/paykit/core/models/pii/PiiString;ILjava/lang/Object;)Lapp/cash/paykit/core/models/response/CustomerResponseData; + public final fun component8 ()Ljava/time/Instant; + public final fun component9 ()Ljava/time/Instant; + public final fun copy (Ljava/util/List;Lapp/cash/paykit/core/models/response/AuthFlowTriggers;Ljava/lang/String;Ljava/lang/String;Lapp/cash/paykit/core/models/response/Origin;Lapp/cash/paykit/core/models/response/RequesterProfile;Ljava/lang/String;Ljava/time/Instant;Ljava/time/Instant;Ljava/time/Instant;Lapp/cash/paykit/core/models/response/CustomerProfile;Ljava/util/List;Lapp/cash/paykit/core/models/pii/PiiString;)Lapp/cash/paykit/core/models/response/CustomerResponseData; + public static synthetic fun copy$default (Lapp/cash/paykit/core/models/response/CustomerResponseData;Ljava/util/List;Lapp/cash/paykit/core/models/response/AuthFlowTriggers;Ljava/lang/String;Ljava/lang/String;Lapp/cash/paykit/core/models/response/Origin;Lapp/cash/paykit/core/models/response/RequesterProfile;Ljava/lang/String;Ljava/time/Instant;Ljava/time/Instant;Ljava/time/Instant;Lapp/cash/paykit/core/models/response/CustomerProfile;Ljava/util/List;Lapp/cash/paykit/core/models/pii/PiiString;ILjava/lang/Object;)Lapp/cash/paykit/core/models/response/CustomerResponseData; public fun equals (Ljava/lang/Object;)Z public final fun getActions ()Ljava/util/List; public final fun getAuthFlowTriggers ()Lapp/cash/paykit/core/models/response/AuthFlowTriggers; public final fun getChannel ()Ljava/lang/String; - public final fun getCreatedAt ()Lkotlinx/datetime/Instant; + public final fun getCreatedAt ()Ljava/time/Instant; public final fun getCustomerProfile ()Lapp/cash/paykit/core/models/response/CustomerProfile; - public final fun getExpiresAt ()Lkotlinx/datetime/Instant; + public final fun getExpiresAt ()Ljava/time/Instant; public final fun getGrants ()Ljava/util/List; public final fun getId ()Ljava/lang/String; public final fun getOrigin ()Lapp/cash/paykit/core/models/response/Origin; public final fun getReferenceId ()Lapp/cash/paykit/core/models/pii/PiiString; public final fun getRequesterProfile ()Lapp/cash/paykit/core/models/response/RequesterProfile; public final fun getStatus ()Ljava/lang/String; - public final fun getUpdatedAt ()Lkotlinx/datetime/Instant; + public final fun getUpdatedAt ()Ljava/time/Instant; public fun hashCode ()I public final fun isAuthTokenExpired ()Z public fun toString ()Ljava/lang/String; diff --git a/core/build.gradle b/core/build.gradle index d662a03b..cb58974c 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -31,6 +31,7 @@ android { } compileOptions { + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } @@ -66,15 +67,14 @@ android { dependencies { + coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugar_jdk_libs_version" + // We want to lock this dependency at a lower than latest version to not force transitive updates onto merchants. //noinspection GradleDependency ksp("com.squareup.moshi:moshi-kotlin-codegen:$moshi_version") //noinspection GradleDependency implementation("com.squareup.moshi:moshi-kotlin:$moshi_version") - // KotlinX Dates. - implementation "org.jetbrains.kotlinx:kotlinx-datetime:$kotlinx_date_version" - // Provides a lifecycle for the whole application process. implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version" diff --git a/core/consumer-rules.pro b/core/consumer-rules.pro index 2346dba4..cc900c4c 100644 --- a/core/consumer-rules.pro +++ b/core/consumer-rules.pro @@ -3,73 +3,3 @@ -keep enum app.cash.paykit.core.models.sdk.CashAppPayCurrency { *; } -keep enum app.cash.paykit.core.impl.RequestType { *; } -keep enum app.cash.paykit.core.utils.ThreadPurpose { *; } - - -# Rules for Kotlin Serializer - a transitive dependency of KotlinX Datetime. -# Can probably be removed after datetime is updated. - -# Keep `Companion` object fields of serializable classes. -# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects. --if @kotlinx.serialization.Serializable class ** --keepclassmembers class <1> { - static <1>$Companion Companion; -} - -# Keep `serializer()` on companion objects (both default and named) of serializable classes. --if @kotlinx.serialization.Serializable class ** { - static **$* *; -} --keepclassmembers class <2>$<3> { - kotlinx.serialization.KSerializer serializer(...); -} - -# Keep `INSTANCE.serializer()` of serializable objects. --if @kotlinx.serialization.Serializable class ** { - public static ** INSTANCE; -} --keepclassmembers class <1> { - public static <1> INSTANCE; - kotlinx.serialization.KSerializer serializer(...); -} - -# @Serializable and @Polymorphic are used at runtime for polymorphic serialization. --keepattributes RuntimeVisibleAnnotations,AnnotationDefault - -# Don't print notes about potential mistakes or omissions in the configuration for kotlinx-serialization classes -# See also https://github.com/Kotlin/kotlinx.serialization/issues/1900 --dontnote kotlinx.serialization.** - -# Serialization core uses `java.lang.ClassValue` for caching inside these specified classes. -# If there is no `java.lang.ClassValue` (for example, in Android), then R8/ProGuard will print a warning. -# However, since in this case they will not be used, we can disable these warnings --dontwarn kotlinx.serialization.internal.ClassValueReferences - -# Serializer for classes with named companion objects are retrieved using `getDeclaredClasses`. -# If you have any, replace classes with those containing named companion objects. --keepattributes InnerClasses # Needed for `getDeclaredClasses`. - --if @kotlinx.serialization.Serializable class -kotlinx.datetime.Instant$Companion, # <-- List serializable classes with named companions. -kotlinx.datetime.Instant$Companion$serializer -{ - static **$* *; -} --keepnames class <1>$$serializer { # -keepnames suffices; class is kept when serializer() is kept. - static <1>$$serializer INSTANCE; -} - -# Keep both serializer and serializable classes to save the attribute InnerClasses --keepclasseswithmembers, allowshrinking, allowobfuscation, allowaccessmodification class -kotlinx.datetime.Instant$Companion, # <-- List serializable classes with named companions. -kotlinx.datetime.Instant$Companion$serializer -{ - *; -} - --dontwarn kotlinx.serialization.KSerializer --dontwarn kotlinx.serialization.Serializable --dontwarn kotlinx.serialization.descriptors.PrimitiveKind$STRING --dontwarn kotlinx.serialization.descriptors.PrimitiveKind --dontwarn kotlinx.serialization.descriptors.SerialDescriptor --dontwarn kotlinx.serialization.descriptors.SerialDescriptorsKt --dontwarn kotlinx.serialization.internal.AbstractPolymorphicSerializer \ No newline at end of file diff --git a/core/src/main/java/app/cash/paykit/core/analytics/PayKitAnalyticsEventDispatcherImpl.kt b/core/src/main/java/app/cash/paykit/core/analytics/PayKitAnalyticsEventDispatcherImpl.kt index b6af7cee..0513f374 100644 --- a/core/src/main/java/app/cash/paykit/core/analytics/PayKitAnalyticsEventDispatcherImpl.kt +++ b/core/src/main/java/app/cash/paykit/core/analytics/PayKitAnalyticsEventDispatcherImpl.kt @@ -288,8 +288,8 @@ internal class PayKitAnalyticsEventDispatcherImpl( clientId, status = customerResponseData?.status, authMobileUrl = customerResponseData?.authFlowTriggers?.mobileUrl, - updatedAt = customerResponseData?.updatedAt?.toEpochMilliseconds(), - createdAt = customerResponseData?.createdAt?.toEpochMilliseconds(), + updatedAt = customerResponseData?.updatedAt?.toEpochMilli(), + createdAt = customerResponseData?.createdAt?.toEpochMilli(), originType = customerResponseData?.origin?.type, originId = customerResponseData?.origin?.id, requestChannel = CHANNEL_IN_APP, diff --git a/core/src/main/java/app/cash/paykit/core/impl/CashAppPayImpl.kt b/core/src/main/java/app/cash/paykit/core/impl/CashAppPayImpl.kt index 9a56d928..79c92f03 100644 --- a/core/src/main/java/app/cash/paykit/core/impl/CashAppPayImpl.kt +++ b/core/src/main/java/app/cash/paykit/core/impl/CashAppPayImpl.kt @@ -58,9 +58,11 @@ import app.cash.paykit.core.utils.ThreadPurpose.DEFERRED_REFRESH import app.cash.paykit.core.utils.ThreadPurpose.REFRESH_AUTH_TOKEN import app.cash.paykit.core.utils.orElse import app.cash.paykit.logging.CashAppLogger -import kotlinx.datetime.Clock +import java.time.Instant import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds +import kotlin.time.toKotlinDuration +import java.time.Duration as JavaDuration /** * @param clientId Client Identifier that should be provided by Cash PayKit integration. @@ -480,9 +482,9 @@ internal class CashAppPayImpl( } // Stop refreshing if the request has expired. - val currentTime = Clock.System.now() + val currentTime = Instant.now() val hasExpired = - customerResponseData?.expiresAt?.let { expiresAt -> currentTime > expiresAt } ?: false + customerResponseData?.expiresAt?.let { expiresAt -> currentTime.isAfter(expiresAt) } ?: false if (hasExpired) { logger.logError(tag = CAP_TAG, msg = "Customer request has expired. Stopping refresh.") return@createThread @@ -535,11 +537,12 @@ internal class CashAppPayImpl( return } - val ttlSeconds = customerResponseData.authFlowTriggers.refreshesAt.minus( + val ttl = JavaDuration.between( customerResponseData.createdAt, - ) + customerResponseData.authFlowTriggers.refreshesAt, + ).toKotlinDuration() - val refreshDelay = ttlSeconds.inWholeSeconds.minus(TOKEN_REFRESH_WINDOW.inWholeSeconds) + val refreshDelay = ttl.inWholeSeconds.minus(TOKEN_REFRESH_WINDOW.inWholeSeconds) logger.logVerbose( tag = CAP_TAG, msg = "Scheduling unauthorized customer request refresh in $refreshDelay seconds.", diff --git a/core/src/main/java/app/cash/paykit/core/models/response/AuthFlowTriggers.kt b/core/src/main/java/app/cash/paykit/core/models/response/AuthFlowTriggers.kt index 04b73f02..e7bd0a8d 100644 --- a/core/src/main/java/app/cash/paykit/core/models/response/AuthFlowTriggers.kt +++ b/core/src/main/java/app/cash/paykit/core/models/response/AuthFlowTriggers.kt @@ -17,7 +17,7 @@ package app.cash.paykit.core.models.response import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import kotlinx.datetime.Instant +import java.time.Instant @JsonClass(generateAdapter = true) data class AuthFlowTriggers( diff --git a/core/src/main/java/app/cash/paykit/core/models/response/CustomerResponseData.kt b/core/src/main/java/app/cash/paykit/core/models/response/CustomerResponseData.kt index d8dd86e0..1b9932e7 100644 --- a/core/src/main/java/app/cash/paykit/core/models/response/CustomerResponseData.kt +++ b/core/src/main/java/app/cash/paykit/core/models/response/CustomerResponseData.kt @@ -19,8 +19,7 @@ import app.cash.paykit.core.models.common.Action import app.cash.paykit.core.models.pii.PiiString import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import kotlinx.datetime.Clock.System -import kotlinx.datetime.Instant +import java.time.Instant const val STATUS_PENDING = "PENDING" const val STATUS_PROCESSING = "PROCESSING" @@ -57,8 +56,8 @@ data class CustomerResponseData( val referenceId: PiiString?, ) { fun isAuthTokenExpired(): Boolean { - val now = System.now() - val isExpired = now > (authFlowTriggers?.refreshesAt ?: return false) + val now = Instant.now() + val isExpired = now.isAfter(authFlowTriggers?.refreshesAt ?: return false) return isExpired } } diff --git a/core/src/main/java/app/cash/paykit/core/network/MoshiProvider.kt b/core/src/main/java/app/cash/paykit/core/network/MoshiProvider.kt index 539466c1..cdc35066 100644 --- a/core/src/main/java/app/cash/paykit/core/network/MoshiProvider.kt +++ b/core/src/main/java/app/cash/paykit/core/network/MoshiProvider.kt @@ -20,7 +20,7 @@ import app.cash.paykit.core.network.adapters.InstantAdapter import app.cash.paykit.core.network.adapters.PiiStringClearTextAdapter import app.cash.paykit.core.network.adapters.PiiStringRedactAdapter import com.squareup.moshi.Moshi -import kotlinx.datetime.Instant +import java.time.Instant internal object MoshiProvider { fun provideDefault(redactPii: Boolean = false): Moshi { diff --git a/core/src/main/java/app/cash/paykit/core/network/adapters/InstantAdapter.kt b/core/src/main/java/app/cash/paykit/core/network/adapters/InstantAdapter.kt index 265d3cde..aa5d7b5b 100644 --- a/core/src/main/java/app/cash/paykit/core/network/adapters/InstantAdapter.kt +++ b/core/src/main/java/app/cash/paykit/core/network/adapters/InstantAdapter.kt @@ -19,8 +19,7 @@ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonReader.Token.NULL import com.squareup.moshi.JsonWriter -import kotlinx.datetime.Instant -import kotlinx.datetime.toInstant +import java.time.Instant internal class InstantAdapter : JsonAdapter() { @@ -29,7 +28,7 @@ internal class InstantAdapter : JsonAdapter() { return reader.nextNull() } val timeRFC3339 = reader.nextString() - return timeRFC3339.toInstant() + return Instant.parse(timeRFC3339) } override fun toJson(writer: JsonWriter, value: Instant?) { diff --git a/core/src/test/java/app/cash/paykit/core/PiiStringTests.kt b/core/src/test/java/app/cash/paykit/core/PiiStringTests.kt index efcda333..3a322780 100644 --- a/core/src/test/java/app/cash/paykit/core/PiiStringTests.kt +++ b/core/src/test/java/app/cash/paykit/core/PiiStringTests.kt @@ -23,8 +23,8 @@ import com.google.common.truth.Truth.assertThat import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.adapter -import kotlinx.datetime.Instant import org.junit.Test +import java.time.Instant class PiiStringTests {