Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing backoffice validation support #11

Closed
fbartolom opened this issue Mar 15, 2016 · 8 comments
Closed

Missing backoffice validation support #11

fbartolom opened this issue Mar 15, 2016 · 8 comments

Comments

@fbartolom
Copy link

Albeit a very appreciated attempt at porting storekit functionaliies to Swift, the solution misses the possibility of validating the purchase remotely in cases when a certain oepration on the backoffice is subjected to the correct processing of the purchase and to prevent fraud. This functionality is missing from all solutions I found using appStoreReceiptUrl while is present in the original MKStoreKit using the deprecated old construct. I know by myself the issue is a very tough one having attempt myself to implement it for a month three years ago, but I am baffled no one suceeded since then.

@bizz84
Copy link
Owner

bizz84 commented Mar 28, 2016

I am not very familiar myself with receipt validation and I won't be able to help on this one unfortunately. @phimage may have a bit more insight as he implemented the feature in this project.

@phimage
Copy link
Contributor

phimage commented Mar 29, 2016

Sorry, I don't know what @fbartolom talk about (need better explanation...)

all information about receipt validation
https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Introduction.html#//apple_ref/doc/uid/TP40010573-CH105-SW1

@fbartolom
Copy link
Author

In fact I know that post you are referring to. But as you may also see, it just speaks of validating the purchase locally or on the AppStore, yet always from the app. In my case I need to validate the certificate from the backoffice in order to update a DB field in the case the purchase is correct. Unfortunately I found no solution for sending the receipt created by the appStoreReceiptURL to the backoffice in order for it to submit it to the Appstore for its due confirmation.

@ShawnAukstak
Copy link

ShawnAukstak commented May 14, 2016

@fbartolom I was able to accomplish this by doing something similar to the below. Essentially once the SwiftyStoreKit.purchaseProduct completes with a 'Success' result, the receipt data should be stored in NSBundle.mainBundle().appStoreReceiptURL. You can then send this to along to your server for validation.

  private func processPaymentRequest() {
    SwiftyStoreKit.purchaseProduct(skProduct.productIdentifier) { result in
      switch result {
      case .Success(let productId):
        print("Purchase Success: \(productId)")
        self.verifyInAppPurchase() { result in
          //TODO - verify completion handler.
        }
      case .Error(let error):
        //TODO - Handle error
      }
    }
  }

  private func verifyInAppPurchase(completionHandler: ([String:String]?) -> Void) {
    if let dataString = InAppReceipt.base64EncodedString {
       //TODO - 
       //This is where you put your network request to server, sending the dataString receipt data.
    }
  }

  internal class InAppReceipt {
    static var URL: NSURL? {
      return NSBundle.mainBundle().appStoreReceiptURL
    }

    static var data: NSData? {
      if let receiptDataURL = URL, data = NSData(contentsOfURL: receiptDataURL) {
        return data
      }
      return nil
    }

    // The base64 encoded receipt data.
    static var base64EncodedString: String? {
      return data?.base64EncodedStringWithOptions([])
    }
  }

@OleksiiKolosovskyi
Copy link

What about situation, when backend not responding? We need call paymentQueue.finishTransaction manually, when backend respond succeed. How do this with SwiftyStoreKit?

@paulw11
Copy link

paulw11 commented Sep 9, 2016

Further to @AlekseyKolosovskiy comment, SwifyStoreKit's approach to purchasing doesn't comply with best practice; it calls finishTransaction before advising the app of the purchase via the completion closure. finishTransaction should not be called until the app has persisted the purchase. It should be the responsibility of the caller to make a call to finishTransaction, perhaps via a function on SwiftyStoreKit rather than SwiftyStoreKit doing it before the callback

@skymook
Copy link

skymook commented Oct 5, 2016

Swift 2.2
I feel my problem relates to what @paulw11 says. It seems the call back is fired almost right away.

I'm having this problem with an auto renewing subscription. When a purchase is a success, it calls verify receipt, and at this point I can check with a remote server who checks the receipt with Apple's servers.

First I call purchaseProduct. On success I call verifyReceipt, but the call back that calls verifyReceipt is fired before the purchase is complete, and then doesn't fire sometimes when the user completes the purchase with Apple.

I have found it intermittent. Sometimes firing a second time on purchase completion, and sometimes not.

// purchase button
@IBAction func purchaseButton(sender: AnyObject) {
    SwiftyStoreKit.purchaseProduct(api.productID) { result in

        switch result {
        case .Success(let productId):
            // verify purchase in app first
            self.verifyPurchase()
        case .Error(let error):
            print("Purchase Failed: \(error)")
            switch error {
            case .Failed(let error):
                if error.domain == SKErrorDomain {
                    self.alertMessageWithTitle("Purchase failed", message: "Please check your Internet connection or try again later")
                } else {
                    self.alertMessageWithTitle("Purchase failed", message: "Unknown error. Please contact support")
                }
            case .InvalidProductId(let productId):
                self.alertMessageWithTitle("Purchase failed", message: "\(productId) is not a valid product identifier")
            case .NoProductIdentifier:
                self.alertMessageWithTitle("Purchase failed", message: "Product not found")
            case .PaymentNotAllowed:
                self.alertMessageWithTitle("Payments not enabled", message: "Please enable payments to make this purchase")
            }
        }
    }
}

func verifyPurchase() {
    SwiftyStoreKit.verifyReceipt(password: "<secret key>") { result in
        switch result {
        case .Success(let receipt):
            // verify the subscription in app first
            let purchaseResult = SwiftyStoreKit.verifySubscription(
                productId: api.productID,
                inReceipt: receipt,
                validUntil: NSDate()
            )

            switch purchaseResult {
            case .Purchased(let expiresDate):
                // this fires when user completes (sometimes)
                // product is valid, so verify with server
                // call to API here where I send the receipt
            case .Expired(let expiresDate):
                // this fires almost immediately before user completes purchase (old receipt is in place)
                print("Purchased - Product is expired since \(expiresDate)")
            case .NotPurchased:
                // handle this
            }

        // error
        case .Error(let error):
            // handle error
        }
    }
}

@bizz84
Copy link
Owner

bizz84 commented Feb 21, 2017

Since this issue was originally opened I have added non-atomic transactions to SwiftyStoreKit, and local receipt verification is being tracked here: #101.

I'm going to close this issue for now. I would advise everyone to update to the latest version, and open new issues if you experience any problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants