And of course the second after I posted that, I decided userInterface
should be a weak var.

On Sun, Jan 8, 2017 at 8:29 AM, Charles Jenkins <cejw...@gmail.com> wrote:

> As a programmer, aren’t you suspicious when you write something that works
> flawlessly the first time? I worry it just means there’s a disastrous time
> bomb.
>
> In Apple’s documentation about in-app purchases, there’s a lot of talk
> about verifying receipts, but when I look at articles and sample code, it
> seems payment transactions are all anyone really cares about. And if that’s
> so, it seems there are two parts to an in-app store: (1) a “clerk”: code
> with no user interface that runs practically all the time to deal with
> payment transactions; and (2) a flashy “salesman” with a UI to display
> products, make the pitch, and offer buttons the user can click to make the
> clerk spring into action.
>
> With that in mind, I decided to write a single-file, general-purpose clerk
> that I could use in any app. It seems to work beautifully, but one thing
> surprised me: when I deleted my app, reloaded, and restored purchases, the
> payment queue replayed instantly, without asking me to sign in. It went so
> smoothly that I’m suspicious.
>
> So I’m going to share the clerk code with you here and ask for critiques,
> if you have time. Thanks!
>
> //
> //  InAppStore.swift
> //
> //  Created by Charles Jenkins on 12/25/16.
> //  Offered into the public domain.
> //
>
> import StoreKit
>
> // The store user interface implements this protocol
> // in order to be notified of updates as the payment
> // queue is processed. It should plug itself into
> // InAppStore.instance.userInterface when it appears
> // and clear the same when it disappears; and should
> // react to update() callbacks by displaying results
> // reported by InAppStore.instance.latestTransaction.
>
> protocol InAppStoreUserInterfaceProvider {
>   func update()
> }
>
> // The app delegate should implement this protocol
> // and be able to react to activate() callbacks
> // regardless of whether any user interface is on
> // display.
>
> protocol InAppStoreProductActivator {
>   func activate( transaction: SKPaymentTransaction )
> }
>
> // Store operations management class
>
> final class InAppStore :
>   NSObject,
>   SKPaymentTransactionObserver
> {
>
>   // MARK: - Public Properties
>
>   static let instance = InAppStore()
>
>   // This object's lifetime is far greater than the
>   // store's UI, so we need to handle all queued
>   // responses without depending on the store's UI.
>   // The userInterface variable allows the store UI
>   // to temporarily plug in and sign up for update
>   // callbacks.
>
>   var userInterface: InAppStoreUserInterfaceProvider?
>
>   // When the store UI appears or receives update()
>   // callbacks, it can check the latestTransaction
>   // variable to determine which elements or messages
>   // to display.
>
>   var latestTransaction: SKPaymentTransaction? {
>     get {
>       forgetExpiredDeferral()
>       return _latestTransaction
>     }
>     set {
>       _latestTransaction = newValue
>     }
>   }
>
>   // Ask if user can make a purchase before calling makePurchase()
>
>   var canMakePurchase: Bool {
>     return SKPaymentQueue.canMakePayments()
>   }
>
>   // MARK: - Non-Public Properties
>
>   private var _latestTransaction: SKPaymentTransaction?
>
>   private var productActivator: InAppStoreProductActivator?
>
>   private var queue : SKPaymentQueue {
>     return SKPaymentQueue.default()
>   }
>
>   // MARK: - Public Methods
>
>   // Call this function as soon as possible after
>   // launching the app, so waiting transactions
>   // can be dealt with immediately.
>   //
>   // NOTE: I do this in the app delegate's
>   // application( _: didFinishLaunchingWithOptions ),
>   // but first I check user defaults: if all products
>   // have already been purchased, we'll never show
>   // the store and we don't need to startObserving()
>
>   func startObserving( productActivator: InAppStoreProductActivator )
>   {
>     NSLog( "Adding payment queue observer" )
>     self.productActivator = productActivator
>     queue.add( self )
>   }
>
>   func stopObserving()
>   {
>     NSLog( "Removing payment queue observer" )
>     queue.remove( self )
>     self.productActivator = nil
>   }
>
>   static public func requestActiveProducts(
>     productIds: [String],
>     observer: SKProductsRequestDelegate
>   ) {
>     let req = SKProductsRequest(
>       productIdentifiers: Set<String>( productIds )
>     )
>     req.delegate = observer
>     req.start()
>   }
>
>   func restorePurchases()
>   {
>     // New successful transactions will be sent to the
>     // payment queue to mimic all previously completed
>     // successful transactions, which should trigger
>     // the proper product activations
>     NSLog( "Restore Purchases - Requesting completed transactions" )
>     self.queue.restoreCompletedTransactions()
>   }
>
>   func makePurchase( paymentRequest: SKPayment )
>   {
>     queue.add( paymentRequest )
>   }
>
>   // MARK: - Non-Public Methods
>
>   private func forgetExpiredDeferral()
>   {
>     if
>       let tran = _latestTransaction,
>       let date = tran.transactionDate,
>       tran.transactionState == .deferred
>     {
>       let timePassed = -( date.timeIntervalSinceNow )
>       let oneDayInSeconds = 60 * 60 * 24
>       if timePassed > TimeInterval( oneDayInSeconds ) {
>         _latestTransaction = nil
>       }
>     }
>   }
>
>   func paymentQueue(
>     _ queue: SKPaymentQueue,
>     updatedTransactions transactions: [SKPaymentTransaction]
>   ) {
>     for tran in transactions {
>
>       latestTransaction = tran
>
>       let state = tran.transactionState
>
>       if state == .purchased {
>         NSLog( "Purchase succeeded: Activating" )
>         productActivator?.activate( transaction: tran )
>       }
>
>       if state == .restored {
>         NSLog( "Purchase restored: Activating" )
>         productActivator?.activate( transaction: tran )
>       }
>
>       if state != .purchasing {
>         queue.finishTransaction( tran )
>       }
>
>       if let userInterface = userInterface {
>         userInterface.update()
>       }
>
>     }
>   }
>
>   deinit {
>     stopObserving()
>   }
>
> }
>
>
> --
>
> Charles
>



-- 

Charles
_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to