I was working on apple subscription and have set two subscription plan on apple store for "monthly" and "yearly" and have set up all the code which is working perfectly on development and adhoc. But after placing build on App Store app crashes.
I have used firebase crashlytic which detected crash I have attached screenshot crash logs.
https://i.stack.imgur.com/e487d.png
Here my code for Subscription:
func getProducts(productIDs: [String]){
//self.showLoader()
print("Start requesting products ...")
//showAlert(text:"Start requesting products ...")
let request = SKProductsRequest(productIdentifiers: Set(productIDs))
request.delegate = self
request.start()
}
func buyProduct(product: SKProduct){
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if response.products.isEmpty {
NotificationAlert().NotificationAlert(titles: "No subscription plan")
}else{
self.myProducts = response.products
for invalidIdentifier in response.invalidProductIdentifiers {
print(invalidIdentifier)
//NotificationAlert().NotificationAlert(titles:"Invalid Identifire")
}
print("Did receive response")
for fetchedProduct in response.products{
//DispatchQueue.main.async {
print("fetchedProduct:",fetchedProduct)
//showAlert(text:"Product Fetched")
//NotificationAmyProductslert().NotificationAlert(titles: "fetchedProduct:\(fetchedProduct)")
DispatchQueue.main.asyncAfter(deadline: .now()+1.0, execute: {
let price = self.getPrice(item: fetchedProduct)
if price == ""{
} else{
self.myProducts.append(fetchedProduct)
if fetchedProduct.productIdentifier == "com.motivatory.monthly"{
DispatchQueue.main.asyncAfter(deadline: .now()+0.3, execute: {
self.oPriceMonthlyLbl.text = price ?? ""
self.monthlyprice = price ?? ""
print(price ?? "")
self.productIDs.insert("com.motivatory.monthly",at: 0)
self.selectedPrice = price ?? ""
})
} else {
DispatchQueue.main.asyncAfter(deadline: .now()+0.3, execute: {
self.oPriceYearlyLbl.text = price ?? ""
self.selectedPrice = price ?? ""
self.yearlyPrice = price ?? ""
print(price ?? "")
self.productIDs.insert("com.motivatory.yearly",at: 1)
})
}
}
})
}
}
}
func getPrice(item: SKProduct) -> String? {
let numberFormatter = NumberFormatter()
let price = item.price
let locale = item.priceLocale
numberFormatter.numberStyle = .currency
numberFormatter.locale = locale
return numberFormatter.string(from: price)
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
print(transaction)
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction{
switch trans.transactionState {
case .purchased:
purchaseTokenStr = trans.transactionIdentifier ?? ""
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
if self.purchased == true {
print("Product Purchased")
print(purchaseTokenStr)
receiptValidation()
}
break;
case .failed:
purchaseTokenStr = trans.transactionIdentifier ?? ""
NotificationAlert().NotificationAlert(titles: "Payment failed")
print("Purchased Failed");
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
SVProgressHUD.dismiss()
break;
case .restored:
print("Already Purchased")
purchaseTokenStr = trans.transactionIdentifier ?? ""
// receiptValidation()
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
break
default:
break
}
}
}
}
//added by me
// 4:
func finishTransaction(_ transaction: SKPaymentTransaction) -> Bool {
let productId = transaction.payment.productIdentifier
//print("Product \(productId) successfully purchased”)
print("Product \(productId) successfully purchased")
return true
}
func restorePurchases() {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
//If an error occurs, the code will\ go to this function
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
SVProgressHUD.dismiss()
NotificationAlert().NotificationAlert(titles: error.localizedDescription)
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
if queue.transactions.count == 0{
NotificationAlert().NotificationAlert(titles: "Please purchase a subscription plan")
SVProgressHUD.dismiss()
} else{
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
if self.purchaseTokenStr == t.transactionIdentifier {
switch prodID {
case "com.motivatory.yearly":
print("Your Yearly Plan has Restored Successfully")
NotificationAlert().NotificationAlert(titles: "Your Yearly Plan has Restored Successfully")
receiptValidation()
SVProgressHUD.dismiss()
break
case "com.motivatory.monthly":
print("Your Montly Plan has Restored Successfully")
NotificationAlert().NotificationAlert(titles: "Your Montly Plan has Restored Successfully")
receiptValidation()
SVProgressHUD.dismiss()
break
default:
if queue.transactions.count == 0 {
self.alert(message: "There are no restorable purchases. Only previously bought non-consumable products and auto-renewable subscriptions can be restored.", title: "Inspiration Daily Quotes")
break
}
SVProgressHUD.dismiss()
}
}
}
}
}
func receiptValidation(){
self.HitReceiptApi(Url: "https://buy.itunes.apple.com/verifyReceipt")
}
func HitReceiptApi(Url:String){
SVProgressHUD.setDefaultMaskType(.custom)
SVProgressHUD.setBackgroundLayerColor(#colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.2472442209))
SVProgressHUD.show(withStatus: "Please Wait")
let receiptFileURL = Bundle.main.appStoreReceiptURL
let receiptData = try? Data(contentsOf: receiptFileURL!)
let recieptString = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
let jsonDict: [String: AnyObject] = ["receipt-data" : recieptString! as AnyObject, "password" : "fad24ce1475b4fdd9662a0ec31ef5a7d" as AnyObject]
print(jsonDict)
UserDefaults.standard.set(recieptString!, forKey: "recieptString")
do {
let requestData = try JSONSerialization.data(withJSONObject: jsonDict, options: JSONSerialization.WritingOptions.prettyPrinted)
let storeURL = URL(string: Url)!
var storeRequest = URLRequest(url: storeURL)
storeRequest.httpMethod = "POST"
storeRequest.httpBody = requestData
let session = URLSession(configuration: URLSessionConfiguration.default)
let task = session.dataTask(with: storeRequest, completionHandler: { [weak self] (data, response, error) in
do {
let jsonResponse = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers)
print("=======>",jsonResponse)
let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary<String, AnyObject>
DispatchQueue.main.sync {
if json["status"] as? Int == 21007{
print("Sandbox User")
self?.HitReceiptApi(Url: "https://sandbox.itunes.apple.com/verifyReceipt")
return
} else {
print("Production")
if self!.purchaseTokenStr == ""{
} else {
self?.purchaseTokenStr = ""
if let receiptInfo2: NSArray = json["pending_renewal_info"] as? NSArray {
let lastReceipt2 = receiptInfo2.lastObject as! NSDictionary
if let original_transaction_id = lastReceipt2["original_transaction_id"] as? String {
self?.purchaseTokenStr = original_transaction_id
}
}
if let receiptInfo2: NSArray = json["latest_receipt_info"] as? NSArray {
let lastReceipt2 = receiptInfo2.firstObject as! NSDictionary
if let purchase_date = lastReceipt2["purchase_date"] as? String {
if let date = Formatter.customDate.date(from:purchase_date){
var startDate1 = String(describing: date)
startDate1.removeLast(6)
self?.startDate = "\(startDate1)"
}
}
}
self?.buySubscription = true
self?.subscriptionBuy()
}
SVProgressHUD.dismiss()
}
UserDefaults.standard.set(true,forKey:"SubscriptionPurchased")
}
} catch let parseError {
print(parseError)
}
})
task.resume()
} catch let parseError {
print(parseError)
}
}
}
Related
I am using auto renewal subscription In-App Purchase in my application. I want to know what happened when i remove my apple account after purchase in iPhone. in app purchase is worked or not ?? I am using sandbox environment.
If not so how to know in-app apple account was removed ??
I have try to verifying the receipt and checked the in-app purchased is expire or not, but after remove apple account i always get receipt response true.
here is my get receipt request code :
#objc func loadreceipt() {
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
if UD.getS(receiptdata).isEmpty == false {
Api.inAppPurchasecall(.receiptVarify(rdata: UD.getS(receiptdata), excludeInclude: true)) { response, error in
if error == nil {
self.checkresponse(response: response as! Map)
}else{
print(error)
}
}
}else{
do {
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
let receiptString = receiptData.base64EncodedString(options: [])
Api.inAppPurchasecall(.receiptVarify(rdata: receiptString, excludeInclude: true)) { response, error in
if error == nil {
self.checkresponse(response: response as! Map)
}else{
print(error)
}
}
}
catch { print("Couldn't read receipt data with error: " + error.localizedDescription) }
}
}else{
requestrefreshreceipt = SKReceiptRefreshRequest()
requestrefreshreceipt?.delegate = self
requestrefreshreceipt?.start()
}
}
receipt response code :
func checkresponse(response : Map) {
if let rdata = response["latest_receipt"] as? String {
UD.setS(receiptdata, rdata)
}
if let latestreceiptinfo = response["latest_receipt_info"] as? [Map] {
if let lastinfo = latestreceiptinfo.first {
if let expiredate = lastinfo["expires_date"] as? String, let edate = Formatter.customDate.date(from: expiredate) {
if let strEdate = gmtToLocal(date: edate) {
self.nextBillingDate = strEdate
}
}
if let expiredate = lastinfo["purchase_date"] as? String, let edate = Formatter.customDate.date(from: expiredate) {
if let strEdate = gmtToLocal(date: edate) {
self.lastBillingDate = strEdate
}
}
if let expiretime = lastinfo["expires_date_ms"] as? String, let doubleexpiretime = Double(expiretime) {
expireTimeStamp = doubleexpiretime/1000
let currenttimestamp = Date().timeIntervalSince1970
if let etimestamp = expireTimeStamp {
if etimestamp > currenttimestamp {
UD.setS(AppConst.SUBSCRIPTION_STATUS, "true")
NotificationCenter.default.post(name: NSNotification.Name(rawValue: kRefreshServices), object:nil)
}else{
UD.setS(AppConst.SUBSCRIPTION_STATUS, "false")
NotificationCenter.default.post(name: NSNotification.Name(rawValue: kRefreshServices), object:nil)
if let expiredate = lastinfo["expires_date"] as? String, let edate = Formatter.customDate.date(from: expiredate) {
if let root = APP_DELEGATE.window?.rootViewController {
if let strEdate = gmtToLocal(date: edate) {
}
}
}
}
}
}
}
}
}
I'm using the message Kit. In a chatDashbaord extension I've boolean condition like if the isnewConversation contain true then call the function insertConversation2 and if isnewConversation contain false then call the function sendMessag.But I've checked using the break points the code is not going to isNewconversation block and I don't know why it's happening.When I'm not using the boolean statement the code working file and append the data into an array please check the code thanks.
MessageViewController:
class ChatDashboard: MessagesViewController {
var userActive: String? = nil
var receiverName:String? = nil
var isnewConversation = false
var recevierName:String? = nil
var otheruserEmail: String? = nil
var conversationId:String? = nil
var messages : [Message] = []{
didSet{
DispatchQueue.main.async {
self.messagesCollectionView.reloadDataAndKeepOffset()
}
}
extension ChatDashboard:
extension ChatDashboard: InputBarAccessoryViewDelegate{
func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
guard !text.replacingOccurrences(of: "", with: "").isEmpty,let selfSender =
self.selfSender,let messageId = createMessageId() else{
return
}
let message = Message(sender: selfSender, messageId: messageId, sentDate: Date(), kind: .text(text))
guard let otheruserEmail = otheruserEmail,let recevierName = recevierName else {return}
if isnewConversation{
NetworkingService.shared.insertConversation2(with:otheruserEmail,name:recevierName, message: message) { (success) in
if success{
print("message Send")
self.isnewConversation = false
}
else{
print("not send")
}
}
}
else{
guard let converId = conversationId else {return}
NetworkingService.shared.sendMessage(converId,recevierName,message) { (success) in
if success{
print("message send in")
}
else{
print("message not send")
}
}
}
}
}
}
SendMessage func code when condtion false:
func sendMessage(_ convesationId:String,_ name:String,_ message:Message,completion:#escaping(Bool) -> Void){
self.sendMessageReciverToSender(convesationId) { (user) in
guard var receiverToSenderNdoe = user else {completion(false); return}
let dformatter = DateFormatter()
dformatter.dateFormat = "dd/MM/yyy HH:mm"
let dateToString = dformatter.string(from: Date())
guard let email = UserDefaults.standard.value(forKey: "useremail") as? String else {
print("Email not found in user default")
completion(false)
return
}
let currentUserEmail = getUserEmail(currentEmail: email)
var messageData = ""
switch message.kind{
case .text(let messageText):
messageData = messageText
default:
break
}
let newMessge: [String:Any] = [
"id":message.messageId,
"type":message.kind.messageKindString,
"content":messageData,
"date": dateToString,
"sender_email":currentUserEmail,
"is_read":false,
"name":name
]
//issues is here
if var conversationData = receiverToSenderNdoe["messages"] as? [[String:Any]]{
conversationData.append(newMessge)
receiverToSenderNdoe["messages"] = conversationData
self.reciverToSenderSave(convesationId, newMessge) { (result) in
if result{
completion(true)
}
else{
completion(false)
}
}
}
}
}
Inside the sendMessage func this two function called
func sendMessageReciverToSender(_ conversationId:String,completion:#escaping([[String:Any]]?) -> Void){
database.child("\(conversationId)/messages").observeSingleEvent(of: .value) { (snapshot) in
if let currentUser = snapshot.value as? [[String:Any]]{
completion(currentUser)
}
else{
print("errro in reciver to sender sendMessageReciverToSender func ")
completion(nil)
}
}
}
func reciverToSenderSave(_ conversationId:String,_ conversation:[[String:Any]],completion:#escaping(Bool)-> Void){
database.child("\(conversationId)/messages").setValue(conversation){(error,ref) in
if error == nil{
completion(true)
}
else{
print("eroor in receiptin \(error?.localizedDescription)")
completion(false)
}
}
}
Your sendMessage has return without calling completion here
guard var receiverToSenderNdoe = user else {return}
Should be
guard var receiverToSenderNdoe = user else { completion(false) ; return }
And here
guard let email = UserDefaults.standard.value(forKey: "useremail") as? String else {
print("Email not found in user default")
return
}
Should be
guard let email = UserDefaults.standard.value(forKey: "useremail") as? String else {
print("Email not found in user default")
completion(false)
return
}
You have to make sure all paths are handled so to have your callback called in success/failure , BTW something like this
if result{
completion(true)
}
else{
completion(false)
}
Could be shortly
completion(result)
Everytime I build an adhoc build and runs it on ios 11 it keeps popping up the message saying "Sign-In Required - Enter the password for ***** [Environment:Sandbox]". Thus, this keep activating like a loop and not going away. tried siging out from iCloud also but didn't work.My inAppPurchase code as bellow.
class InAppPurchase: NSObject,SKProductsRequestDelegate,
SKPaymentTransactionObserver {
///Singleton
private static var inappPurChase:InAppPurchase?;
public static var shared:InAppPurchase {
if inappPurChase == nil{
inappPurChase = InAppPurchase()
}
return inappPurChase!
}
override private init() {
//Singleton complete
}
////////////
var productsList = [SKProduct]()
var productToPurchase = SKProduct()
var productID = ""
let PURCHASE_ID_PREFIX = ""//"com.purelightbeta."
var onInAppPurchaseSuccess: (()->Void )? = nil
var onInAppPurchaseError: (()->Void )? = nil
var onNoProductsIDs: (()->Void )? = nil
var appEnvironment: ((String,Bool)->Void )? = nil
var onReceiptVarificationError: (()->Void )? = nil
var onRestorePurchaseError: ((String)->Void )? = nil
var onRestoreProductsIDs: ((NSMutableArray)->Void )? = nil
public func initPayment (productID: String) {
self.productID = PURCHASE_ID_PREFIX + productID
print("Initializing Purchase Product ID: \(self.productID)")
if SKPaymentQueue.canMakePayments() {
let productID: NSSet = NSSet(objects: self.productID)
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
}else {
print("In App Purchases not enabled")
}
}
func buyProduct() {
let payment = SKPayment(product: productToPurchase)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment as SKPayment)
}
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let myProducts = response.products
print(myProducts)
productsList = []
if myProducts.count == 0 {
print("No products")
if(onNoProductsIDs != nil){
onNoProductsIDs!()
}
}
for product in myProducts {
print("Prod Details :\(product.productIdentifier) \(product.price) \(product.localizedDescription)")
productsList.append(product)
}
for product in productsList {
let productID = product.productIdentifier
if productID == self.productID {
productToPurchase = product
buyProduct()
}
}
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
let productIDs: NSMutableArray = []
for trasacton in queue.transactions {
if trasacton.transactionState == SKPaymentTransactionState.restored
{
let t: SKPaymentTransaction = trasacton as SKPaymentTransaction
let prodID = t.payment.productIdentifier as String
print("prodID:",prodID)
//let appStoreId = prodID.components(separatedBy: "com.purelightbeta.")
productIDs.add(prodID)
SKPaymentQueue .default().finishTransaction(trasacton)
}
}
if onRestoreProductsIDs != nil {
print(productIDs)
onRestoreProductsIDs! (productIDs)
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction: AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
print(trans.error ?? "No in app error")
switch trans.transactionState {
case .purchased:
print("buy ok, AIP unlocked for item :", productToPurchase.productIdentifier)
let productID = productToPurchase.productIdentifier
switch productID {
case self.productID:
print("Item found")
// startValidatingReceipt()
default:
print("Item not found")
}
queue.finishTransaction(trans)
if self.onInAppPurchaseSuccess != nil {
self.onInAppPurchaseSuccess! ()
self.startValidatingReceipt()
}
case .failed:
print("buy error")
if self.onInAppPurchaseError != nil {
self.onInAppPurchaseError! ()
}
queue.finishTransaction(trans)
break
case .restored:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
// break
default:
print("default")
break
}
}
}
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
print("ERROR: \(error.localizedDescription)")
if onRestorePurchaseError != nil {
onRestorePurchaseError! (error.localizedDescription)
}
}
func restorePurchase() {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
func startValidatingReceipt() {
if let isExists = try? self.getReceiptURL()?.checkResourceIsReachable(), isExists == true {
do {
let data = try Data(contentsOf: self.getReceiptURL()!)
// self.startValidatingData(data: data)
self.sendReceiptToLocalServer(data: data)
}catch {
let appReceiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
appReceiptRefreshRequest.delegate = self
appReceiptRefreshRequest.start()
}
}else {
print("No receipt for this purchase")
}
}
func getReceiptURL() -> URL? {
return Bundle.main.appStoreReceiptURL
}
enum receiptValidationURLs:String {
case sandbox = "https://sandbox.itunes.apple.com/verifyReceipt"
case production = "https://buy.itunes.apple.com/verifyReceipt"
static var url:URL{
if isDebug {
return URL.init(string: self.sandbox.rawValue)!
}else{
return URL.init(string: self.production.rawValue)!
}
}
}
func sendReceiptToLocalServer(data:Data) {
let base64encodedReceipt = data.base64EncodedString()
if self.appEnvironment != nil {
self.appEnvironment! (base64encodedReceipt,isDebug)
}
}
func startValidatingData(data:Data){
let base64encodedReceipt = data.base64EncodedString()
print(base64encodedReceipt)
let requestDictionary = ["receipt-data":base64encodedReceipt]
guard JSONSerialization.isValidJSONObject(requestDictionary) else { print("requestDictionary is not valid JSON"); return }
do {
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
// let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt" // this works but as noted above it's best to use your own trusted server
NSLog("The Environment is: %#", receiptValidationURLs.url.absoluteString)
guard let validationURL = URL(string: receiptValidationURLs.url.absoluteString) 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 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)
print((appReceiptJSON as AnyObject).count)
print("success. here is the json representation of the app receipt: \(appReceiptJSON)")
NSLog("Receipt is: %#", appReceiptJSON as! NSDictionary)
if let environment = (appReceiptJSON as? NSDictionary)?["environment"]{
if self.appEnvironment != nil {
self.appEnvironment! (environment as? String ?? "No value",isDebug)
}
}
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
} else {
print("the upload task returned an error: \(error)")
}
}
task.resume()
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
}
// func requestDidFinish(_ request: SKRequest) {
// // a fresh receipt should now be present at the url
// do {
//
//
//
// } catch {
// // still no receipt, possible but unlikely to occur since this is the "success" delegate method
// }
// }
func request(_ request: SKRequest, didFailWithError error: Error) {
print("app receipt refresh request did fail with error: \(error)")
// for some clues see here: https://samritchie.net/2015/01/29/the-operation-couldnt-be-completed-sserrordomain-error-100/
if self.onReceiptVarificationError != nil {
self.onReceiptVarificationError! ()
}
}
}
This may not be the answer but would be a workaround. Best is to create a new sandbox user, sign out the old one and put the new.
Right now I have an app on the app store. I knew that I would late update it but I just wanted to make sure I put it up before I did that. So now I want to implement in-app purchases. However, for some reason it is not working:
This is in the ViewDidLoad()
let productIdentifiers: Set<String> = ["1000Coins"]
let productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
productsRequest.delegate = self
productsRequest.start()
And this is the other function. For some reason it is not printing this
print("Product: \(product.productIdentifier), \(product.localizedTitle),\(product.price.floatValue)")
func productsRequest(_ request: SKProductsRequest, didReceive
response: SKProductsResponse) {
print("Loaded Products")
for product in response.products {
print("Product: \(product.productIdentifier), \(product.localizedTitle),\(product.price.floatValue)")
}
}
I created the in app purchase with iTunes connect but for some reason it is not working. Any suggestions?? Thanks!
Don't clutter your ViewDidLoad unnecessarily. Create an in-app-purchase helper to handle all the functions associated with IAPs e.g get product identifiers, verify receipt, restore etc.
A good helper written by Ron Buencamino is this:
import StoreKit
protocol IAPManagerDelegate {
func managerDidRestorePurchases()
}
class IAPManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver, SKRequestDelegate {
static let sharedInstance = IAPManager()
var request:SKProductsRequest!
var products:NSArray!
var delegate:IAPManagerDelegate?
//Load product identifiers for store usage
func setupInAppPurchases(){
self.validateProductIdentifiers(self.getProductIdentifiersFromMainBundle())
SKPaymentQueue.default().add(self)
}
//Get product identifiers
func getProductIdentifiersFromMainBundle() -> NSArray {
var identifiers = NSArray()
if let url = Bundle.main.url(forResource: "iap_products", withExtension: "plist"){
identifiers = NSArray(contentsOf: url)!
}
return identifiers
}
//Retrieve product information
func validateProductIdentifiers(_ identifiers:NSArray) {
let productIdentifiers = NSSet(array: identifiers as [AnyObject])
let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
self.request = productRequest
productRequest.delegate = self
productRequest.start()
}
func createPaymentRequestForProduct(_ product:SKProduct){
let payment = SKMutablePayment(product: product)
payment.quantity = 1
SKPaymentQueue.default().add(payment)
}
func verifyReceipt(_ transaction:SKPaymentTransaction?){
let receiptURL = Bundle.main.appStoreReceiptURL!
if let receipt = try? Data(contentsOf: receiptURL){
//Receipt exists
let requestContents = ["receipt-data" : receipt.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))]
//Perform request
do {
let requestData = try JSONSerialization.data(withJSONObject: requestContents, options: JSONSerialization.WritingOptions(rawValue: 0))
//Build URL Request
//change the storeURL before you submit to the app store
let storeURL = URL(string: "https:/sandbox.itunes.apple.com/verifyReceipt")
var request = URLRequest(url: storeURL!)
request.httpMethod = "Post"
request.httpBody = requestData
let session = URLSession.shared
let task = session.dataTask(with: request , completionHandler: { (responseData, response , error) -> Void in
do {
let json = try JSONSerialization.jsonObject(with: responseData! as Data, options: .mutableLeaves) as! NSDictionary
print(json)
if (json.object(forKey: "status") as! NSNumber) == 0 {
//
let receipt_dict = json["receipt"] as! NSDictionary
if let purchases = receipt_dict["in_app"] as? NSArray {
self.validatePurchaseArray(purchases)
}
// you can add more statements to check different parts of the receipt
if transaction != nil {
SKPaymentQueue.default().finishTransaction(transaction!)
}
DispatchQueue.main.sync(execute: { () -> Void in self.delegate?.managerDidRestorePurchases() } ) }
else {
//Debug the receipt
print(json.object(forKey: "status") as! NSNumber)
}
}
catch {
print(error)
}
} )
task.resume()
}
catch {
print(error)
}
}
else {
//Receipt does not exist
print("No Receipt")
}
}
func validatePurchaseArray(_ purchases:NSArray){
for purchase in purchases as! [NSDictionary]{
self.unlockPurchasedFunctionalityForProductIdentifier(purchase["product_id"] as! String)
}
}
func unlockPurchasedFunctionalityForProductIdentifier(_ productIdentifier:String){
///here you put the functionality that you want to unlock for the user
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
func isDateExpired(_ expires_date:Double) -> Bool{
var isExpired:Bool = false
let currentDate = (Date().timeIntervalSince1970 * 1000) as TimeInterval
if currentDate > expires_date{
isExpired = true
}
return isExpired
}
//MARK: SKProductsRequestDelegate
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
//
self.products = response.products as NSArray!
print(self.products)
}
//MARK: SKPaymentTransactionObserver Delegate Protocol
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
//
for transaction in transactions as [SKPaymentTransaction]{
switch transaction.transactionState{
case .purchasing:
print("Purchasing")
UIApplication.shared.isNetworkActivityIndicatorVisible = true
case .deferred:
print("Deferrred")
UIApplication.shared.isNetworkActivityIndicatorVisible = false
case .failed:
print("Failed")
print(transaction.error?.localizedDescription)
UIApplication.shared.isNetworkActivityIndicatorVisible = false
SKPaymentQueue.default().finishTransaction(transaction)
case.purchased:
print("Purchased")
//
self.verifyReceipt(transaction)
case .restored:
print("Restored")
}
}
}
func restorePurchases(){
let request = SKReceiptRefreshRequest()
request.delegate = self
request.start()
}
func requestDidFinish(_ request: SKRequest) {
self.verifyReceipt(nil)
}
}
add a plist file named iap_products.plist to your Xcode and include the product ids. In said file include:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<string>product1Id</string>
<string>product2Id</string>
so on....
</array>
</plist>
I'm working on this subscription system for my app,it took a few days.But as I'm working more on it,it makes more problems.
Sometimes it happens that the app crashes when clicking on buy subscription.
Also I'm having the problem of not knowing when to stop the Please wait.... alert.I need to place the code in:
case .Purchasing:
self.alert_show()
break
But I don't know where to end it,I need to know the information when is the alert from iTunes loaded than to stop the Please wait.... alert.
The biggest problem right now that I'm facing is the crashing sometimes when i click on the Buy Button.
The crashing happens when first time launching the app,and clicking one the Buy Subscription.
Here is the code for the Buy Subscription:
#IBAction func buy_sub(sender: AnyObject) {
let payment:SKPayment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
Here is the error that I'm getting.
fatal error: unexpectedly found nil while unwrapping an Optional value..
I would like to share with you my source code of the subscription.If you have any advices what to add,or to correct or to correct this problems that i have it would be great.
Here is the full source code of the Subscription View:
class SubscriptionViewController: UIViewController ,SKPaymentTransactionObserver, SKProductsRequestDelegate {
//var productID = ""
var product: SKProduct!
#IBOutlet var buy_trial_button: UIButton!
override func viewDidAppear(animated: Bool) {
if(SKPaymentQueue.canMakePayments()) {
} else {
buy_trial_button.enabled = false
message_alert("Please enable IAPS to continue(Credit card information required in your iTunes account).")
}
//validateReceipt()
let keystore = NSUbiquitousKeyValueStore.defaultStore()
if keystore.objectForKey("expiration_date") != nil{
let expiration: AnyObject? = keystore.objectForKey("expiration_date")
let today = NSDate()
let expiredate = expiration as! NSDate
print("expiredate is %#",expiredate,today)
// var date1 = NSDate()
// var date2 = NSDate()
if(today.compare(expiredate) == NSComparisonResult.OrderedDescending){
print("today is later than expiredate")
validateReceipt()
print("Validating")
}else if(today.compare(expiredate) == NSComparisonResult.OrderedAscending){
print("today is earlier than expiredate")
self.performSegueWithIdentifier("subscriptionPassed", sender: self)
}
}else{
print("First time launch")
}
}
override func viewDidLoad() {
super.viewDidLoad()
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
self.getProductInfo()
//SKPaymentQueue.defaultQueue().addTransactionObserver(self)
//self.getProductInfo()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillDisappear(animated: Bool) {
}
#IBAction func private_policy(sender: AnyObject) {
let openLink = NSURL(string : "http://arsutech.com/private_policy.php")
UIApplication.sharedApplication().openURL(openLink!)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "subscriptionPassed")
{
}
}
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse){
let products = response.products
if products.count != 0
{
product = products[0] as SKProduct
print(product.localizedTitle + "\n" + product.localizedDescription)
}
}
func getProductInfo(){
if (SKPaymentQueue.canMakePayments()){
let productID:NSSet = NSSet(object: "com.sxxxxxxxxxxxxxxxxx")
let request:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
}
}
#IBAction func buy_sub(sender: AnyObject) {
let payment:SKPayment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
#IBAction func buy_pro(sender: AnyObject) {
let openLink = NSURL(string : "https://itunes.apple.com/us/app/home-workouts-exercises-mma/id1060747118?mt=8")
UIApplication.sharedApplication().openURL(openLink!)
}
#IBAction func exit(sender: AnyObject) {
exit(0)
}
func validateReceipt(){
alert_show()
let mainBundle = NSBundle.mainBundle() as NSBundle;
let receiptUrl = mainBundle.appStoreReceiptURL;
let isPresent = receiptUrl?.checkResourceIsReachableAndReturnError(NSErrorPointer());
if(isPresent == true){
let data = NSData(contentsOfURL: receiptUrl! );
// Create the JSON object that describes the request
let requestContents = NSMutableDictionary();
//let encodeddata = data!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions());
let encodeddata = data!.base64EncodedString();
//print("encodeddata = \(encodeddata)");
requestContents.setObject(encodeddata, forKey: "receipt-data");
requestContents.setObject("c40f23af1aa44e159aezzzzzzzzzzzzzz", forKey: "password");
var requestData : NSData?
do{
requestData = try NSJSONSerialization.dataWithJSONObject(requestContents, options: NSJSONWritingOptions());
}catch{
// NSLog("Error in json data creation at verifyPaymentReceipt");
}
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
let file = "\(documentsPath)/requestData"
if(NSFileManager.defaultManager().createFileAtPath(file, contents: data, attributes: nil)){
NSLog("File %# ",file);
}
else{
//NSLog("error File %# ",file);
}
if(requestData != nil){
//let strRequestData = NSString(data: requestData!, encoding: NSUTF8StringEncoding);
//print(" strRequestData = \(strRequestData)");
// Create a POST request with the receipt data.
//https://buy.itunes.apple.com/verifyReceipt
//https://sandbox.itunes.apple.com/verifyReceipt
let storeURL = NSURL(string: "https://sandbox.itunes.apple.com/verifyReceipt");
let storeRequest = NSMutableURLRequest(URL: storeURL!);
storeRequest.HTTPMethod = "POST";
storeRequest.HTTPBody = requestData;
// Make a connection to the iTunes Store on a background queue.
let queue = NSOperationQueue();
NSURLConnection.sendAsynchronousRequest(storeRequest, queue: queue, completionHandler: { (response : NSURLResponse?, data : NSData?, error : NSError?) -> Void in
if(error != nil){
//Handle Error
}
else{
let d = NSString(data: data!, encoding: NSUTF8StringEncoding);
// NSLog("DATA:%#", d!);
let dataA = d!.dataUsingEncoding(NSUTF8StringEncoding)
var jsonResponseInternal: NSMutableDictionary?
do{
jsonResponseInternal = try NSJSONSerialization.JSONObjectWithData(dataA!,options: NSJSONReadingOptions.AllowFragments) as? NSMutableDictionary;
//print(jsonResponseInternal);
}catch{
// NSLog("Parsing issue : verifyPaymentReceipt");
}
var jsonResponse: NSMutableDictionary?
do{
jsonResponse = try NSJSONSerialization.JSONObjectWithData(data!,
options: NSJSONReadingOptions.AllowFragments) as? NSMutableDictionary;
//print(jsonResponse);
}catch{
// NSLog("Parsing issue : verifyPaymentReceipt");
}
if(jsonResponse != nil){
if(jsonResponse != nil){
//NSLog("Expiration Date: %#", jsonResponse!);
//print("Passed")
/*
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "Purchase")
NSUserDefaults.standardUserDefaults().synchronize()
*/
//self.performSegueWithIdentifier("subscriptionPassed", sender: self)
if jsonResponse!["status"] as? Int == 0 {
//print("Sucessfully returned internal receipt data")
if let receiptInfo: NSArray = jsonResponse!["latest_receipt_info"] as? NSArray {
let lastReceipt = receiptInfo.lastObject as! NSDictionary
var trial_period: Bool = false
// Get last receipt
//print("LAST RECEIPT INFORMATION \n",lastReceipt)
print("Last from internal memory",lastReceipt["original_transaction_id"])
//var is_trial = lastReceipt["is_trial_period"] as! Bool
//var date_bought =
//var date_expires =
// Format date
//print(is_trial)
// Format date
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
// Get Expiry date as NSDate
let subscriptionExpirationDate: NSDate = formatter.dateFromString(lastReceipt["expires_date"] as! String) as NSDate!
print("\n - DATE SUBSCRIPTION EXPIRES = \(subscriptionExpirationDate)")
let currentDateTime = NSDate()
print(currentDateTime)
if var is_trial:Bool = false{
if(lastReceipt["is_trial_period"] as? String == "Optional(\"true\")"){
is_trial = true
trial_period = is_trial
}else if(lastReceipt["is_trial_period"] as? String == "Optional(\"false\")"){
is_trial = false
trial_period = is_trial
}
}
if (subscriptionExpirationDate.compare(currentDateTime) == NSComparisonResult.OrderedDescending) {
self.alrt_close()
print("Pass");
print("The trial period is \(trial_period)")
let keystore = NSUbiquitousKeyValueStore.defaultStore()
keystore.setObject(subscriptionExpirationDate,forKey:"expiration_date")
keystore.synchronize()
self.performSegueWithIdentifier("subscriptionPassed", sender: self)
} else if (subscriptionExpirationDate.compare(currentDateTime) == NSComparisonResult.OrderedAscending) {
print("Not Pass");
print("Subscription expired")
self.alrt_close()
//self.message_alert("Subscription expired")
}
}
}
}
}
}
});
}
}
}
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction{
switch trans.transactionState {
case .Purchasing:
//self.alrt_close()
break
case .Deferred:
self.alert_show()
break
case .Purchased:
alrt_close()
self.validateReceipt()
//self.setExpirationDate()
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
//dismissViewControllerAnimated(false, completion: nil)
break
case .Failed:
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
print("Not called Expired")
message_alert("Error while purchasing!")
break
case .Restored:
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
print("Restored")
break
default:
break
}
}
}
}
//Alrt
func message_alert(let message_A:String){
let alert = UIAlertController(title: "", message: "\(message_A)", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
func alert_show(){
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .Alert)
alert.view.tintColor = UIColor.blackColor()
let loadingIndicator: UIActivityIndicatorView = UIActivityIndicatorView(frame: CGRectMake(10, 5, 50, 50)) as UIActivityIndicatorView
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
presentViewController(alert, animated: true, completion: nil)
}
func alrt_close(){
self.dismissViewControllerAnimated(false, completion: nil)
}
}