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