1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 /**
30     Provides in-app purchase functionality.
31 
32     Your app should create a single instance of this class, and on iOS it should
33     be created as soon as your app starts. This is because on application startup
34     any previously pending transactions will be resumed.
35 
36     Once an InAppPurchases object is created, call addListener() to attach listeners.
37 
38     @tags{ProductUnlocking}
39 */
40 class JUCE_API  InAppPurchases  : private DeletedAtShutdown
41 {
42 public:
43     #ifndef DOXYGEN
44      JUCE_DECLARE_SINGLETON (InAppPurchases, false)
45     #endif
46 
47     //==============================================================================
48     /** Represents a product available in the store. */
49     struct Product
50     {
51         /** Product ID (also known as SKU) that uniquely identifies a product in the store. */
52         String identifier;
53 
54         /** Title of the product. */
55         String title;
56 
57         /** Description of the product. */
58         String description;
59 
60         /** Price of the product in local currency. */
61         String price;
62 
63         /** Price locale. */
64         String priceLocale;
65     };
66 
67     //==============================================================================
68     /** Represents a purchase of a product in the store. */
69     struct Purchase
70     {
71         /** A unique order identifier for the transaction (generated by the store). */
72         String orderId;
73 
74         /** A unique identifier of in-app product that was purchased. */
75         String productId;
76 
77         /** This will be bundle ID on iOS and package name on Android, of the application for which this
78             in-app product was purchased. */
79         String applicationBundleName;
80 
81         /** Date of the purchase (in ISO8601 format). */
82         String purchaseTime;
83 
84         /** Android only: purchase token that should be used to consume purchase, provided that In-App product
85             is consumable. */
86         String purchaseToken;
87     };
88 
89     //==============================================================================
90     /** iOS only: represents in-app purchase download. Download will be available only
91         for purchases that are hosted on the AppStore. */
92     struct Download
93     {
94         enum class Status
95         {
96             waiting = 0,    /**< The download is waiting to start. Called at the beginning of a download operation. */
97             active,         /**< The download is in progress. */
98             paused,         /**< The download was paused and is awaiting resuming or cancelling. */
99             finished,       /**< The download was finished successfully. */
100             failed,         /**< The download failed (e.g. because of no internet connection). */
101             cancelled,      /**< The download was cancelled. */
102         };
103 
~DownloadDownload104         virtual ~Download() {}
105 
106         /** A unique identifier for the in-app product to be downloaded. */
107         virtual String getProductId() const = 0;
108 
109         /** Content length in bytes. */
110         virtual int64 getContentLength() const = 0;
111 
112         /** Content version. */
113         virtual String getContentVersion() const = 0;
114 
115         /** Returns current status of the download. */
116         virtual Status getStatus() const = 0;
117     };
118 
119 
120     //==============================================================================
121     /** Represents an object that gets notified about events such as product info returned or product purchase
122         finished. */
123     struct Listener
124     {
~ListenerListener125         virtual ~Listener() {}
126 
127         /** Called whenever a product info is returned after a call to InAppPurchases::getProductsInformation(). */
productsInfoReturnedListener128         virtual void productsInfoReturned (const Array<Product>& /*products*/) {}
129 
130         /** Structure holding purchase information */
131         struct PurchaseInfo
132         {
133             Purchase purchase;
134             Array<Download*> downloads;
135         };
136 
137         /** Called whenever a purchase is complete, with additional state whether the purchase completed successfully.
138 
139             For hosted content (iOS only), the downloads array within PurchaseInfo will contain all download objects corresponding
140             with the purchase. For non-hosted content, the downloads array will be empty.
141 
142             InAppPurchases class will own downloads and will delete them as soon as they are finished.
143 
144             NOTE: It is possible to receive this callback for the same purchase multiple times. If that happens,
145             only the newest set of downloads and the newest orderId will be valid, the old ones should be not used anymore!
146         */
productPurchaseFinishedListener147         virtual void productPurchaseFinished (const PurchaseInfo&, bool /*success*/, const String& /*statusDescription*/) {}
148 
149         /** Called when a list of all purchases is restored. This can be used to figure out to
150             which products a user is entitled to.
151 
152             NOTE: It is possible to receive this callback for the same purchase multiple times. If that happens,
153             only the newest set of downloads and the newest orderId will be valid, the old ones should be not used anymore!
154         */
purchasesListRestoredListener155         virtual void purchasesListRestored (const Array<PurchaseInfo>&, bool /*success*/, const String& /*statusDescription*/) {}
156 
157         /** Called whenever a product consumption finishes. */
productConsumedListener158         virtual void productConsumed (const String& /*productId*/, bool /*success*/, const String& /*statusDescription*/) {}
159 
160         /** iOS only: Called when a product download progress gets updated. If the download was interrupted in the last
161             application session, this callback may be called after the application starts.
162 
163             If the download was in progress and the application was closed, the download may happily continue in the
164             background by OS. If you open the app and the download is still in progress, you will receive this callback.
165             If the download finishes in the background before you start the app again, you will receive productDownloadFinished
166             callback instead. The download will only stop when it is explicitly cancelled or when it is finished.
167         */
productDownloadProgressUpdateListener168         virtual void productDownloadProgressUpdate (Download&, float /*progress*/, RelativeTime /*timeRemaining*/) {}
169 
170         /** iOS only: Called when a product download is paused. This may also be called after the application starts, if
171             the download was in a paused state and the application was closed before finishing the download.
172 
173             Only after the download is finished successfully or cancelled you will stop receiving this callback on startup.
174         */
productDownloadPausedListener175         virtual void productDownloadPaused (Download&) {}
176 
177         /** iOS only: Called when a product download finishes (successfully or not). Call Download::getStatus()
178             to check if the downloaded finished successfully.
179 
180             It is your responsibility to move the download content into your app directory and to clean up
181             any files that are no longer needed.
182 
183             After the download is finished, the download object is destroyed and should not be accessed anymore.
184         */
productDownloadFinishedListener185         virtual void productDownloadFinished (Download&, const URL& /*downloadedContentPath*/) {}
186     };
187 
188     //==============================================================================
189     /** Checks whether in-app purchases is supported on current platform. On iOS this always returns true. */
190     bool isInAppPurchasesSupported() const;
191 
192     /** Asynchronously requests information for products with given ids. Upon completion, for each enquired product
193         there is going to be a corresponding Product object.
194         If there is no information available for the given product identifier, it will be ignored.
195      */
196     void getProductsInformation (const StringArray& productIdentifiers);
197 
198     /** Asynchronously requests to buy a product with given id.
199 
200         @param productIdentifier               The product identifier.
201 
202         @param upgradeOrDowngradeFromSubscriptionWithProductIdentifier (Android only) specifies the subscription that will be replaced by
203                                                                        the one being purchased now. Used only when buying a subscription
204                                                                        that is an upgrade or downgrade from another.
205 
206         @param creditForUnusedSubscription     (Android only) controls whether a user should be credited for any unused subscription time on
207                                                the product that is being upgraded or downgraded.
208     */
209     void purchaseProduct (const String& productIdentifier,
210                           const String& upgradeOrDowngradeFromSubscriptionWithProductIdentifier = {},
211                           bool creditForUnusedSubscription = true);
212 
213     /** Asynchronously asks about a list of products that a user has already bought. Upon completion, Listener::purchasesListReceived()
214         callback will be invoked. The user may be prompted to login first.
215 
216         @param includeDownloadInfo      (iOS only) if true, then after restoration is successful, the downloads array passed to
217                                         Listener::purchasesListReceived() callback will contain all the download objects corresponding with
218                                         the purchase. In the opposite case, the downloads array will be empty.
219 
220         @param subscriptionsSharedSecret    (iOS only) required when not including download information and when there are
221                                             auto-renewable subscription set up with this app. Refer to In-App-Purchase settings in the store.
222     */
223     void restoreProductsBoughtList (bool includeDownloadInfo, const juce::String& subscriptionsSharedSecret = {});
224 
225     /** Android only: asynchronously sends a request to mark a purchase with given identifier as consumed.
226         To consume a product, provide product identifier as well as a purchase token that was generated when
227         the product was purchased. The purchase token can also be retrieved by using getProductsInformation().
228         In general if it is available on hand, it is better to use it, because otherwise another async
229         request will be sent to the store, to first retrieve the token.
230 
231         After successful consumption, a product will no longer be returned in getProductsBought() and
232         it will be available for purchase.
233 
234         On iOS consumption happens automatically. If the product was set as consumable, this function is a no-op.
235     */
236     void consumePurchase (const String& productIdentifier, const String& purchaseToken = {});
237 
238     //==============================================================================
239     /** Adds a listener. */
240     void addListener (Listener*);
241 
242     /** Removes a listener. */
243     void removeListener (Listener*);
244 
245     //==============================================================================
246     /** iOS only: Starts downloads of hosted content from the store. */
247     void startDownloads  (const Array<Download*>& downloads);
248 
249     /** iOS only: Pauses downloads of hosted content from the store. */
250     void pauseDownloads  (const Array<Download*>& downloads);
251 
252     /** iOS only: Resumes downloads of hosted content from the store. */
253     void resumeDownloads (const Array<Download*>& downloads);
254 
255     /** iOS only: Cancels downloads of hosted content from the store. */
256     void cancelDownloads (const Array<Download*>& downloads);
257 
258     //==============================================================================
259     // On Android, it is no longer necessary to specify whether the product being purchased is a subscription
260     // and only a single subscription can be upgraded/downgraded. Use the updated purchaseProduct() method
261     // which takes a single String argument.
262     JUCE_DEPRECATED_WITH_BODY (void purchaseProduct (const String& productIdentifier,
263                                                      bool isSubscription,
264                                                      const StringArray& upgradeOrDowngradeFromSubscriptionsWithProductIdentifiers = {},
265                                                      bool creditForUnusedSubscription = true),
266                                {
267 
268                                    ignoreUnused (isSubscription);
269                                    purchaseProduct (productIdentifier,
270                                                     upgradeOrDowngradeFromSubscriptionsWithProductIdentifiers[0],
271                                                     creditForUnusedSubscription);
272                                })
273 
274 private:
275     //==============================================================================
276    #ifndef DOXYGEN
277     InAppPurchases();
278     ~InAppPurchases();
279    #endif
280 
281     //==============================================================================
282     ListenerList<Listener> listeners;
283 
284    #if JUCE_ANDROID
285     friend void juce_inAppPurchaseCompleted (void*);
286    #endif
287 
288    #if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
289     struct Pimpl;
290     friend struct Pimpl;
291 
292     std::unique_ptr<Pimpl> pimpl;
293    #endif
294 };
295 
296 } // namespace juce
297