Skip to content

Commit

Permalink
Add Amazon freeTrialPeriod to introPrice (#952)
Browse files Browse the repository at this point in the history
  • Loading branch information
vegaro authored Oct 29, 2024
1 parent 6f39586 commit 68eba8c
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.revenuecat.purchases.hybridcommon.mappers

import androidx.annotation.VisibleForTesting
import com.revenuecat.purchases.ProductType
import com.revenuecat.purchases.amazon.AmazonStoreProduct
import com.revenuecat.purchases.models.InstallmentsInfo
import com.revenuecat.purchases.models.Period
import com.revenuecat.purchases.models.Price
Expand All @@ -15,21 +16,22 @@ val StoreProduct.priceString: String
get() = this.price.formatted
val StoreProduct.priceCurrencyCode: String
get() = this.price.currencyCode
val StoreProduct.freeTrialPeriod: Period?

val StoreProduct.googleFreeTrialPeriod: Period?
get() = this.defaultOption?.freePhase?.billingPeriod
val StoreProduct.freeTrialCycles: Int?
val StoreProduct.googleFreeTrialCycles: Int?
get() = this.defaultOption?.freePhase?.billingCycleCount

private val StoreProduct.introductoryPhase: PricingPhase?
private val StoreProduct.googleIntroductoryPhase: PricingPhase?
get() = this.defaultOption?.introPhase
val StoreProduct.introductoryPrice: String?
get() = this.introductoryPhase?.price?.formatted
val StoreProduct.introductoryPricePeriod: Period?
get() = this.introductoryPhase?.billingPeriod
val StoreProduct.introductoryPriceAmountMicros: Long
get() = this.introductoryPhase?.price?.amountMicros ?: 0
val StoreProduct.introductoryPriceCycles: Int
get() = this.introductoryPhase?.billingCycleCount ?: 0
val StoreProduct.googleIntroductoryPrice: String?
get() = this.googleIntroductoryPhase?.price?.formatted
val StoreProduct.googleIntroductoryPricePeriod: Period?
get() = this.googleIntroductoryPhase?.billingPeriod
val StoreProduct.googleIntroductoryPriceAmountMicros: Long
get() = this.googleIntroductoryPhase?.price?.amountMicros ?: 0
val StoreProduct.googleIntroductoryPriceCycles: Int
get() = this.googleIntroductoryPhase?.billingCycleCount ?: 0

private const val DAYS_PER_WEEK = 7
private const val MICROS_CONVERSION_METRIC = 1_000_000.0
Expand Down Expand Up @@ -102,37 +104,47 @@ internal fun StoreProduct.mapProductType(): String {

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun StoreProduct.mapIntroPrice(): Map<String, Any?>? {
return when {
freeTrialPeriod != null -> {
// Check freeTrialPeriod first to give priority to trials
// Format using device locale. iOS will format using App Store locale, but there's no way
// to figure out how the price in the SKUDetails is being formatted.
freeTrialPeriod?.mapPeriodForStoreProduct()?.let { periodFields ->
mapOf(
"price" to 0,
"priceString" to formatUsingDeviceLocale(priceCurrencyCode, 0),
"period" to freeTrialPeriod?.iso8601,
"cycles" to (freeTrialCycles ?: 1),
) + periodFields
}
return when (this) {
is AmazonStoreProduct -> freeTrialPeriod?.mapPeriodForStoreProduct()?.let { periodFields ->
mapOf(
"price" to 0,
"priceString" to formatUsingDeviceLocale(priceCurrencyCode, 0),
"period" to freeTrialPeriod?.iso8601,
"cycles" to 1,
) + periodFields
}
introductoryPrice != null -> {
introductoryPricePeriod?.mapPeriodForStoreProduct()?.let { periodFields ->
mapOf(
"price" to introductoryPriceAmountMicros / MICROS_CONVERSION_METRIC,
"priceString" to introductoryPrice,
"period" to introductoryPricePeriod?.iso8601,
"cycles" to introductoryPriceCycles,
) + periodFields
else -> when {
googleFreeTrialPeriod != null -> {
// Check freeTrialPeriod first to give priority to trials
// Format using device locale. iOS will format using App Store locale, but there's no way
// to figure out how the price in the SKUDetails is being formatted.
googleFreeTrialPeriod?.mapPeriodForStoreProduct()?.let { periodFields ->
mapOf(
"price" to 0,
"priceString" to formatUsingDeviceLocale(priceCurrencyCode, 0),
"period" to googleFreeTrialPeriod?.iso8601,
"cycles" to (googleFreeTrialCycles ?: 1),
) + periodFields
}
}
googleIntroductoryPrice != null -> {
googleIntroductoryPricePeriod?.mapPeriodForStoreProduct()?.let { periodFields ->
mapOf(
"price" to googleIntroductoryPriceAmountMicros / MICROS_CONVERSION_METRIC,
"priceString" to googleIntroductoryPrice,
"period" to googleIntroductoryPricePeriod?.iso8601,
"cycles" to googleIntroductoryPriceCycles,
) + periodFields
}
}
else -> {
null
}
}
else -> {
null
}
}
}

private fun Period.mapPeriodForStoreProduct(): Map<String, Any?>? {
private fun Period.mapPeriodForStoreProduct(): Map<String, Any?> {
return when (this.unit) {
Period.Unit.DAY -> mapOf(
"periodUnit" to "DAY",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.revenuecat.purchases.hybridcommon.mappers

import com.revenuecat.purchases.amazon.AmazonStoreProduct
import com.revenuecat.purchases.models.Period
import com.revenuecat.purchases.models.Price
import com.revenuecat.purchases.models.PricingPhase
Expand Down Expand Up @@ -93,10 +94,10 @@ internal class StoreProductIntroPriceMapperTests {
inner class MappingIntroPrice {
@BeforeEach
fun beforeEachTest() {
every { mockStoreProduct.freeTrialPeriod?.iso8601 } returns null
every { mockStoreProduct.introductoryPriceAmountMicros } returns 10_000_000
every { mockStoreProduct.introductoryPrice } returns "$10.00"
every { mockStoreProduct.introductoryPriceCycles } returns 2
every { mockStoreProduct.googleFreeTrialPeriod?.iso8601 } returns null
every { mockStoreProduct.googleIntroductoryPriceAmountMicros } returns 10_000_000
every { mockStoreProduct.googleIntroductoryPrice } returns "$10.00"
every { mockStoreProduct.googleIntroductoryPriceCycles } returns 2
}

private val expectedCommon = mapOf(
Expand All @@ -107,8 +108,8 @@ internal class StoreProductIntroPriceMapperTests {

@Test
fun `of 7 days, the map has the correct intro price values`() {
every { mockStoreProduct.freeTrialPeriod } returns null
every { mockStoreProduct.introductoryPricePeriod } returns Period(7, Period.Unit.DAY, "P7D")
every { mockStoreProduct.googleFreeTrialPeriod } returns null
every { mockStoreProduct.googleIntroductoryPricePeriod } returns Period(7, Period.Unit.DAY, "P7D")
received = mockStoreProduct.mapIntroPrice()
val expected = mapOf(
"period" to "P7D",
Expand All @@ -120,8 +121,8 @@ internal class StoreProductIntroPriceMapperTests {

@Test
fun `of 1 month, the map has the correct intro price values`() {
every { mockStoreProduct.freeTrialPeriod } returns null
every { mockStoreProduct.introductoryPricePeriod } returns Period(1, Period.Unit.MONTH, "P1M")
every { mockStoreProduct.googleFreeTrialPeriod } returns null
every { mockStoreProduct.googleIntroductoryPricePeriod } returns Period(1, Period.Unit.MONTH, "P1M")
received = mockStoreProduct.mapIntroPrice()

val expected = mapOf(
Expand All @@ -134,8 +135,8 @@ internal class StoreProductIntroPriceMapperTests {

@Test
fun `of 0 days, the map has the correct intro price values`() {
every { mockStoreProduct.freeTrialPeriod } returns null
every { mockStoreProduct.introductoryPricePeriod } returns Period(0, Period.Unit.DAY, "P0D")
every { mockStoreProduct.googleFreeTrialPeriod } returns null
every { mockStoreProduct.googleIntroductoryPricePeriod } returns Period(0, Period.Unit.DAY, "P0D")
received = mockStoreProduct.mapIntroPrice()

val expected = mapOf(
Expand All @@ -149,9 +150,35 @@ internal class StoreProductIntroPriceMapperTests {

@Test
fun `when mapping a StoreProduct with no free trial nor introductory price, intro price is null`() {
every { mockStoreProduct.freeTrialPeriod } returns null
every { mockStoreProduct.introductoryPrice } returns null
every { mockStoreProduct.googleFreeTrialPeriod } returns null
every { mockStoreProduct.googleIntroductoryPrice } returns null
received = mockStoreProduct.mapIntroPrice()
assertThat(received).isEqualTo(null)
}

@Test
fun `when mapping a AmazonStoreProduct with no free trial nor introductory price, intro price is null`() {
val mockAmazonStoreProduct = mockk<AmazonStoreProduct>(relaxed = true)

every { mockAmazonStoreProduct.freeTrialPeriod } returns null
received = mockAmazonStoreProduct.mapIntroPrice()
assertThat(received).isEqualTo(null)
}

@Test
fun `when mapping a AmazonStoreProduct with free trial nor introductory price, introPrice has the free trial`() {
val mockAmazonStoreProduct = mockk<AmazonStoreProduct>(relaxed = true)
every { mockAmazonStoreProduct.priceCurrencyCode } returns "USD"
every { mockAmazonStoreProduct.freeTrialPeriod } returns Period(1, Period.Unit.DAY, "P1D")
received = mockAmazonStoreProduct.mapIntroPrice()
val expected = mapOf(
"cycles" to 1,
"period" to "P1D",
"periodUnit" to "DAY",
"periodNumberOfUnits" to 1,
"price" to 0,
"priceString" to "$0.00",
)
assertThat(expected).isEqualTo(received)
}
}

0 comments on commit 68eba8c

Please sign in to comment.