I have tried a number of methods to validate a storeKit receipt using simulator with storeKit and in testFlight but can't seem to get anything other than status 21002. The code I have last used originated (Implementing Receipt Validation in Swift 3). Its seems many have had this issue, some saying that validation can't be done using the sandbox. Apple documentation indicates it can be done? The issue seems to be with func receiptValidation10() at the bottom of code below. Everything else seems to work fine. Any help is appreciated. Thanks!
import StoreKit
import UIKit
let SUBSCRIPTION_SECRET = "xxx"
#if DEBUG
let certificate = "StoreKitTestCertificate"
#else
let certificate = "AppleIncRootCertificate"
#endif
class purchaseViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
private var models = [SKProduct]()
var pChoice = "Trial"
var pDate = today
var eDate = today
var myProducts: SKProduct?
@IBAction func back(_ sender: Any) { dismiss(animated: true, completion: nil)
}
@IBAction func home(_ sender: Any) { nav(to: "home")
}
@IBAction func basic(_ sender: Any) {
pChoice = "Basic"
eDate=dateAdd(date1: today, inc: 1, metric: "yr")
if SKPaymentQueue.canMakePayments() { // needed? https://www.youtube.com/watch?v=qyKmpr9EjwU
let payment = SKPayment(product: models[whichProduct(title: "com.markv.REX3.basic")])
SKPaymentQueue.default().add(self) // needed? https://www.youtube.com/watch?v=qyKmpr9EjwU
SKPaymentQueue.default().add(payment)
}
}
@IBAction func premium(_ sender: Any) {
pChoice = "Premium"
eDate=dateAdd(date1: today, inc: 1, metric: "yr")
if SKPaymentQueue.canMakePayments() {
let payment = SKPayment(product: models[whichProduct(title: "com.markv.REX3.premium")])
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
}
}
@IBAction func unlimited(_ sender: Any) {
pChoice = "Unlimited"
eDate="None"
if SKPaymentQueue.canMakePayments() {
let payment = SKPayment(product: models[whichProduct(title: "com.markv.REX3.unlimited")])
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
}
}
@IBAction func cancel(_ sender: Any) {
UIApplication.shared.open(URL(string: "Https://apps.apple.com/account/subscriptions")!)
}
func fetchProduct(upgrade: String) {
let request = SKProductsRequest(productIdentifiers: [upgrade])
request.delegate = self
request.start()
}
func whichProduct(title: String) -> Int {
for i in 0 ..< models.count {
let product = models[i]
if title == product.productIdentifier {
return i
}
}
print("No product match")
return models.count
}
enum Product: String, CaseIterable {
case unlimited = "com.markv.ddd.unlimited"
case basic = "com.markv.ddd.basic"
case premium = "com.markv.ddd.premium"
}
private func fetchProducts() {
let request = SKProductsRequest(productIdentifiers: Set(Product.allCases.compactMap({ $0.rawValue })))
request.delegate = self
request.start()
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
DispatchQueue.main.async {
print("Count:\(response.products.count)")
self.models = response.products
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchasing:
print("purchasing")
// no op
break
case .purchased, .restored:
SKPaymentQueue.default().finishTransaction(transaction)
SKPaymentQueue.default().remove(self) // needed? https://www.youtube.com/watch?v=qyKmpr9EjwU
print("Purchase or Restore, version=",pChoice)
print("Method1:")
receiptValidation10()
break
case .failed, .deferred:
SKPaymentQueue.default().finishTransaction(transaction)
SKPaymentQueue.default().remove(self)
print("no payment")
//dismiss(animated: true)
break
default:
SKPaymentQueue.default().finishTransaction(transaction)
SKPaymentQueue.default().remove(self)
print("no payment")
//dismiss(animated: true)
break
}
}
}
func receiptValidation10() {
// method by Yasin Aktimur, updated by Not Batman, says working for swift 4, 11/17/2017, updated 1/17/18 https://stackoverflow.com/questions/39711350/implementing-receipt-validation-in-swift-3
//
#if DEBUG
let urlString = "https://sandbox.itunes.apple.com/verifyReceipt"
#else
let urlString = "https://buy.itunes.apple.com/verifyReceipt"
#endif
let receiptPath = Bundle.main.appStoreReceiptURL?.path
if FileManager.default.fileExists(atPath: receiptPath!){
var receiptData:NSData?
do{
receiptData = try NSData(contentsOf: Bundle.main.appStoreReceiptURL!, options: NSData.ReadingOptions.alwaysMapped)
}
catch{
print("ERROR: " + error.localizedDescription)
}
let base64encodedReceipt = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithCarriageReturn)
let requestDictionary = ["receipt-data":base64encodedReceipt!,"password":SUBSCRIPTION_SECRET]
guard JSONSerialization.isValidJSONObject(requestDictionary) else { print("requestDictionary is not valid JSON"); return }
do {
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
let validationURLString = urlString // this works but as noted above it's best to use your own trusted server
guard let validationURL = URL(string: validationURLString) else { print("the validation url could not be created, unlikely error"); return }
let session = URLSession(configuration: URLSessionConfiguration.default)
var request = URLRequest(url: validationURL)
request.httpMethod = "POST"
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
let queue = DispatchQueue(label: "itunesConnect")
queue.async {
let task = session.uploadTask(with: request, from: requestData) { (data, response, error) in
if let data = data , error == nil {
do {
let appReceiptJSON = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary
print("success. here is the json representation of the app receipt: \(String(describing: appReceiptJSON))")
//self.getExpireInfo(appReceiptJSON!)
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
} else {
print("the upload task returned an error: \(error ?? "couldn't upload" as! Error)")
}
}
task.resume()
}
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
}
}