Skip to content
This repository was archived by the owner on Oct 17, 2025. It is now read-only.

Commit c7af9f0

Browse files
committed
fix(example): update subscription upgrade/downgrade senario
1 parent b5f21d2 commit c7af9f0

File tree

11 files changed

+755
-178
lines changed

11 files changed

+755
-178
lines changed

Example/src/main/java/dev/hyo/martie/Constants.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ object IapConstants {
99
)
1010

1111
val SUBS_SKUS = listOf(
12-
"dev.hyo.martie.premium",
13-
"dev.hyo.martie.premium_year"
12+
"dev.hyo.martie.premium", // Main subscription with multiple offers
13+
"dev.hyo.martie.premium_year" // Separate yearly subscription product
1414
)
15+
16+
// Base plan IDs for dev.hyo.martie.premium subscription
17+
const val PREMIUM_MONTHLY_BASE_PLAN = "premium" // Monthly base plan
18+
const val PREMIUM_YEARLY_BASE_PLAN = "premium-year" // Yearly base plan
1519
}
1620

Example/src/main/java/dev/hyo/martie/screens/AllProductsScreen.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import dev.hyo.openiap.Product
2727
import dev.hyo.openiap.ProductAndroid
2828
import dev.hyo.openiap.ProductQueryType
2929
import dev.hyo.openiap.ProductType
30+
import dev.hyo.openiap.ProductRequest
3031
import dev.hyo.openiap.ProductSubscription
3132
import dev.hyo.openiap.store.OpenIapStore
3233
import dev.hyo.openiap.store.PurchaseResultStatus
@@ -64,10 +65,11 @@ fun AllProductsScreen(
6465
iapStore.setActivity(activity)
6566
// Fetch all products at once using ProductQueryType.All
6667
// This fetches both in-app and subscription products in a single call
67-
iapStore.fetchProducts(
68+
val request = ProductRequest(
6869
skus = IapConstants.INAPP_SKUS + IapConstants.SUBS_SKUS,
6970
type = ProductQueryType.All
7071
)
72+
iapStore.fetchProducts(request)
7173
}
7274
} catch (_: Exception) { }
7375
}
@@ -140,10 +142,11 @@ fun AllProductsScreen(
140142
if (connected) {
141143
iapStore.setActivity(activity)
142144
// Fetch all products after reconnecting using ProductQueryType.All
143-
iapStore.fetchProducts(
145+
val request = ProductRequest(
144146
skus = IapConstants.INAPP_SKUS + IapConstants.SUBS_SKUS,
145147
type = ProductQueryType.All
146148
)
149+
iapStore.fetchProducts(request)
147150
}
148151
} catch (_: Exception) { }
149152
}

Example/src/main/java/dev/hyo/martie/screens/AvailablePurchasesScreen.kt

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import androidx.compose.ui.unit.dp
2020
import androidx.navigation.NavController
2121
import dev.hyo.martie.models.AppColors
2222
import dev.hyo.martie.screens.uis.*
23+
import dev.hyo.martie.util.PREMIUM_SUBSCRIPTION_PRODUCT_ID
2324
import dev.hyo.openiap.IapContext
2425
import dev.hyo.openiap.PurchaseAndroid
2526
import dev.hyo.openiap.PurchaseState
@@ -53,7 +54,7 @@ fun AvailablePurchasesScreen(
5354
try {
5455
val connected = iapStore.initConnection()
5556
if (connected) {
56-
iapStore.getAvailablePurchases()
57+
iapStore.getAvailablePurchases(null)
5758
}
5859
} catch (_: Exception) { }
5960
}
@@ -77,7 +78,7 @@ fun AvailablePurchasesScreen(
7778
onClick = {
7879
scope.launch {
7980
try {
80-
val restored = iapStore.restorePurchases()
81+
val restored = iapStore.getAvailablePurchases(null)
8182
iapStore.postStatusMessage(
8283
message = "Restored ${restored.size} purchases",
8384
status = PurchaseResultStatus.Success
@@ -185,14 +186,14 @@ fun AvailablePurchasesScreen(
185186
}
186187
val nonConsumables = androidPurchases.filter {
187188
!it.isAutoRenewing &&
188-
it.productId != "dev.hyo.martie.premium" &&
189+
it.productId != PREMIUM_SUBSCRIPTION_PRODUCT_ID &&
189190
!(
190191
it.productId.contains("consumable", ignoreCase = true) ||
191192
it.productId.contains("bulb", ignoreCase = true)
192193
)
193194
}
194195
val subscriptions = androidPurchases.filter {
195-
it.isAutoRenewing || it.productId == "dev.hyo.martie.premium"
196+
it.isAutoRenewing || it.productId == PREMIUM_SUBSCRIPTION_PRODUCT_ID
196197
}
197198

198199
// Check for unfinished transactions (purchases that need acknowledgment/consumption)
@@ -217,21 +218,24 @@ fun AvailablePurchasesScreen(
217218
onFinish = { isConsumable ->
218219
scope.launch {
219220
try {
220-
val ok = iapStore.finishTransaction(purchase, isConsumable)
221-
if (ok) {
222-
iapStore.postStatusMessage(
223-
message = "Transaction finished successfully",
224-
status = PurchaseResultStatus.Success,
225-
productId = purchase.productId
226-
)
227-
iapStore.getAvailablePurchases()
228-
} else {
229-
iapStore.postStatusMessage(
230-
message = "Failed to finish transaction",
231-
status = PurchaseResultStatus.Error,
232-
productId = purchase.productId
233-
)
234-
}
221+
val purchaseInput = dev.hyo.openiap.PurchaseInput(
222+
id = purchase.id,
223+
ids = purchase.ids,
224+
isAutoRenewing = purchase.isAutoRenewing,
225+
platform = purchase.platform,
226+
productId = purchase.productId,
227+
purchaseState = purchase.purchaseState,
228+
purchaseToken = purchase.purchaseToken,
229+
quantity = purchase.quantity,
230+
transactionDate = purchase.transactionDate
231+
)
232+
iapStore.finishTransaction(purchaseInput, isConsumable)
233+
iapStore.postStatusMessage(
234+
message = "Transaction finished successfully",
235+
status = PurchaseResultStatus.Success,
236+
productId = purchase.productId
237+
)
238+
iapStore.getAvailablePurchases(null)
235239
} catch (e: Exception) {
236240
iapStore.postStatusMessage(
237241
message = e.message ?: "Failed to finish transaction",
@@ -399,7 +403,7 @@ fun AvailablePurchasesScreen(
399403
horizontalArrangement = Arrangement.spacedBy(8.dp)
400404
) {
401405
OutlinedButton(
402-
onClick = { scope.launch { iapStore.getAvailablePurchases() } },
406+
onClick = { scope.launch { iapStore.getAvailablePurchases(null) } },
403407
modifier = Modifier.weight(1f),
404408
enabled = !status.isLoading
405409
) {
@@ -412,7 +416,7 @@ fun AvailablePurchasesScreen(
412416
onClick = {
413417
scope.launch {
414418
try {
415-
val restored = iapStore.restorePurchases()
419+
val restored = iapStore.getAvailablePurchases(null)
416420
iapStore.postStatusMessage(
417421
message = "Restored ${restored.size} purchases",
418422
status = PurchaseResultStatus.Success

Example/src/main/java/dev/hyo/martie/screens/PurchaseFlowScreen.kt

Lines changed: 77 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,17 @@ import kotlinx.coroutines.flow.first
3131
import kotlinx.coroutines.launch
3232
import dev.hyo.openiap.ProductAndroid
3333
import dev.hyo.openiap.ProductQueryType
34+
import dev.hyo.openiap.ProductRequest
3435
import dev.hyo.openiap.ProductType
3536
import dev.hyo.openiap.Purchase
3637
import dev.hyo.openiap.PurchaseAndroid
38+
import dev.hyo.openiap.PurchaseInput
39+
import dev.hyo.openiap.RequestPurchaseProps
40+
import dev.hyo.openiap.RequestPurchaseAndroidProps
41+
import dev.hyo.openiap.RequestPurchasePropsByPlatforms
42+
import dev.hyo.openiap.RequestSubscriptionAndroidProps
43+
import dev.hyo.openiap.RequestSubscriptionPropsByPlatforms
44+
import dev.hyo.openiap.utils.toPurchaseInput
3745
import dev.hyo.martie.util.findActivity
3846

3947
@OptIn(ExperimentalMaterial3Api::class)
@@ -74,11 +82,12 @@ fun PurchaseFlowScreen(
7482
val connected = iapStore.initConnection()
7583
if (connected) {
7684
iapStore.setActivity(activity)
77-
iapStore.fetchProducts(
85+
val request = ProductRequest(
7886
skus = IapConstants.INAPP_SKUS,
7987
type = ProductQueryType.InApp
8088
)
81-
iapStore.getAvailablePurchases()
89+
iapStore.fetchProducts(request)
90+
iapStore.getAvailablePurchases(null)
8291
}
8392
} catch (_: Exception) { }
8493
}
@@ -107,10 +116,11 @@ fun PurchaseFlowScreen(
107116
scope.launch {
108117
try {
109118
iapStore.setActivity(activity)
110-
iapStore.fetchProducts(
119+
val request = ProductRequest(
111120
skus = IapConstants.INAPP_SKUS,
112121
type = ProductQueryType.InApp
113122
)
123+
iapStore.fetchProducts(request)
114124
} catch (_: Exception) { }
115125
}
116126
},
@@ -238,13 +248,32 @@ fun PurchaseFlowScreen(
238248
isPurchasing = status.isPurchasing(androidProduct.id),
239249
onPurchase = {
240250
scope.launch {
241-
val reqType = if (androidProduct.type == ProductType.Subs)
242-
ProductQueryType.Subs else ProductQueryType.InApp
243251
iapStore.setActivity(activity)
244-
iapStore.requestPurchase(
245-
skus = listOf(androidProduct.id),
246-
type = reqType
247-
)
252+
if (androidProduct.type == ProductType.Subs) {
253+
val props = RequestPurchaseProps(
254+
request = RequestPurchaseProps.Request.Subscription(
255+
RequestSubscriptionPropsByPlatforms(
256+
android = RequestSubscriptionAndroidProps(
257+
skus = listOf(androidProduct.id)
258+
)
259+
)
260+
),
261+
type = ProductQueryType.Subs
262+
)
263+
iapStore.requestPurchase(props)
264+
} else {
265+
val props = RequestPurchaseProps(
266+
request = RequestPurchaseProps.Request.Purchase(
267+
RequestPurchasePropsByPlatforms(
268+
android = RequestPurchaseAndroidProps(
269+
skus = listOf(androidProduct.id)
270+
)
271+
)
272+
),
273+
type = ProductQueryType.InApp
274+
)
275+
iapStore.requestPurchase(props)
276+
}
248277
}
249278
},
250279
onClick = {
@@ -263,23 +292,7 @@ fun PurchaseFlowScreen(
263292
)
264293
}
265294
}
266-
267-
// Active Purchases Section
268-
if (androidPurchases.isNotEmpty()) {
269-
item {
270-
SectionHeaderView(title = "Active Purchases")
271-
}
272-
273-
items(androidPurchases) { androidPurchase ->
274-
ActivePurchaseCard(
275-
purchase = androidPurchase,
276-
onClick = {
277-
selectedPurchase = androidPurchase
278-
}
279-
)
280-
}
281-
}
282-
295+
283296
// Instructions Card
284297
item {
285298
InstructionCard()
@@ -297,7 +310,7 @@ fun PurchaseFlowScreen(
297310
onClick = {
298311
scope.launch {
299312
try {
300-
val restored = iapStore.restorePurchases()
313+
val restored = iapStore.getAvailablePurchases(null)
301314
iapStore.postStatusMessage(
302315
message = "Restored ${restored.size} purchases",
303316
status = PurchaseResultStatus.Success
@@ -322,10 +335,11 @@ fun PurchaseFlowScreen(
322335
onClick = {
323336
scope.launch {
324337
try {
325-
iapStore.fetchProducts(
338+
val request = ProductRequest(
326339
skus = IapConstants.INAPP_SKUS,
327340
type = ProductQueryType.InApp
328341
)
342+
iapStore.fetchProducts(request)
329343
} catch (_: Exception) { }
330344
}
331345
},
@@ -380,21 +394,22 @@ fun PurchaseFlowScreen(
380394
}
381395

382396
// 4) Finish transaction
383-
val ok = iapStore.finishTransaction(purchase, isConsumable)
384-
if (!ok) {
385-
iapStore.postStatusMessage(
386-
message = "finishTransaction failed",
387-
status = PurchaseResultStatus.Error,
388-
productId = purchase.productId
389-
)
390-
} else {
391-
iapStore.loadPurchases()
397+
val purchaseInput = purchase.toPurchaseInput()
398+
try {
399+
iapStore.finishTransaction(purchaseInput, isConsumable)
400+
iapStore.getAvailablePurchases(null) // Reload purchases after finishing
392401
iapStore.postStatusMessage(
393402
message = "Purchase finished successfully",
394403
status = PurchaseResultStatus.Success,
395404
productId = purchase.productId
396405
)
397406
selectedProduct = null
407+
} catch (e: Exception) {
408+
iapStore.postStatusMessage(
409+
message = "finishTransaction failed: ${e.message}",
410+
status = PurchaseResultStatus.Error,
411+
productId = purchase.productId
412+
)
398413
}
399414
} catch (e: Exception) {
400415
iapStore.postStatusMessage(
@@ -412,13 +427,32 @@ fun PurchaseFlowScreen(
412427
onDismiss = { selectedProduct = null },
413428
onPurchase = {
414429
uiScope.launch {
415-
val reqType = if (product.type == ProductType.Subs)
416-
ProductQueryType.Subs else ProductQueryType.InApp
417430
iapStore.setActivity(activity)
418-
iapStore.requestPurchase(
419-
skus = listOf(product.id),
420-
type = reqType
421-
)
431+
if (product.type == ProductType.Subs) {
432+
val props = RequestPurchaseProps(
433+
request = RequestPurchaseProps.Request.Subscription(
434+
RequestSubscriptionPropsByPlatforms(
435+
android = RequestSubscriptionAndroidProps(
436+
skus = listOf(product.id)
437+
)
438+
)
439+
),
440+
type = ProductQueryType.Subs
441+
)
442+
iapStore.requestPurchase(props)
443+
} else {
444+
val props = RequestPurchaseProps(
445+
request = RequestPurchaseProps.Request.Purchase(
446+
RequestPurchasePropsByPlatforms(
447+
android = RequestPurchaseAndroidProps(
448+
skus = listOf(product.id)
449+
)
450+
)
451+
),
452+
type = ProductQueryType.InApp
453+
)
454+
iapStore.requestPurchase(props)
455+
}
422456
}
423457
},
424458
isPurchasing = status.isPurchasing(product.id)

0 commit comments

Comments
 (0)