In app purchases not selectable in appstoreconnect - swift

My new app has four in app purchases (consumables) and I submitted the first version with these IAPs. These IAP's were marked as "Ready for Review". However, the app got rejected due to another reason, and when I uploaded a new build, I couldn't select these IAP's anymore in the app details page, even though they're still "Ready for Review":
screenshot of the app details page
So after resubmitting a new version of the app for review, I got this rejection information:
We found that your in-app purchase products exhibited one or more bugs when reviewed on iPad running iOS 15.4 on Wi-Fi.
Specifically, we were not able to buy the in app purchases. The buttons did not react to taps
Next Steps
When validating receipts on your server, your server needs to be able to handle a production-signed app getting its receipts from Apple’s test environment. The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code "Sandbox receipt used in production," you should validate against the test environment instead.
I tested everything on Testflight before and all the IAP's were working fine. I know that prior to submitting an app with IAP's, these purchases have to be selected on the app details page, so I'm curious why I can't select them and if that's causing the issue.
Right when the app launches, in the AppDelegate, I fetch the products: IAPManager.shared.fetchProducts()
and the code for the IAPManager is as follows:
final class IAPManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
static let shared = IAPManager()
var products = [SKProduct]()
enum Product: String, CaseIterable {
case firstIdentifier = "com.fahrprueferCreate.tokens_1_1000"
case secondIdentifier = "com.FahrprueferCreate.tokens_5_4000"
case thirdIdentifier = "com.FahrprueferCreate.tokens_10_8000"
case fourthIdentifier = "com.FahrprueferCreate.tokens_20_15000"
var count: Int {
switch self {
case .firstIdentifier:
return 1
case .secondIdentifier:
return 5
case .thirdIdentifier:
return 10
case .fourthIdentifier:
return 20
}
}
}
private var completion: ((Int) -> Void)?
// Fetch Product Objects from Apple
func fetchProducts() {
let request = SKProductsRequest(productIdentifiers: Set(Product.allCases.compactMap({ $0.rawValue})))
request.delegate = self
request.start()
}
// Prompt a product payment transaction
public func purchase(product: Product, completion: #escaping ((Int) -> Void)) {
guard SKPaymentQueue.canMakePayments() else {
// Show some error here
return
}
guard let storeKitProduct = products.first(where: { $0.productIdentifier == product.rawValue }) else {
return
}
self.completion = completion
let payment = SKPayment(product: storeKitProduct)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
}
// Observe the transaction state
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
transactions.forEach({ transaction in
switch transaction.transactionState {
case.purchasing:
break
case .purchased:
if let product = Product(rawValue: transaction.payment.productIdentifier) {
completion?(product.count)
}
SKPaymentQueue.default().finishTransaction(transaction)
SKPaymentQueue.default().remove(self)
break
case .restored:
break
case .failed:
break
case .deferred:
break
#unknown default:
break
}
})
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
self.products = response.products
print("products: ", response.products)
}
func request(_ request: SKRequest, didFailWithError error: Error) {
guard request is SKProductsRequest else {
return
}
print("Product fetch request failed")
}
}

Related

Firebase phone auth error "TOO_LONG" on iPad (using test number)

So I have submitted an iOS app for review and the apple team has rejected it saying they cannot sign in on an iPad using the test number I provided. I know the test number works on my physical iPhone and it also works on an Xcode iPad simulator, however I do not have a physical iPad to test on.
Screenshot from them:
screenshot from apple review team
here is the phone number handling func:
private func startAuth(completion: #escaping (Bool) -> Void) {
let areaPhoneNum = "+\(self.getCountryCode() + self.phoneNum)"
print(areaPhoneNum)
PhoneAuthProvider.provider().verifyPhoneNumber(areaPhoneNum, uiDelegate: nil) { [weak self] verificationID, error in
if let verificationID = verificationID {
self?.verificationID = verificationID
completion(true)
} else if let error = error {
print(error)
self?.authPhoneErrorMess = error.localizedDescription
self?.isLoading = false
completion(false)
}
}
}
Why would an error message print on a valid test number?

StoreKit purchases stuck in a loop

I am trying to implement in-app purchases in an app. this is the firs time i am doing this. I think i have the whole thing set up correctly, when i test on the device it seems to work however i get "Purchased" printed on the console a lot, it purchased 500 times (it seemed to be caught in a loop). I checked this by checking Debug -> Store Kit -> Manage transactions it seems to be purchasing in a loop. I can't see where I've gone wrong with the code.
This started happening after I added in a button that restores the purchase.
enum Product: String, CaseIterable {
case noadspremium = "unlock1"
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if let oProduct = response.products.first {
print("product is available")
self.purchase(aproduct: oProduct)
}
else {
print("product not available")
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchasing:
print("customer is in the process of purchase")
case .purchased:
SKPaymentQueue.default().finishTransaction(transaction)
print("purchased")
case .failed:
SKPaymentQueue.default().finishTransaction(transaction)
print("failed")
case .restored:
SKPaymentQueue.default().restoreCompletedTransactions()
print("Restored")
case .deferred:
print("deferred")
default:
break
}
}
}
func purchase(aproduct: SKProduct) {
let payment = SKPayment(product: aproduct)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
}
#IBAction func buyPremium(_ sender: Any) {
if SKPaymentQueue.canMakePayments(){
let set: Set<String> = [Product.noadspremium.rawValue]
let productRequest = SKProductsRequest(productIdentifiers: set)
productRequest.delegate = self
productRequest.start()
}
}
#IBAction func restorePressed(_ sender: Any) {
SKPaymentQueue.default().restoreCompletedTransactions()
}

In-App Purchase Payment Transaction Not Functioning

My app displays prompts when the user clicks a specific button. I'm want to have additional prompt packages for non-consumable in-app purchase available (click the package to purchase, then if purchased you can use that same button to toggle the package on and off).
Below is all the relevant code...
import UIKit
import QuartzCore
import StoreKit
class ViewController: UIViewController, SKPaymentTransactionObserver {
let productID = "com.domain.app.purchase"
override func viewDidLoad() {
super.viewDidLoad()
SKPaymentQueue.default().add(self)
}
var packsUnlocked = false
#IBAction func selectPack1(_ sender: UIButton) {
if SKPaymentQueue.canMakePayments() {
let paymentRequest = SKMutablePayment()
paymentRequest.productIdentifier = productID
SKPaymentQueue.default().add(paymentRequest)
print("Initiating Transaction")
} else {
print("No Purchased")
}
if packsUnlocked == false {
print("It's locked, ‘Pack 1’ not enabled")
} else {
print(“Utilize Purchase”)
//this is where you place code to use the purchased ‘Pack 1’
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions{
if transaction.transactionState == .purchased {
packsUnlocked = true
print("Transaction Successful")
} else if transaction.transactionState == .failed {
print("Transaction Failed")
}
}
}
}
However whenever I run the code and click the button all I get is the below outputs in the debugger:
"
Initiating Transaction
It's locked, 'Pack 1' not enabled
Transaction Failed
"
This is my first app and I've never set up a sandbox tester before, so I'm not sure if the code is the problem or if it's something with my App Store Connect setup.
Thank you - I've been stuck on this for wayyy too long so any help is GREATLY appreciated.
You need to test In-App Purchases on an actual device in the sandbox environment.
Here is the Apple Documentation on it, but I can guide you as well.
https://developer.apple.com/documentation/storekit/in-app_purchase/testing_in-app_purchase_transactions
Main steps are:
Create a sandbox or test user account in App Store Connect.
For iOS 12 or later — Don't sign out of the App Store; simply build and run your app from Xcode. Sandbox accounts are stored separately, and you can control your sandbox account directly on-device in Settings. (You must run on your attached device.)

In app purchase restore button doesn't cause any action?

I am new to working in in-app purchases. I have set up my app to allow multiple non-consumable in-apps. If it is a first time purchase it works perfectly. If I try and click the buy button again it shows "This in-app has already been purchased etc. etc." once you click Okay, it does nothing. I have noticed it only shows "Okay" as the option and not "Cancel" and "Okay". In my test app, it shows both and works great.` #IBOutlet weak var buyProductID: UILabel!
let product1 = "TestAd.com"
#IBOutlet weak var adView1: UIView!
func buyProduct1(product1: SKProduct){
print("Sending the Payment Request to Apple 1");
let payment1 = SKPayment(product: product1)
SKPaymentQueue.default().add(payment1);
}
#IBAction func product1Btn(sender: AnyObject) {
buyProductID.text = "Product1"
print("About to fetch the product... 1")
// Can make payments
if (SKPaymentQueue.canMakePayments())
{
let productID1:NSSet = NSSet(object: self.product1);
let productsRequest1:SKProductsRequest = SKProductsRequest(productIdentifiers: productID1 as! Set<String>);
productsRequest1.delegate = self;
productsRequest1.start();
print("Fetching Products 1");
}else{
print("Can't make purchases 1");
}
}
func purchase1ViewDid(){
if (UserDefaults.standard.bool(forKey: "purchased1")){
adView1.isHidden = true
print("No ads for 1")
} else {
print("Yes ads 1")
}
}
func productsRequest (_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
//PRODUCT 1
let count1 : Int = response.products.count
if (count1>0) {
let validProduct1: SKProduct = response.products[0] as SKProduct
if (validProduct1.productIdentifier == self.product1) {
print(validProduct1.localizedTitle)
print(validProduct1.localizedDescription)
print(validProduct1.price)
buyProduct1(product1: validProduct1);
} else {
print(validProduct1.productIdentifier)
}
} else {
print("nothing 1")
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("Error Fetching product information 1");
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions1: [SKPaymentTransaction]) {
print("Received Payment Transaction Response from Apple 1");
for transaction1:AnyObject in transactions1 {
if let trans:SKPaymentTransaction = transaction1 as? SKPaymentTransaction{
switch trans.transactionState {
case .purchased:
if buyProductID.text == "Product1" {
print("Product Purchased 1");
SKPaymentQueue.default().finishTransaction(transaction1 as! SKPaymentTransaction)
// Handle the purchase
UserDefaults.standard.set(true , forKey: "purchased1")
adView1.isHidden = true
}
break;
case .failed:
print("Purchased Failed 1");
SKPaymentQueue.default().finishTransaction(transaction1 as! SKPaymentTransaction)
break;
case .restored:
print("Already Purchased 1");
SKPaymentQueue.default().restoreCompletedTransactions()
// Handle the purchase
UserDefaults.standard.set(true , forKey: "purchased1")
adView1.isHidden = true
break;
default:
break;
}
}
}
}
`
Non-consumable purchases can only be purchased once, so this is the expected behavior. The message is displayed because you can only purchase the non-consumable item once, and it remains associated with the account. It thinks that since you have purchased the item already, that you simply want to restore it. As a side note, for testing purposes, IAPs have to be tested using a real device, so the simulator won't test IAPs correctly.

ios sandbox restored non-consumable IAP without buying

Friends, my question about testing IAP in the Sandbox.
My Steps:
Sign up in itunes-connect the new sandbox tester.
itunes / appstore on the test phone log out
Delete App from device
RUN in Xcode.
All beautifully displayed on my phone. Everything works exactly the way I want to. Except for one scenario.
I just don't know, maybe it should be in the Sandbox, Sandbox-testers feauture.
In my view-controller, which is implemented non-consumable IAP, there are two buttons: "buy" and "restore."
By clicking "restore" (ONLY "restore"), and entering id / password just registered tester, I expect that nothing will be restored, because this id has never been pressed "Buy" button.
But the recovery is successful. Without buying process.
It's OK?
My code
import UIKit
import StoreKit
class PurchaseUI: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver{
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.setHidesBackButton(true, animated:true)
if (SKPaymentQueue.canMakePayments()) {
let productID: NSSet = NSSet(object: "bla.bla.bla.pro1")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
}
}
#IBAction func buyBtn(sender: AnyObject) {
for product in flag0{
let prodID = product.productIdentifier
if (prodID == ""bla.bla.bla.pro1""){
flag1 = product
buyproduct()
break
}
}
}
func fullVers(){
cashflag = true // global var
}
#IBAction func restorebtn(sender: AnyObject) {
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
var flag0 = [SKProduct]()
var flag1 = SKProduct()
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
let myProduct = response.products
for product in myProduct {
flag0.append(product)
}
}
func buyproduct(){
let pay = SKPayment(product: flag1)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(pay as SKPayment)
}
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction as SKPaymentTransaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "bla.bla.bla.pro1":
fullVers()
default:
break
}
}
}
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
switch trans.transactionState {
case .Purchased, .Restored:
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction as SKPaymentTransaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "bla.bla.bla.pro1":
fullVers()
default:
break
}
}
queue.finishTransaction(trans)
break
case .Deferred:
queue.finishTransaction(trans)
break
case .Failed:
queue.finishTransaction(trans)
break
default:
break
}
}
}
func finishTransaction(trans: SKPaymentTransaction){
}
func paymentQueue(queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
}
}
In the paymentQueueRestoreCompletedTransactionsFinished method, you need to check the transaction state - you are granting full access no matter what happened in the transaction. The method has returned saying the transaction finished but, since no purchases were made, nothing should happen. Check for SKPaymentTransactionStateRestored in there and I think that should do the trick.
You're doing the check in paymentQueue:updatedTransactions but not in that one.