Re: Code Review Requested - In-App Purchases
Re: Code Review Requested - In-App Purchases
- Subject: Re: Code Review Requested - In-App Purchases
- From: Charles Jenkins <email@hidden>
- Date: Sun, 08 Jan 2017 08:35:01 -0500
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 <email@hidden> 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 (email@hidden)
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:
This email sent to email@hidden