iOS In-App purchase for Non-Consumable affected by IPV6 - ios

I implement IAP using Swift to unlock my game stage. It works well on IPv4. So I submit this binary for review and get rejected by Apple when they tested on IPv6 network.
Reject binary reason:
We discovered one or more bugs in your app when reviewed on iPhone running iOS 9.3.2 on Wi-Fi connected to an IPv6 network.
Specifically, after we purchase the In App Purchase, the level does not unlock.
I put break point for every case but the program didn't go inside any 1 of them when I running on IPv6 network.
Here my code for do purchase:
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("Received Payment Transaction Response from Apple");
for transaction:AnyObject in transactions {
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction{
switch trans.transactionState {
case .Purchased:
print("Product Purchased");
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
self.levelButtonHalloween.enabled = true
lockImage.removeFromSuperview()
Overlay.removeFromSuperview()
userSettingDefaults.setBool(true, forKey: "enableHalloween")
userSettingDefaults.synchronize()
DesertOver50 = userSettingDefaults.boolForKey("enableHalloween")
buyBottom.removeFromSuperview()
backgroundImage.removeFromParent()
backgroundImage = SKSpriteNode(imageNamed: "StageSelect_Background2")
backgroundImage.size = self.frame.size
backgroundImage.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
backgroundImage.anchorPoint = CGPointMake(0.5, 0.5)
backgroundImage.zPosition = 0
addChild(backgroundImage)
break;
case .Failed:
print("Purchase Failed");
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
break;
case .Restored:
print("Transaction restored")
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
self.levelButtonHalloween.enabled = true
lockImage.removeFromSuperview()
Overlay.removeFromSuperview()
userSettingDefaults.setBool(true, forKey: "enableHalloween")
userSettingDefaults.synchronize()
DesertOver50 = userSettingDefaults.boolForKey("enableHalloween")
buyBottom.removeFromSuperview()
backgroundImage.removeFromParent()
backgroundImage = SKSpriteNode(imageNamed: "StageSelect_Background2")
backgroundImage.size = self.frame.size
backgroundImage.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
backgroundImage.anchorPoint = CGPointMake(0.5, 0.5)
backgroundImage.zPosition = 0
addChild(backgroundImage)
default:
break;
}
}
}
SKPaymentQueue.defaultQueue().removeTransactionObserver(self)
}
I also implement button for restore purchase and it work well for both ipv4 and ipv6.
Here the code for restore purchase:
func restorePurchaseButtonAction(){
button_Clicked()
if (DesertOver50 == false){
if (SKPaymentQueue.canMakePayments()) {
// Enable SKPayment as soon as possible during viewdidload
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
}else{
let alert = UIAlertController(title: "It's already unlocked ¬_¬", message: "Your have already unlocked or purchased the item(s)", preferredStyle: .ActionSheet)
let ok = UIAlertAction(title: "OK", style: .Cancel) { action -> Void in
}
alert.addAction(ok)
alert.popoverPresentationController?.sourceView = view
alert.popoverPresentationController?.sourceRect = self.frame
self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}
}
Anyone have experience this before? Any different implement IAP on IPv4 vs IPv6? What should I do next?

Do you check your settings in ItunesConnect whether in-app purchase is registered correctly? Remember to put
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
in viewDidLoad. This issue has been plaguing me too.

Thank you for your advice. My ituneConnect seem ok.
that SKPaymentQueue.defaultQueue().addTransactionObserver(self) is root cause
of my problem.
Scenario 1:
Item completely purchase but stage not unlock =>Fail
func buyNonConsumable() {
button_Clicked()
print("About to fetch the products");
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
// We check that we are allow to make the purchase.
if (SKPaymentQueue.canMakePayments()) {
let productID:NSSet = NSSet(object: self.product_id!);
let productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>);
productsRequest.delegate = self;
productsRequest.start();
print("Fething Products");
} else {
print("can't make purchases");
}
}
Scenario 2: Item auto unlock itself when app start without need to login =>Fail
override func didMoveToView(view: SKView) {
// In-App button and function call
if(DesertOver50 == false) {
product_id = "xxxxxxxx";
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
addBotton();
Scenario 3: Working properly
func buyProduct(product: SKProduct) {
print("Sending the Payment Request to Apple");
let payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment);
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
}
From what I understand scenario 2 should best solution but it fail.
Since code now working perfectly, I assume scenario 3 is the solution.

Related

inApp purchase payment in ios

InApp purchase payment in ios
i need to subscribe a plan using InApp purchase. for payment but i am showing an alert from sandbox environment like "would you like to buy ...." i need to add my card details and show some sample transaction happening. How to redirect to payment and add card details? how the InApp purchase payment can be done in
real time environment.
// MARK: - IAP payment queue
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
if let trans = transaction as? SKPaymentTransaction {
switch trans.transactionState {
case .purchased:
hideLoader()
if let paymentTransaction = transaction as? SKPaymentTransaction {
SKPaymentQueue.default().finishTransaction(paymentTransaction)
}
if productID == PRODUCT_ID {
UserDefaults.standard.set(true, forKey: "isPurchased")
lblPurchaseDone.text = "Pro version PURCHASED!"
lblPurchaseDone.isHidden = false
btnPurchase.isHidden = true
btnRestore.isHidden = true
self.present(Utilities().showAlertContrller(title: "Purchase Success", message: "You've successfully purchased"), animated: true, completion: nil)
}
case .failed:
hideLoader()
if trans.error != nil {
self.present(Utilities().showAlertContrller(title: "Purchase failed!", message: trans.error!.localizedDescription), animated: true, completion: nil)
print(trans.error!)
}
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
case .restored:
print("restored")
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
default: break
}
}
}
}

How to check purchase status of In-App Purchase (Auto Renewing Subscription)?

I created an In-App Purchase feature for the app, using an Auto-Renewing Subscription. The problem is that I'm not sure how to check the purchase status, whether the subscription has run out or is still active.
Here's where I try saving the purchased status:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("add payment")
for transaction: AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
print(trans.error)
switch trans.transactionState {
case .purchased:
print("buy ok, unlock IAP HERE")
print(p.productIdentifier)
let prodID = p.productIdentifier
switch prodID {
case "com.test.UnlockTools.Subscription1":
print("tool set 1 unlocked")
uTool1()
print("tool set 2 unlocked")
uTool2()
print("tool set 3 unlocked")
uTool3()
UserDefaults.standard.set(true, forKey: "isSubbed")
default:
print("IAP not found")
}
queue.finishTransaction(trans)
case .failed:
print("buy error")
queue.finishTransaction(trans)
break
default:
print("Default")
break
}
}
}
This is where I call the UserDefaults and allow or deny button interaction:
override func viewDidLoad() {
super.viewDidLoad()
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID: NSSet = NSSet(object: "com.test.UnlockTools.Subscription1")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
status = UserDefaults.standard.bool(forKey: "isSubbed") ?? false
if status == true {
unlockTool1.isUserInteractionEnabled = true
unlockTool2.isUserInteractionEnabled = true
unlockTool3.isUserInteractionEnabled = true
} else {
unlockTool1.isUserInteractionEnabled = false
unlockTool2.isUserInteractionEnabled = false
unlockTool3.isUserInteractionEnabled = false
}
}
If I find out the status then I will be able to save the state as true/false. I tried using UserDefaults with some success. Not sure if I placed the code in the best location though.
If the subscription is still active, I'd like to allow the user to click a button, and deny the button click if the subscription has run out.
I'll add more code if needed. Any help would be greatly appreciated.
The only way to check the subscription status is to verify the receipt with Apple to see if it's still valid using their /verifyReceipt endpoint - docs.
What you could do is cache some expiration date after the purchase and use that to check if the subscription is valid. If it's passed the expiration date you can re-check the receipt with Apple to see if it's renewed. There are also edge cases where a user is refunded and their subscription is cancelled before the expiration date - you should update your receipts periodically with Apple to check this case. Ideally, this should all be happening server side as well to avoid piracy.
Here's a great post that summarizes the nauces of Apple subscriptions very well: iOS Subscriptions are Hard

Hidden Adjacent CollectionViewCells are still active?

I have a collectionView with paging enabled in the storyboard. Each collection view cell is the full width of the screen
return CGSize(width: collectionView.bounds.width, height: collectionView.bounds.height)
I am trying to set up IAP and have encountered an Issue. Each cell has a button that checks for a product_ID associated with the row when tapped.
print("About to fetch the product...")
// Can make payments
if (SKPaymentQueue.canMakePayments())
{
let productID:NSSet! = NSSet(object: self.product_ID)
let productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
productsRequest.delegate = self
productsRequest.start()
print("Fetching Products")
}else{
print("Can't make purchases")
UIAlertView(title: "Purchases Disabled",
message: "Purchases are disabled on your device! Enable Purchases in the Restriction Settings on your device.",
delegate: nil, cancelButtonTitle: "OK").show()
}
payment queue
func paymentQueue(_ queue: SKPaymentQueue,
updatedTransactions transactions: [SKPaymentTransaction])
{
print("Received Payment Transaction Response from Apple")
for transaction:AnyObject in transactions {
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction{
switch trans.transactionState {
case .purchased:
print("Product Purchased")
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
// Handle the purchase
print(product_ID)
UserDefaults.standard.set(true, forKey: product_ID)
setTitle = "Apply Theme"
case .failed:
print("Purchased Failed")
setTitle = "4.99"
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
case .restored:
print("Already Purchased")
//SKPaymentQueue.default().restoreCompletedTransactions()
// Handle the purchase
UserDefaults.standard.set(true, forKey: product_ID)
setTitle = "Apply Theme"
default:
break
}
purchaseButton.setTitle(setTitle, for: .normal)
}
}
}
`
The issue I have is that when I press the button, it doesn't return 1 productID, it returns 3. It processes the payment for the cell I select and every adjacent cell.. It seems that even when the other cells are off screen they are still active. They don't seem to be removed by the system. Is there a solution to this? I've never run into an issue like this before.

In-app purchase ios sending more transactions than needed

Im using in-app purchase in SpriteKit. The first transaction does fine, but when i do the second one my
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction])
called 2 times, than 3,4,5
so i do one request but there it adds 100 coins instead of 50...
i gues the problem in that function:
func buyProduct() {
print("buy +" + p.productIdentifier)
var payment = SKMutablePayment(product: p)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
i also have :
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
var myProduct = response.products
for product in myProduct {
list.append(product)
print("product added")
print(product.productIdentifier)
print(product.localizedTitle)
}
print("list is \(list)")
}
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print(transactions.count)
for transaction in transactions {
print(transaction.error)
switch transaction.transactionState {
case .Purchased:
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
print("buyed")
print(p.productIdentifier)
let prodID = p.productIdentifier
switch prodID {
case "com.addCoins" :
print("increaing coinsCount")
coinsCount = coinsCount + 50
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setInteger(coinsCount, forKey: "coins")
userDefaults.synchronize() // don't forget this!!!!
coinsLabel.text = String(coinsCount)
default: print("IAD not setuped")
}
print("premium added")
queue.finishTransaction(transaction)
break
case .Failed:
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
print("buy error")
queue.finishTransaction(transaction)
break
default: break
}
}
}
im calling purchase with:
for product in list {
let loadingNotification = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
loadingNotification.mode = MBProgressHUDMode.Indeterminate
loadingNotification.labelText = "Loading"
let prodID = product.productIdentifier
if prodID == "com.addCoins" {
p = product
buyProduct()
break
}
}
At some point you have to tell Apple that a purchase has been carried out and you delivered the goods. Until then, the purchases stay in the transaction queue forever.
Its bad practice to add
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
everytime you buy something. You should add the transaction observer at app launch and remove it when your app is closed, as per apples recommended guidelines.
Also you dont need to call synchronise for your userDefaults anymore since iOS 8, yet I still see a lot of people doing it.
In regards to you problem the code seems to be ok. The only thing I noticed so far is why in the buy function are you adding a SKMutablePayment?
Try changing SKMutablePayment to SKPayment and see if it makes a difference.
func buyProduct() {
print("buy +" + p.productIdentifier)
var payment = SKPayment(product: p)
SKPaymentQueue.defaultQueue().addTransactionObserver(self) // should not be here
SKPaymentQueue.defaultQueue().addPayment(payment)
}

Swift, spritekit: In app purchase code runs, NOTHING happens?

Ok, so I'm working in Swift and I just need help. I have followed 4 different tutorials on how to implement in app purchases in sprite kit with Swift, copied code verbatim, and nothing is working for me.
Here are the steps I have taken:
Gone in Itunes Connect and created an in app purchase under my app's record. My in app purchase's product Id is "GameOverSaveSavior"
In Xcode, I've turned my app's In app purchase capability to ON, made sure my team is set to my account, and made sure my Bundle Identifier under info is set to my app's bundle identifier in ITunes Connect
Before writing any code, I have import StoreKit in my GameScene.swift file
As for code, this is what I have done:
(1) In Gamescene.swift, at the end of my didMoveToView func, I have:
// Set IAPS
if(SKPaymentQueue.canMakePayments()) {
println("IAP is enabled, loading")
var productID:NSSet = NSSet(objects: "GameOverSaveSavior")
var request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as Set<NSObject>)
request.delegate = self
request.start()
} else {
println("please enable IAPS")
}
This outputs "IAP is enabled, loading" when the app is run.
(2) In GameScene.swift, within the class but outside of didMoveToView, I have all the functions and variables others have used for in app purchases:
var list = [SKProduct]()
var p = SKProduct()
func purchaseMade() {
println("they bought it!")
}
func buyProduct() {
println("buy" + p.productIdentifier)
var pay = SKPayment(product: p)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(pay as SKPayment)
}
func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) {
println("product request")
var myProduct = response.products
for product in myProduct {
println("product added")
println(product.productIdentifier)
println(product.localizedTitle)
println(product.localizedDescription)
println(product.price)
list.append(product as! SKProduct)
}
}
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue!) {
println("transactions restored")
var purchasedItemIDS = []
for transaction in queue.transactions {
var t: SKPaymentTransaction = transaction as! SKPaymentTransaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "GameOverSaveSavior":
purchaseMade()
//Right here is where you should put the function that you want to execute when your in app purchase is complete
default:
println("IAP not setup")
}
}
var alert = UIAlertView(title: "Thank You", message: "Your purchase(s) were restored. You may have to restart the app before banner ads are removed.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
println("add paymnet")
for transaction:AnyObject in transactions {
var trans = transaction as! SKPaymentTransaction
println(trans.error)
switch trans.transactionState {
case .Purchased, .Restored:
println("buy, ok unlock iap here")
println(p.productIdentifier)
let prodID = p.productIdentifier as String
switch prodID {
case "GameOverSaveSavior":
//Here you should put the function you want to execute when the purchase is complete
var alert = UIAlertView(title: "Thank You", message: "You may have to restart the app before the banner ads are removed.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
default:
println("IAP not setup")
}
queue.finishTransaction(trans)
break;
case .Failed:
println("buy error")
queue.finishTransaction(trans)
break;
default:
println("default")
break;
}
}
}
func finishTransaction(trans:SKPaymentTransaction)
{
println("finish trans")
}
func paymentQueue(queue: SKPaymentQueue!, removedTransactions transactions: [AnyObject]!)
{
println("remove trans");
}
This outputs "product request" to the console when the app is run.
(3) In GameScene.swift, in my touchesBegan func, I have the following for when the right button is touched:
//In app purchase
if touchedNode == saveMeBtn {
println("button touched!")
for product in list {
var prodID = product.productIdentifier
if(prodID == "GameOverSaveSavior") {
p = product
buyProduct() //This is one of the functions we added earlier
break;
}
}
This outputs "button touched!" when the button is touched.
I do not know what I am doing wrong. The code compiles with no errors, yet nothing occurs when my button is touched. There is no alert asking the user to sign in or to purchase anything- nothing.
I have mainly followed this question:
in app purchase in SKScene
Is there anything else I should have done prior to writing the code? Am I missing something in my code?
I think I can help you:
I had your issue in the past. And another similar one which I fixed here: My IAP isn't working. Bugs at func Paymentqueue
Here is the solution I had found:
Delete
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
everywhere you have it and put it once (ONLY ONCE) in a place where it will be executed each time your app boots up ( I put it in viewDidLoad()).
This will check for all unfinished transactions and terminate them once the app has loaded, thus removing any possible errors before your users triggers an IAP.
(If this answer helped you, don't forget to upvote ;))
P.S.: Also, this wasn't my issue, but make sure to finishTransaction() for each PurchaseState, like here:
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("Add Payment")
for transaction:AnyObject in transactions{
let trans = transaction as! SKPaymentTransaction
print(trans.error)
switch trans.transactionState{
case .Purchased:
print("IAP unlocked")
print(p.productIdentifier)
let prodID = p.productIdentifier as String
switch prodID{
case "IAP id":
print("Keep on")
keepOn()
default:
print("IAP not setup")
}
queue.finishTransaction(trans)
break
case .Failed:
print ("Buy error")
queue.finishTransaction(trans)
break
default:
print("default: Error")
break
}
}
}
Never forget this:
queue.finishTransaction(trans)
And make separate case for .Restored.

Resources