You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/developer-guide/Monetization.asciidoc
+31-16Lines changed: 31 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -64,21 +64,6 @@ public class HelloWorldIAP implements PurchaseCallback {
64
64
...
65
65
}
66
66
67
-
@Override
68
-
public void itemRefunded(String sku) {
69
-
...
70
-
}
71
-
72
-
@Override
73
-
public void subscriptionStarted(String sku) {
74
-
...
75
-
}
76
-
77
-
@Override
78
-
public void subscriptionCanceled(String sku) {
79
-
...
80
-
}
81
-
82
67
@Override
83
68
public void paymentFailed(String paymentCode, String failureReason) {
84
69
...
@@ -92,7 +77,7 @@ public class HelloWorldIAP implements PurchaseCallback {
92
77
}
93
78
----
94
79
95
-
Using these callbacks, we'll be notified whenever something changes in our purchases. For our simple app we're only interested in `itemPurchased()` and `itemPurchaseError()`.
80
+
Using these callbacks, we'll be notified whenever something changes in our purchases. For our simple app we're only interested in `itemPurchased()` and `itemPurchaseError()`. The legacy `itemRefunded()`, `subscriptionStarted()`, and `subscriptionCanceled()` hooks have been deprecated in the core API and are no longer dispatched by the stores. Instead of relying on callbacks for long-term entitlement state, query the current receipts at runtime using helpers such as `Purchase.wasPurchased(...)`, `Purchase.isSubscribed(...)`, or by iterating through `Purchase.getReceipts()` so that your UI always reflects the latest data provided by the underlying store.
96
81
97
82
Now in the start method, we'll add a button that allows the user to buy the world:
98
83
@@ -222,6 +207,36 @@ NOTE: The concept of an "Non-renewable" subscription is unique to iTunes. Googl
222
207
223
208
IMPORTANT: The `Purchase` class includes both a `purchase()` method and a `subscribe()` method. On some platforms it makes no difference which one you use, but on Android it matters. If the product is set up as a subscription in Google Play, then you *must* use `subscribe()` to purchase the product. If it is set up as a regular product, then you *must* use `purchase()`. Since we enter "Non-renewable" subscriptions as regular products in the play store, we would use the `purchase()` method.
224
209
210
+
==== Promotional offers (iOS)
211
+
212
+
Apple allows you to present discounted introductory pricing to existing subscribers via https://developer.apple.com/documentation/storekit/skpaymentdiscount[promotional offers]. Codename One surfaces this capability through overloads of both `Purchase.purchase(String, PromotionalOffer)` and `Purchase.subscribe(String, PromotionalOffer)`, which forward the promotional context to StoreKit when you initiate the transaction. Promotional offers are only honoured by iOS, so the overloads simply fall back to the regular purchase flow on other platforms.
213
+
214
+
To build the signed discount payload required by Apple you can use the `ApplePromotionalOffer` helper:
215
+
216
+
[source,java]
217
+
----
218
+
ApplePromotionalOffer offer = new ApplePromotionalOffer();
219
+
offer.setOfferIdentifier("my-intro-offer");
220
+
offer.setKeyIdentifier("A1B2C3D4");
221
+
offer.setNonce(UUID.randomUUID().toString());
222
+
offer.setSignature(signatureFromYourServer);
223
+
offer.setTimestamp(timestampFromYourServer);
224
+
225
+
Purchase purchase = Purchase.getInAppPurchase();
226
+
purchase.subscribe(SKU_WORLD_MONTHLY, offer);
227
+
----
228
+
229
+
Apple generates the signature and timestamp from your App Store Connect server notifications endpoint; Codename One simply passes them to the native StoreKit APIs. For one-time products you can call `purchase(sku, offer)` instead of `subscribe(...)`.
230
+
231
+
==== Restoring purchases and managing subscriptions
232
+
233
+
Both Apple and Google provide built-in user interfaces for restoring past purchases and managing subscription billing preferences. Codename One exposes these entry points so you can surface the native flows without reimplementing them yourself.
234
+
235
+
* Restores: Call `Purchase.isRestoreSupported()` before presenting a "Restore purchases" button. When supported (iOS currently implements this natively), invoke `Purchase.restore()` to prompt the operating system to re-deliver past transactions. Your app should implement `RestoreCallback` (similar to how you implement `PurchaseCallback`) so you can respond to individual `itemRestored(...)` events and to the completion or failure of the restore request.
236
+
* Subscription management: Use `Purchase.isManageSubscriptionsSupported()` to detect whether the platform can show the subscription management UI. When it returns `true`, calling `Purchase.manageSubscriptions(null)` opens the store-specific settings screen (Apple's subscription center on iOS and Google Play's subscription management activity on Android). On Android you can optionally pass a SKU to deep-link directly to the plan the user should manage.
237
+
238
+
Because these flows are handled by the underlying store your UI doesn't need to rebuild any billing screens. Simply gate the buttons on the capability checks above so that iOS and Android users get the familiar restore/manage dialogs while other platforms can fall back to your own help copy.
239
+
225
240
==== The Server-Side
226
241
227
242
Since a subscription purchased on one user device *needs* to be available across the user's devices (Apple's rules for non-renewable subscriptions), our app will need to have a server-component. In this section, we'll gloss over that & "mock" the server interface. We'll go into the specifics of the server-side below.
0 commit comments