Skip to content

Commit 3dd9b7b

Browse files
authored
Merge pull request #369 from superwall/develop
2.7.4
2 parents 5139e3a + a171917 commit 3dd9b7b

File tree

73 files changed

+8154
-258
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+8154
-258
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
The changelog for `Superwall`. Also see the [releases](https://github.com/superwall/Superwall-Android/releases) on GitHub.
44

5+
## 2.7.4
6+
7+
## Enhancements
8+
9+
- Adds support for "Test Mode", which allows you to simulate in-app purchases without involving Google Play. Test Mode can be enabled through the Superwall dashboard by marking specific users as test store users, or activates automatically when a an application ID mismatch is detected or behavior is set to `ALWAYS`. When active, a configuration modal lets you select starting entitlements and override free trial availability. Purchases are simulated with a UI that lets users complete, abandon, or fail transactions, with all purchase events firing normally for end-to-end paywall testing.
10+
- Improved logging in web entitlement and entitlement redeeming for easier debugging
11+
- Adds active entitlements to subscription status change event
12+
- Ensures purchases are always queried with connected clients and retried in background
13+
- Adds `expirationDate`, `subscriptionGroupId` and `offerId` to `StoreTransactionType`
14+
15+
## Fixes
16+
- Improve rounding to match editor's pricing consistently
17+
518
## 2.7.3
619

720
### Enhancements

superwall/src/main/java/com/superwall/sdk/Superwall.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,14 @@ class Superwall(
275275
* @param subscriptionStatus The entitlement status of the user.
276276
*/
277277
fun setSubscriptionStatus(subscriptionStatus: SubscriptionStatus) {
278+
if (dependencyContainer.testModeManager.isTestMode) {
279+
Logger.debug(
280+
LogLevel.warn,
281+
LogScope.superwallCore,
282+
"setSubscriptionStatus ignored: test mode is active",
283+
)
284+
return
285+
}
278286
entitlements.setSubscriptionStatus(subscriptionStatus)
279287
}
280288

@@ -289,6 +297,14 @@ class Superwall(
289297
* @param entitlements A list of entitlements.
290298
* */
291299
fun setSubscriptionStatus(vararg entitlements: String) {
300+
if (dependencyContainer.testModeManager.isTestMode) {
301+
Logger.debug(
302+
LogLevel.warn,
303+
LogScope.superwallCore,
304+
"setSubscriptionStatus ignored: test mode is active",
305+
)
306+
return
307+
}
292308
if (entitlements.isEmpty()) {
293309
this@Superwall.entitlements.setSubscriptionStatus(SubscriptionStatus.Inactive)
294310
} else {
@@ -309,6 +325,9 @@ class Superwall(
309325
if (dependencyContainer.makeHasExternalPurchaseController()) {
310326
return
311327
}
328+
if (dependencyContainer.testModeManager.isTestMode) {
329+
return
330+
}
312331
val webEntitlements = dependencyContainer.entitlements.web
313332
when (toStatus) {
314333
is SubscriptionStatus.Active -> {
@@ -1403,7 +1422,6 @@ class Superwall(
14031422
.activityProvider
14041423
?.getCurrentActivity()
14051424
) as SuperwallPaywallActivity?
1406-
14071425
// Cancel any existing fallback notification of the same type before scheduling
14081426
// the dynamic notification from the paywall
14091427
paywallActivity?.attemptToScheduleNotifications(

superwall/src/main/java/com/superwall/sdk/analytics/internal/trackable/TrackableSuperwallEvent.kt

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ import com.superwall.sdk.paywall.presentation.internal.PaywallPresentationReques
2525
import com.superwall.sdk.paywall.presentation.internal.PresentationRequestType
2626
import com.superwall.sdk.paywall.view.survey.SurveyPresentationResult
2727
import com.superwall.sdk.paywall.view.webview.WebviewError
28+
import com.superwall.sdk.store.abstractions.product.RawStoreProduct
2829
import com.superwall.sdk.store.abstractions.product.StoreProduct
2930
import com.superwall.sdk.store.abstractions.product.StoreProductType
31+
import com.superwall.sdk.store.abstractions.product.SubscriptionPeriod
32+
import com.superwall.sdk.store.abstractions.transactions.GoogleBillingPurchaseTransaction
3033
import com.superwall.sdk.store.abstractions.transactions.StoreTransaction
3134
import com.superwall.sdk.store.abstractions.transactions.StoreTransactionType
3235
import com.superwall.sdk.store.transactions.RestoreType
@@ -35,6 +38,7 @@ import com.superwall.sdk.web.WebPaywallRedeemer
3538
import kotlinx.serialization.Serializable
3639
import kotlinx.serialization.encodeToString
3740
import java.net.URI
41+
import java.util.Calendar
3842

3943
interface TrackableSuperwallEvent : Trackable {
4044
val superwallPlacement: SuperwallEvent
@@ -285,14 +289,21 @@ sealed class InternalSuperwallEvent(
285289
override var audienceFilterParams: HashMap<String, Any> = HashMap(),
286290
) : InternalSuperwallEvent(SuperwallEvent.SubscriptionStatusDidChange()) {
287291
override suspend fun getSuperwallParameters(): HashMap<String, Any> =
288-
hashMapOf(
292+
hashMapOf<String, Any>(
289293
"subscription_status" to
290294
when (subscriptionStatus) {
291295
is SubscriptionStatus.Active -> "active"
292296
is SubscriptionStatus.Inactive -> "inactive"
293297
is SubscriptionStatus.Unknown -> "unknown"
294298
},
295-
)
299+
).apply {
300+
if (subscriptionStatus is SubscriptionStatus.Active) {
301+
put(
302+
"active_entitlement_ids",
303+
subscriptionStatus.entitlements.joinToString(",") { it.id },
304+
)
305+
}
306+
}
296307
}
297308

298309
class SessionStart(
@@ -518,8 +529,60 @@ sealed class InternalSuperwallEvent(
518529

519530
class Complete(
520531
val product: StoreProductType,
521-
val transaction: StoreTransactionType?,
522-
) : State()
532+
transaction: StoreTransactionType?,
533+
) : State() {
534+
val transaction =
535+
when (transaction) {
536+
is GoogleBillingPurchaseTransaction -> {
537+
val rawProduct = product as? RawStoreProduct
538+
if (rawProduct == null) {
539+
transaction
540+
} else {
541+
transaction.copy(
542+
expirationDate =
543+
transaction.transactionDate?.let { date ->
544+
product.subscriptionPeriod?.let { period ->
545+
Calendar
546+
.getInstance()
547+
.apply {
548+
time = date
549+
when (period.unit) {
550+
SubscriptionPeriod.Unit.day ->
551+
add(
552+
Calendar.DAY_OF_YEAR,
553+
period.value,
554+
)
555+
556+
SubscriptionPeriod.Unit.week ->
557+
add(
558+
Calendar.WEEK_OF_YEAR,
559+
period.value,
560+
)
561+
562+
SubscriptionPeriod.Unit.month ->
563+
add(
564+
Calendar.MONTH,
565+
period.value,
566+
)
567+
568+
SubscriptionPeriod.Unit.year ->
569+
add(
570+
Calendar.YEAR,
571+
period.value,
572+
)
573+
}
574+
}.time
575+
}
576+
},
577+
subscriptionGroupId = rawProduct?.basePlanId,
578+
offerId = rawProduct?.offerId,
579+
)
580+
}
581+
}
582+
583+
else -> transaction
584+
}
585+
}
523586

524587
class Restore(
525588
val restoreType: RestoreType,
@@ -560,12 +623,13 @@ sealed class InternalSuperwallEvent(
560623
paywallInfo = paywallInfo,
561624
)
562625

563-
is State.Complete ->
626+
is State.Complete -> {
564627
SuperwallEvent.TransactionComplete(
565628
transaction = state.transaction,
566629
product = state.product,
567630
paywallInfo = paywallInfo,
568631
)
632+
}
569633

570634
is State.Restore ->
571635
SuperwallEvent.TransactionRestore(
@@ -1183,11 +1247,13 @@ sealed class InternalSuperwallEvent(
11831247
permissionName = permissionName,
11841248
paywallIdentifier = paywallIdentifier,
11851249
)
1250+
11861251
State.Granted ->
11871252
SuperwallEvent.PermissionGranted(
11881253
permissionName = permissionName,
11891254
paywallIdentifier = paywallIdentifier,
11901255
)
1256+
11911257
State.Denied ->
11921258
SuperwallEvent.PermissionDenied(
11931259
permissionName = permissionName,
@@ -1229,4 +1295,26 @@ sealed class InternalSuperwallEvent(
12291295
"paywall_count" to paywallCount,
12301296
)
12311297
}
1298+
1299+
data class TestModeModal(
1300+
val state: State,
1301+
) : InternalSuperwallEvent(
1302+
SuperwallEvent.TestModeModalOpen(),
1303+
) {
1304+
enum class State {
1305+
Open,
1306+
Close,
1307+
}
1308+
1309+
override val superwallPlacement: SuperwallEvent
1310+
get() =
1311+
when (state) {
1312+
State.Open -> SuperwallEvent.TestModeModalOpen()
1313+
State.Close -> SuperwallEvent.TestModeModalClose()
1314+
}
1315+
1316+
override val audienceFilterParams: Map<String, Any> = emptyMap()
1317+
1318+
override suspend fun getSuperwallParameters(): Map<String, Any> = emptyMap()
1319+
}
12321320
}

superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvent.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,16 @@ sealed class SuperwallEvent {
459459
get() = "cel_expression_result"
460460
}
461461

462+
class TestModeModalOpen : SuperwallEvent() {
463+
override val rawName: String
464+
get() = SuperwallEvents.TestModeModalOpen.rawName
465+
}
466+
467+
class TestModeModalClose : SuperwallEvent() {
468+
override val rawName: String
469+
get() = SuperwallEvents.TestModeModalClose.rawName
470+
}
471+
462472
object RedemptionComplete : SuperwallPlacement() {
463473
override val rawName: String
464474
get() = SuperwallEvents.RedemptionComplete.rawName

superwall/src/main/java/com/superwall/sdk/analytics/superwall/SuperwallEvents.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,6 @@ enum class SuperwallEvents(
6565
PermissionDenied("permission_denied"),
6666
PaywallPreloadStart("paywallPreload_start"),
6767
PaywallPreloadComplete("paywallPreload_complete"),
68+
TestModeModalOpen("testModeModal_open"),
69+
TestModeModalClose("testModeModal_close"),
6870
}

superwall/src/main/java/com/superwall/sdk/billing/ConsumePurchaseUseCase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ internal class ConsumePurchaseUseCase(
1515
private val useCaseParams: ConsumePurchaseUseCaseParams,
1616
private val onReceive: (String) -> Unit,
1717
onError: (BillingError) -> Unit,
18-
private val withConnectedClient: (BillingClient.() -> Unit) -> Unit,
18+
private val withConnectedClient: (BillingClient.() -> Unit) -> Unit?,
1919
executeRequestOnUIThread: ExecuteRequestOnUIThreadFunction,
2020
) : BillingClientUseCase<String>(useCaseParams, onError, executeRequestOnUIThread) {
2121
override fun executeAsync() {

0 commit comments

Comments
 (0)