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 struct SKDelegateAndPaymentObserver
30 {
SKDelegateAndPaymentObserverjuce::SKDelegateAndPaymentObserver31     SKDelegateAndPaymentObserver()  : delegate ([getClass().createInstance() init])
32     {
33         Class::setThis (delegate.get(), this);
34     }
35 
~SKDelegateAndPaymentObserverjuce::SKDelegateAndPaymentObserver36     virtual ~SKDelegateAndPaymentObserver() {}
37 
38     virtual void didReceiveResponse (SKProductsRequest*, SKProductsResponse*) = 0;
39     virtual void requestDidFinish (SKRequest*) = 0;
40     virtual void requestDidFailWithError (SKRequest*, NSError*) = 0;
41     virtual void updatedTransactions (SKPaymentQueue*, NSArray<SKPaymentTransaction*>*) = 0;
42     virtual void restoreCompletedTransactionsFailedWithError (SKPaymentQueue*, NSError*) = 0;
43     virtual void restoreCompletedTransactionsFinished (SKPaymentQueue*) = 0;
44     virtual void updatedDownloads (SKPaymentQueue*, NSArray<SKDownload*>*) = 0;
45 
46 protected:
47     std::unique_ptr<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>, NSObjectDeleter> delegate;
48 
49 private:
50     struct Class   : public ObjCClass<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>>
51     {
52         //==============================================================================
Classjuce::SKDelegateAndPaymentObserver::Class53         Class()  : ObjCClass<NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>> ("SKDelegateAndPaymentObserverBase_")
54         {
55             addIvar<SKDelegateAndPaymentObserver*> ("self");
56 
57             addMethod (@selector (productsRequest:didReceiveResponse:),                       didReceiveResponse,                          "v@:@@");
58             addMethod (@selector (requestDidFinish:),                                         requestDidFinish,                            "v@:@");
59             addMethod (@selector (request:didFailWithError:),                                 requestDidFailWithError,                     "v@:@@");
60             addMethod (@selector (paymentQueue:updatedTransactions:),                         updatedTransactions,                         "v@:@@");
61             addMethod (@selector (paymentQueue:restoreCompletedTransactionsFailedWithError:), restoreCompletedTransactionsFailedWithError, "v@:@@");
62             addMethod (@selector (paymentQueueRestoreCompletedTransactionsFinished:),         restoreCompletedTransactionsFinished,        "v@:@");
63             addMethod (@selector (paymentQueue:updatedDownloads:),                            updatedDownloads,                            "v@:@@");
64 
65             registerClass();
66         }
67 
68         //==============================================================================
getThisjuce::SKDelegateAndPaymentObserver::Class69         static SKDelegateAndPaymentObserver& getThis (id self)           { return *getIvar<SKDelegateAndPaymentObserver*> (self, "self"); }
setThisjuce::SKDelegateAndPaymentObserver::Class70         static void setThis (id self, SKDelegateAndPaymentObserver* s)   { object_setInstanceVariable (self, "self", s); }
71 
72         //==============================================================================
didReceiveResponsejuce::SKDelegateAndPaymentObserver::Class73         static void didReceiveResponse (id self, SEL, SKProductsRequest* request, SKProductsResponse* response)      { getThis (self).didReceiveResponse (request, response); }
requestDidFinishjuce::SKDelegateAndPaymentObserver::Class74         static void requestDidFinish (id self, SEL, SKRequest* request)                                              { getThis (self).requestDidFinish (request); }
requestDidFailWithErrorjuce::SKDelegateAndPaymentObserver::Class75         static void requestDidFailWithError (id self, SEL, SKRequest* request, NSError* err)                         { getThis (self).requestDidFailWithError (request, err); }
updatedTransactionsjuce::SKDelegateAndPaymentObserver::Class76         static void updatedTransactions (id self, SEL, SKPaymentQueue* queue, NSArray<SKPaymentTransaction*>* trans) { getThis (self).updatedTransactions (queue, trans); }
restoreCompletedTransactionsFailedWithErrorjuce::SKDelegateAndPaymentObserver::Class77         static void restoreCompletedTransactionsFailedWithError (id self, SEL, SKPaymentQueue* q, NSError* err)      { getThis (self).restoreCompletedTransactionsFailedWithError (q, err); }
restoreCompletedTransactionsFinishedjuce::SKDelegateAndPaymentObserver::Class78         static void restoreCompletedTransactionsFinished (id self, SEL, SKPaymentQueue* queue)                       { getThis (self).restoreCompletedTransactionsFinished (queue); }
updatedDownloadsjuce::SKDelegateAndPaymentObserver::Class79         static void updatedDownloads (id self, SEL, SKPaymentQueue* queue, NSArray<SKDownload*>* downloads)          { getThis (self).updatedDownloads (queue, downloads); }
80     };
81 
82     //==============================================================================
getClassjuce::SKDelegateAndPaymentObserver83     static Class& getClass()
84     {
85         static Class c;
86         return c;
87     }
88 };
89 
90 //==============================================================================
91 struct InAppPurchases::Pimpl   : public SKDelegateAndPaymentObserver
92 {
93     /** AppStore implementation of hosted content download. */
94     struct DownloadImpl  : public Download
95     {
DownloadImpljuce::InAppPurchases::Pimpl::DownloadImpl96         DownloadImpl (SKDownload* downloadToUse)
97             : download (downloadToUse)
98         {
99             [download retain];
100         }
101 
~DownloadImpljuce::InAppPurchases::Pimpl::DownloadImpl102         ~DownloadImpl() override
103         {
104             [download release];
105         }
106 
getProductIdjuce::InAppPurchases::Pimpl::DownloadImpl107         String getProductId()      const override  { return nsStringToJuce (download.contentIdentifier); }
getContentVersionjuce::InAppPurchases::Pimpl::DownloadImpl108         String getContentVersion() const override  { return nsStringToJuce (download.contentVersion); }
109 
110       #if JUCE_IOS
getContentLengthjuce::InAppPurchases::Pimpl::DownloadImpl111         int64 getContentLength()   const override  { return download.contentLength; }
getStatusjuce::InAppPurchases::Pimpl::DownloadImpl112         Status getStatus()         const override  { return SKDownloadStateToDownloadStatus (download.downloadState); }
113       #else
getContentLengthjuce::InAppPurchases::Pimpl::DownloadImpl114         int64 getContentLength()   const override  { return download.expectedContentLength; }
getStatusjuce::InAppPurchases::Pimpl::DownloadImpl115         Status getStatus()         const override  { return SKDownloadStateToDownloadStatus (download.state); }
116       #endif
117 
118         SKDownload* download;
119     };
120 
121     /** Represents a pending request initialised with [SKProductRequest start]. */
122     struct PendingProductInfoRequest
123     {
124         enum class Type
125         {
126             query = 0,
127             purchase
128         };
129 
130         Type type;
131         std::unique_ptr<SKProductsRequest, NSObjectDeleter> request;
132     };
133 
134     /** Represents a pending request started from [SKReceiptRefreshRequest start]. */
135     struct PendingReceiptRefreshRequest
136     {
137         String subscriptionsSharedSecret;
138         std::unique_ptr<SKReceiptRefreshRequest, NSObjectDeleter> request;
139     };
140 
141     /** Represents a transaction with pending downloads. Only after all downloads
142         are finished, the transaction is marked as finished. */
143     struct PendingDownloadsTransaction
144     {
PendingDownloadsTransactionjuce::InAppPurchases::Pimpl::PendingDownloadsTransaction145         PendingDownloadsTransaction (SKPaymentTransaction* t)  : transaction (t)
146         {
147             addDownloadsFromSKTransaction (transaction);
148         }
149 
addDownloadsFromSKTransactionjuce::InAppPurchases::Pimpl::PendingDownloadsTransaction150         void addDownloadsFromSKTransaction (SKPaymentTransaction* transactionToUse)
151         {
152             for (SKDownload* download in transactionToUse.downloads)
153                 downloads.add (new DownloadImpl (download));
154         }
155 
canBeMarkedAsFinishedjuce::InAppPurchases::Pimpl::PendingDownloadsTransaction156         bool canBeMarkedAsFinished() const
157         {
158             for (SKDownload* d in transaction.downloads)
159             {
160               #if JUCE_IOS
161                 SKDownloadState state = d.downloadState;
162               #else
163                 SKDownloadState state = d.state;
164               #endif
165                 if (state != SKDownloadStateFinished
166                      && state != SKDownloadStateFailed
167                      && state != SKDownloadStateCancelled)
168                 {
169                     return false;
170                 }
171             }
172 
173             return true;
174         }
175 
176         OwnedArray<DownloadImpl> downloads;
177         SKPaymentTransaction* const transaction;
178     };
179 
180     //==============================================================================
Pimpljuce::InAppPurchases::Pimpl181     Pimpl (InAppPurchases& p) : owner (p)  { [[SKPaymentQueue defaultQueue] addTransactionObserver:    delegate.get()]; }
~Pimpljuce::InAppPurchases::Pimpl182     ~Pimpl() noexcept override             { [[SKPaymentQueue defaultQueue] removeTransactionObserver: delegate.get()]; }
183 
184     //==============================================================================
isInAppPurchasesSupportedjuce::InAppPurchases::Pimpl185     bool isInAppPurchasesSupported() const     { return true; }
186 
getProductsInformationjuce::InAppPurchases::Pimpl187     void getProductsInformation (const StringArray& productIdentifiers)
188     {
189         auto productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithArray: createNSArrayFromStringArray (productIdentifiers)]];
190 
191         pendingProductInfoRequests.add (new PendingProductInfoRequest { PendingProductInfoRequest::Type::query,
192                                                                         std::unique_ptr<SKProductsRequest, NSObjectDeleter> (productsRequest) });
193 
194         productsRequest.delegate = delegate.get();
195         [productsRequest start];
196     }
197 
purchaseProductjuce::InAppPurchases::Pimpl198     void purchaseProduct (const String& productIdentifier, const String&, bool)
199     {
200         if (! [SKPaymentQueue canMakePayments])
201         {
202             owner.listeners.call ([&] (Listener& l) { l.productPurchaseFinished ({}, false, NEEDS_TRANS ("Payments not allowed")); });
203             return;
204         }
205 
206         auto productIdentifiers = [NSArray arrayWithObject: juceStringToNS (productIdentifier)];
207         auto productsRequest    = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIdentifiers]];
208 
209         pendingProductInfoRequests.add (new PendingProductInfoRequest { PendingProductInfoRequest::Type::purchase,
210                                                                         std::unique_ptr<SKProductsRequest, NSObjectDeleter> (productsRequest) });
211 
212         productsRequest.delegate = delegate.get();
213         [productsRequest start];
214     }
215 
restoreProductsBoughtListjuce::InAppPurchases::Pimpl216     void restoreProductsBoughtList (bool includeDownloadInfo, const String& subscriptionsSharedSecret)
217     {
218         if (includeDownloadInfo)
219         {
220             [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
221         }
222         else
223         {
224             auto receiptRequest = [[SKReceiptRefreshRequest alloc] init];
225 
226             pendingReceiptRefreshRequests.add (new PendingReceiptRefreshRequest { subscriptionsSharedSecret,
227                                                                                   std::unique_ptr<SKReceiptRefreshRequest, NSObjectDeleter> ([receiptRequest retain]) });
228             receiptRequest.delegate = delegate.get();
229             [receiptRequest start];
230         }
231     }
232 
consumePurchasejuce::InAppPurchases::Pimpl233     void consumePurchase (const String&, const String&) {}
234 
235     //==============================================================================
startDownloadsjuce::InAppPurchases::Pimpl236     void startDownloads (const Array<Download*>& downloads)
237     {
238         [[SKPaymentQueue defaultQueue] startDownloads: downloadsToSKDownloads (removeInvalidDownloads (downloads))];
239     }
240 
pauseDownloadsjuce::InAppPurchases::Pimpl241     void pauseDownloads (const Array<Download*>& downloads)
242     {
243         [[SKPaymentQueue defaultQueue] pauseDownloads: downloadsToSKDownloads (removeInvalidDownloads (downloads))];
244     }
245 
resumeDownloadsjuce::InAppPurchases::Pimpl246     void resumeDownloads (const Array<Download*>& downloads)
247     {
248         [[SKPaymentQueue defaultQueue] resumeDownloads: downloadsToSKDownloads (removeInvalidDownloads (downloads))];
249     }
250 
cancelDownloadsjuce::InAppPurchases::Pimpl251     void cancelDownloads (const Array<Download*>& downloads)
252     {
253         [[SKPaymentQueue defaultQueue] cancelDownloads: downloadsToSKDownloads (removeInvalidDownloads (downloads))];
254     }
255 
256     //==============================================================================
didReceiveResponsejuce::InAppPurchases::Pimpl257     void didReceiveResponse (SKProductsRequest* request, SKProductsResponse* response) override
258     {
259         for (auto i = 0; i < pendingProductInfoRequests.size(); ++i)
260         {
261             auto& pendingRequest = *pendingProductInfoRequests[i];
262 
263             if (pendingRequest.request.get() == request)
264             {
265                 if      (pendingRequest.type == PendingProductInfoRequest::Type::query)    notifyProductsInfoReceived (response.products);
266                 else if (pendingRequest.type == PendingProductInfoRequest::Type::purchase) startPurchase (response.products);
267                 else break;
268 
269                 pendingProductInfoRequests.remove (i);
270                 return;
271             }
272         }
273 
274         // Unknown request received!
275         jassertfalse;
276     }
277 
requestDidFinishjuce::InAppPurchases::Pimpl278     void requestDidFinish (SKRequest* request) override
279     {
280         if (auto receiptRefreshRequest = getAs<SKReceiptRefreshRequest> (request))
281         {
282             for (auto i = 0; i < pendingReceiptRefreshRequests.size(); ++i)
283             {
284                 auto& pendingRequest = *pendingReceiptRefreshRequests[i];
285 
286                 if (pendingRequest.request.get() == receiptRefreshRequest)
287                 {
288                     processReceiptRefreshResponseWithSubscriptionsSharedSecret (pendingRequest.subscriptionsSharedSecret);
289                     pendingReceiptRefreshRequests.remove (i);
290                     return;
291                 }
292             }
293         }
294     }
295 
requestDidFailWithErrorjuce::InAppPurchases::Pimpl296     void requestDidFailWithError (SKRequest* request, NSError* error) override
297     {
298         if (auto receiptRefreshRequest = getAs<SKReceiptRefreshRequest> (request))
299         {
300             for (auto i = 0; i < pendingReceiptRefreshRequests.size(); ++i)
301             {
302                 auto& pendingRequest = *pendingReceiptRefreshRequests[i];
303 
304                 if (pendingRequest.request.get() == receiptRefreshRequest)
305                 {
306                     auto errorDetails = error != nil ? (", " + nsStringToJuce ([error localizedDescription])) : String();
307                     owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored ({}, false, NEEDS_TRANS ("Receipt fetch failed") + errorDetails); });
308                     pendingReceiptRefreshRequests.remove (i);
309                     return;
310                 }
311             }
312         }
313     }
314 
updatedTransactionsjuce::InAppPurchases::Pimpl315     void updatedTransactions (SKPaymentQueue*, NSArray<SKPaymentTransaction*>* transactions) override
316     {
317         for (SKPaymentTransaction* transaction in transactions)
318         {
319             switch (transaction.transactionState)
320             {
321                 case SKPaymentTransactionStatePurchasing: break;
322                 case SKPaymentTransactionStateDeferred:   break;
323                 case SKPaymentTransactionStateFailed:     processTransactionFinish (transaction, false); break;
324                 case SKPaymentTransactionStatePurchased:  processTransactionFinish (transaction, true);  break;
325                 case SKPaymentTransactionStateRestored:   processTransactionFinish (transaction, true);  break;
326                 default:                                  jassertfalse; break;  // Unexpected transaction state
327             }
328         }
329     }
330 
restoreCompletedTransactionsFailedWithErrorjuce::InAppPurchases::Pimpl331     void restoreCompletedTransactionsFailedWithError (SKPaymentQueue*, NSError* error) override
332     {
333         owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored ({}, false, nsStringToJuce (error.localizedDescription)); });
334     }
335 
restoreCompletedTransactionsFinishedjuce::InAppPurchases::Pimpl336     void restoreCompletedTransactionsFinished (SKPaymentQueue*) override
337     {
338         owner.listeners.call ([this] (Listener& l) { l.purchasesListRestored (restoredPurchases, true, NEEDS_TRANS ("Success")); });
339         restoredPurchases.clear();
340     }
341 
updatedDownloadsjuce::InAppPurchases::Pimpl342     void updatedDownloads (SKPaymentQueue*, NSArray<SKDownload*>* downloads) override
343     {
344         for (SKDownload* download in downloads)
345         {
346             if (auto* pendingDownload = getPendingDownloadFor (download))
347             {
348               #if JUCE_IOS
349                 switch (download.downloadState)
350               #else
351                 switch (download.state)
352               #endif
353                 {
354                     case SKDownloadStateWaiting: break;
355                     case SKDownloadStatePaused:  owner.listeners.call ([&] (Listener& l) { l.productDownloadPaused (*pendingDownload); }); break;
356                     case SKDownloadStateActive:  owner.listeners.call ([&] (Listener& l) { l.productDownloadProgressUpdate (*pendingDownload,
357                                                                                                                             download.progress,
358                                                                                                                             RelativeTime (download.timeRemaining)); }); break;
359                     case SKDownloadStateFinished:
360                     case SKDownloadStateFailed:
361                     case SKDownloadStateCancelled: processDownloadFinish (pendingDownload, download); break;
362 
363                     default:  jassertfalse; break;  // Unexpected download state
364                 }
365             }
366         }
367     }
368 
369     //==============================================================================
notifyProductsInfoReceivedjuce::InAppPurchases::Pimpl370     void notifyProductsInfoReceived (NSArray<SKProduct*>* products)
371     {
372         Array<Product> productsToReturn;
373 
374         for (SKProduct* skProduct in products)
375             productsToReturn.add (SKProductToIAPProduct (skProduct));
376 
377         owner.listeners.call ([&] (Listener& l) { l.productsInfoReturned (productsToReturn); });
378     }
379 
startPurchasejuce::InAppPurchases::Pimpl380     void startPurchase (NSArray<SKProduct*>* products)
381     {
382         if ([products count] > 0)
383         {
384             // Only one product can be bought at once!
385             jassert ([products count] == 1);
386 
387             auto* product = products[0];
388             auto payment = [SKPayment paymentWithProduct: product];
389             [[SKPaymentQueue defaultQueue] addPayment: payment];
390         }
391         else
392         {
393             owner.listeners.call ([] (Listener& l) { l.productPurchaseFinished ({}, false, NEEDS_TRANS ("Your app is not setup for payments")); });
394         }
395     }
396 
397     //==============================================================================
removeInvalidDownloadsjuce::InAppPurchases::Pimpl398     Array<Download*> removeInvalidDownloads (const Array<Download*>& downloadsToUse)
399     {
400         Array<Download*> downloads (downloadsToUse);
401 
402         for (int i = downloads.size(); --i >= 0;)
403         {
404             auto hasPendingDownload = hasDownloadInPendingDownloadsTransaction (*downloads[i]);
405 
406             // Invalid download passed, it does not exist in pending downloads list
407             jassert (hasPendingDownload);
408 
409             if (! hasPendingDownload)
410                 downloads.remove (i);
411         }
412 
413         return downloads;
414     }
415 
hasDownloadInPendingDownloadsTransactionjuce::InAppPurchases::Pimpl416     bool hasDownloadInPendingDownloadsTransaction (const Download& download)
417     {
418         for (auto* pdt : pendingDownloadsTransactions)
419             for (auto* pendingDownload : pdt->downloads)
420                 if (pendingDownload == &download)
421                     return true;
422 
423         return false;
424     }
425 
426     //==============================================================================
processTransactionFinishjuce::InAppPurchases::Pimpl427     void processTransactionFinish (SKPaymentTransaction* transaction, bool success)
428     {
429         auto orderId      = nsStringToJuce (transaction.transactionIdentifier);
430         auto packageName  = nsStringToJuce ([[NSBundle mainBundle] bundleIdentifier]);
431         auto productId    = nsStringToJuce (transaction.payment.productIdentifier);
432         auto purchaseTime = Time (1000 * (int64) transaction.transactionDate.timeIntervalSince1970)
433                               .toString (true, true, true, true);
434 
435         Purchase purchase { orderId, productId, packageName, purchaseTime, {} };
436 
437         Array<Download*> downloads;
438 
439         // If transaction failed or there are no downloads, finish the transaction immediately, otherwise
440         // finish the transaction only after all downloads are finished.
441         if (transaction.transactionState == SKPaymentTransactionStateFailed
442              || transaction.downloads == nil
443              || [transaction.downloads count] == 0)
444         {
445             [[SKPaymentQueue defaultQueue]  finishTransaction: transaction];
446         }
447         else
448         {
449             // On application startup or when the app is resumed we may receive multiple
450             // "purchased" callbacks with the same underlying transaction. Sadly, only
451             // the last set of downloads will be valid.
452             auto* pdt = getPendingDownloadsTransactionForSKTransaction (transaction);
453 
454             if (pdt == nullptr)
455             {
456                 pdt = pendingDownloadsTransactions.add (new PendingDownloadsTransaction (transaction));
457             }
458             else
459             {
460                 pdt->downloads.clear();
461                 pdt->addDownloadsFromSKTransaction (transaction);
462             }
463 
464             for (auto* download : pdt->downloads)
465                 downloads.add (download);
466         }
467 
468         if (transaction.transactionState == SKPaymentTransactionStateRestored)
469             restoredPurchases.add ({ purchase, downloads });
470         else
471             owner.listeners.call ([&] (Listener& l) { l.productPurchaseFinished ({ purchase, downloads }, success,
472                                                                                  SKPaymentTransactionStateToString (transaction.transactionState)); });
473     }
474 
getPendingDownloadsTransactionForSKTransactionjuce::InAppPurchases::Pimpl475     PendingDownloadsTransaction* getPendingDownloadsTransactionForSKTransaction (SKPaymentTransaction* transaction)
476     {
477         for (auto* pdt : pendingDownloadsTransactions)
478             if (pdt->transaction == transaction)
479                 return pdt;
480 
481         return nullptr;
482     }
483 
484     //==============================================================================
getPendingDownloadsTransactionSKDownloadForjuce::InAppPurchases::Pimpl485     PendingDownloadsTransaction* getPendingDownloadsTransactionSKDownloadFor (SKDownload* download)
486     {
487         for (auto* pdt : pendingDownloadsTransactions)
488             for (auto* pendingDownload : pdt->downloads)
489                 if (pendingDownload->download == download)
490                     return pdt;
491 
492         jassertfalse;
493         return nullptr;
494     }
495 
getPendingDownloadForjuce::InAppPurchases::Pimpl496     Download* getPendingDownloadFor (SKDownload* download)
497     {
498         if (auto* pdt = getPendingDownloadsTransactionSKDownloadFor (download))
499             for (auto* pendingDownload : pdt->downloads)
500                 if (pendingDownload->download == download)
501                     return pendingDownload;
502 
503         jassertfalse;
504         return nullptr;
505     }
506 
processDownloadFinishjuce::InAppPurchases::Pimpl507     void processDownloadFinish (Download* pendingDownload, SKDownload* download)
508     {
509         if (auto* pdt = getPendingDownloadsTransactionSKDownloadFor (download))
510         {
511           #if JUCE_IOS
512             SKDownloadState state = download.downloadState;
513           #else
514             SKDownloadState state = download.state;
515           #endif
516 
517             auto contentURL = state == SKDownloadStateFinished
518                                 ? URL (nsStringToJuce (download.contentURL.absoluteString))
519                                 : URL();
520 
521             owner.listeners.call ([&] (Listener& l) { l.productDownloadFinished (*pendingDownload, contentURL); });
522 
523             if (pdt->canBeMarkedAsFinished())
524             {
525                 // All downloads finished, mark transaction as finished too.
526                 [[SKPaymentQueue defaultQueue]  finishTransaction: pdt->transaction];
527 
528                 pendingDownloadsTransactions.removeObject (pdt);
529             }
530         }
531     }
532 
533     //==============================================================================
processReceiptRefreshResponseWithSubscriptionsSharedSecretjuce::InAppPurchases::Pimpl534     void processReceiptRefreshResponseWithSubscriptionsSharedSecret (const String& secret)
535     {
536         auto receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
537 
538         if (auto receiptData = [NSData dataWithContentsOfURL: receiptURL])
539             fetchReceiptDetailsFromAppStore (receiptData, secret);
540         else
541             owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored ({}, false, NEEDS_TRANS ("Receipt fetch failed")); });
542     }
543 
fetchReceiptDetailsFromAppStorejuce::InAppPurchases::Pimpl544     void fetchReceiptDetailsFromAppStore (NSData* receiptData, const String& secret)
545     {
546         auto requestContents = [NSMutableDictionary dictionaryWithCapacity: (NSUInteger) (secret.isNotEmpty() ? 2 : 1)];
547         [requestContents setObject: [receiptData base64EncodedStringWithOptions:0] forKey: nsStringLiteral ("receipt-data")];
548 
549         if (secret.isNotEmpty())
550             [requestContents setObject: juceStringToNS (secret) forKey: nsStringLiteral ("password")];
551 
552         NSError* error;
553         auto requestData = [NSJSONSerialization dataWithJSONObject: requestContents
554                                                            options: 0
555                                                              error: &error];
556         if (requestData == nil)
557         {
558             sendReceiptFetchFail();
559             return;
560         }
561 
562        #if JUCE_IN_APP_PURCHASES_USE_SANDBOX_ENVIRONMENT
563         auto storeURL = "https://sandbox.itunes.apple.com/verifyReceipt";
564        #else
565         auto storeURL = "https://buy.itunes.apple.com/verifyReceipt";
566        #endif
567 
568         // TODO: use juce URL here
569         auto* urlPtr = [NSURL URLWithString: nsStringLiteral (storeURL)];
570         auto storeRequest = [NSMutableURLRequest requestWithURL: urlPtr];
571         [storeRequest setHTTPMethod: nsStringLiteral ("POST")];
572         [storeRequest setHTTPBody: requestData];
573 
574         auto task = [[NSURLSession sharedSession] dataTaskWithRequest: storeRequest
575                                                     completionHandler:
576                                                        ^(NSData* data, NSURLResponse*, NSError* connectionError)
577                                                        {
578                                                            if (connectionError != nil)
579                                                            {
580                                                                sendReceiptFetchFail();
581                                                            }
582                                                            else
583                                                            {
584                                                                NSError* err;
585 
586                                                                if (NSDictionary* receiptDetails = [NSJSONSerialization JSONObjectWithData: data options: 0 error: &err])
587                                                                    processReceiptDetails (receiptDetails);
588                                                                else
589                                                                    sendReceiptFetchFail();
590                                                            }
591                                                        }];
592 
593         [task resume];
594     }
595 
processReceiptDetailsjuce::InAppPurchases::Pimpl596     void processReceiptDetails (NSDictionary* receiptDetails)
597     {
598         if (auto receipt = getAs<NSDictionary> (receiptDetails[nsStringLiteral ("receipt")]))
599         {
600             if (auto bundleId = getAs<NSString> (receipt[nsStringLiteral ("bundle_id")]))
601             {
602                 if (auto inAppPurchases = getAs<NSArray> (receipt[nsStringLiteral ("in_app")]))
603                 {
604                     Array<Listener::PurchaseInfo> purchases;
605 
606                     for (id inAppPurchaseData in inAppPurchases)
607                     {
608                         if (auto* purchaseData = getAs<NSDictionary> (inAppPurchaseData))
609                         {
610                             // Ignore products that were cancelled.
611                             if (purchaseData[nsStringLiteral ("cancellation_date")] != nil)
612                                 continue;
613 
614                             if (auto transactionId = getAs<NSString> (purchaseData[nsStringLiteral ("original_transaction_id")]))
615                             {
616                                 if (auto productId = getAs<NSString> (purchaseData[nsStringLiteral ("product_id")]))
617                                 {
618                                     auto purchaseTime = getPurchaseDateMs (purchaseData[nsStringLiteral ("purchase_date_ms")]);
619 
620                                     if (purchaseTime > 0)
621                                     {
622                                         purchases.add ({ { nsStringToJuce (transactionId),
623                                                            nsStringToJuce (productId),
624                                                            nsStringToJuce (bundleId),
625                                                            Time (purchaseTime).toString (true, true, true, true),
626                                                            {} }, {} });
627                                     }
628                                     else
629                                     {
630                                         return sendReceiptFetchFailAsync();
631                                     }
632                                 }
633                             }
634                         }
635                         else
636                         {
637                             return sendReceiptFetchFailAsync();
638                         }
639                     }
640 
641                     MessageManager::callAsync ([this, purchases] { owner.listeners.call ([&] (Listener& l) { l.purchasesListRestored (purchases, true, NEEDS_TRANS ("Success")); }); });
642                     return;
643                 }
644             }
645         }
646 
647         sendReceiptFetchFailAsync();
648     }
649 
sendReceiptFetchFailjuce::InAppPurchases::Pimpl650     void sendReceiptFetchFail()
651     {
652         owner.listeners.call ([] (Listener& l) { l.purchasesListRestored ({}, false, NEEDS_TRANS ("Receipt fetch failed")); });
653     }
654 
sendReceiptFetchFailAsyncjuce::InAppPurchases::Pimpl655     void sendReceiptFetchFailAsync()
656     {
657         MessageManager::callAsync ([this] { sendReceiptFetchFail(); });
658     }
659 
getPurchaseDateMsjuce::InAppPurchases::Pimpl660     static int64 getPurchaseDateMs (id date)
661     {
662         if (auto dateAsNumber = getAs<NSNumber> (date))
663         {
664             return [dateAsNumber longLongValue];
665         }
666         else if (auto dateAsString = getAs<NSString> (date))
667         {
668             auto formatter = [[NSNumberFormatter alloc] init];
669             [formatter setNumberStyle: NSNumberFormatterDecimalStyle];
670             dateAsNumber = [formatter numberFromString: dateAsString];
671             [formatter release];
672             return [dateAsNumber longLongValue];
673         }
674 
675         return -1;
676     }
677 
678     //==============================================================================
SKProductToIAPProductjuce::InAppPurchases::Pimpl679     static Product SKProductToIAPProduct (SKProduct* skProduct)
680     {
681         NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
682         [numberFormatter setFormatterBehavior: NSNumberFormatterBehavior10_4];
683         [numberFormatter setNumberStyle: NSNumberFormatterCurrencyStyle];
684         [numberFormatter setLocale: skProduct.priceLocale];
685 
686         auto identifier   = nsStringToJuce (skProduct.productIdentifier);
687         auto title        = nsStringToJuce (skProduct.localizedTitle);
688         auto description  = nsStringToJuce (skProduct.localizedDescription);
689         auto priceLocale  = nsStringToJuce ([skProduct.priceLocale objectForKey: NSLocaleLanguageCode]);
690         auto price        = nsStringToJuce ([numberFormatter stringFromNumber: skProduct.price]);
691 
692         [numberFormatter release];
693 
694         return { identifier, title, description, price, priceLocale };
695     }
696 
SKPaymentTransactionStateToStringjuce::InAppPurchases::Pimpl697     static String SKPaymentTransactionStateToString (SKPaymentTransactionState state)
698     {
699         switch (state)
700         {
701             case SKPaymentTransactionStatePurchasing: return NEEDS_TRANS ("Purchasing");
702             case SKPaymentTransactionStatePurchased:  return NEEDS_TRANS ("Success");
703             case SKPaymentTransactionStateFailed:     return NEEDS_TRANS ("Failure");
704             case SKPaymentTransactionStateRestored:   return NEEDS_TRANS ("Restored");
705             case SKPaymentTransactionStateDeferred:   return NEEDS_TRANS ("Deferred");
706             default:                                  jassertfalse; return NEEDS_TRANS ("Unknown status");
707         }
708 
709     }
710 
SKDownloadStateToDownloadStatusjuce::InAppPurchases::Pimpl711     static Download::Status SKDownloadStateToDownloadStatus (SKDownloadState state)
712     {
713         switch (state)
714         {
715             case SKDownloadStateWaiting:    return Download::Status::waiting;
716             case SKDownloadStateActive:     return Download::Status::active;
717             case SKDownloadStatePaused:     return Download::Status::paused;
718             case SKDownloadStateFinished:   return Download::Status::finished;
719             case SKDownloadStateFailed:     return Download::Status::failed;
720             case SKDownloadStateCancelled:  return Download::Status::cancelled;
721             default:                        jassertfalse; return Download::Status::waiting;
722         }
723     }
724 
downloadsToSKDownloadsjuce::InAppPurchases::Pimpl725     static NSArray<SKDownload*>* downloadsToSKDownloads (const Array<Download*>& downloads)
726     {
727         NSMutableArray<SKDownload*>* skDownloads = [NSMutableArray arrayWithCapacity: (NSUInteger) downloads.size()];
728 
729         for (const auto& d : downloads)
730             if (auto impl = dynamic_cast<DownloadImpl*>(d))
731                 [skDownloads addObject: impl->download];
732 
733         return skDownloads;
734     }
735 
736     template <typename ObjCType>
getAsjuce::InAppPurchases::Pimpl737     static ObjCType* getAs (id o)
738     {
739         if (o == nil || ! [o isKindOfClass: [ObjCType class]])
740             return nil;
741 
742         return (ObjCType*) o;
743     }
744 
745     //==============================================================================
746     InAppPurchases& owner;
747 
748     OwnedArray<PendingProductInfoRequest> pendingProductInfoRequests;
749     OwnedArray<PendingReceiptRefreshRequest> pendingReceiptRefreshRequests;
750 
751     OwnedArray<PendingDownloadsTransaction> pendingDownloadsTransactions;
752     Array<Listener::PurchaseInfo> restoredPurchases;
753 };
754 
755 } // namespace juce
756