Swift 3 SKProductsRequest turns 0 element - in-app-purchase

I have done every step which apple says. But still cannot get products. This part looks ok. I can see 'IAP is enabled. loading' message.
if SKPaymentQueue.canMakePayments() {
print("IAP is enabled. loading")
let productID:NSSet = NSSet(objects: "com.companyname.appname.onecredit", "com.companyname.appname.threecredits")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
And this is the SKProductsRequest of the code. Which I cannot get any product.
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("product request")
let myProduct = response.products
for product in myProduct {
print("product added")
print(product.productIdentifier)
print(product.localizedTitle)
print(product.localizedDescription)
print(product.price)
list.append(product as SKProduct)
}
}
The only thing, on the Agreements, Tax, and Banking section, status is still 'pending tax'. Is this the reason?

The problem was US Tax Form. Once its approved, everything is fine.

Yes - I can confirm that ALL of the "Agreements, Tax, and Banking" in iTunes Connect need to be "COMPLETED" in full. After I filled out all the info, it took about 30 minutes for the PROCESSING status to be finished. Then I was able to get a response from the SKProductsRequestDelegate for my list of products! Yay!

Related

In App Purchase Failed “Cannot connect to iTunes Store” error

I am facing this issue since last two months
My App is live with auto-renew InApp purchase before it was working fine then many user complained that when they click on Buy Button Nothing happens so i debug my code and run on my real device I found this error
failed SKPrdouctName
Error Domain=SKErrorDomain Code=2 "Cannot connect to iTunes Store" UserInfo={NSLocalizedDescription=Cannot connect to iTunes Store}
i did not change any code
i did not upload new build
My product is also Approved on Appstore Connect
I did not change anything on the product
I already tried Restarting device, clear caches, login logout apple id still getting the same error
This is happening to many live users
I really need a solution to this
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print(response.products)
products = response.products
for product in response.products {
print(product.localizedTitle)
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print(error.localizedDescription)
}
func requestDidFinish(_ request: SKRequest) {
print(request)
Global.sharedInstance.isConsiderPurchasedOrNot = true
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
print(transaction.transactionState.status(), transaction.payment.productIdentifier)
switch transaction.transactionState {
case .purchasing:
break
case .purchased:
Miscellaneous.APPDELEGATE.window!.stopMyToastActivity()
if Global.sharedInstance.isConsiderPurchasedOrNot{
print(transaction.payment.productIdentifier + "is purchased")
let prodID = transaction.payment.productIdentifier as String
if prodID == MyProducts.autoRenewSubscription
{
Global.sharedInstance.isPurchased = 1
purchaseSubscriber(usediD: Global.getUserID())
}
}
break
case .restored:
print(transaction.payment.productIdentifier + "is purchased/restored! hello ")
// let str1 = UserDefaults.standard.string(forKey: "username")
Global.sharedInstance.isPurchased = 1
let prodID = transaction.payment.productIdentifier as String
Miscellaneous.APPDELEGATE.window!.stopMyToastActivity()
if prodID == MyProducts.autoRenewSubscription
{
Global.sharedInstance.isPurchased = 1
}
case .failed:
print(transaction.payment.productIdentifier + "is failed!")
Miscellaneous.APPDELEGATE.window!.stopMyToastActivity()
default: queue.finishTransaction(transaction)
}
}
}

How can fetch ProductIDs in Apple Connect?(In App Purchase)

I'm trying to do fetch ProductIDs. But response always be null.
I want to show what I do.
I Create App IDs in developer Account.BundleID equal to AppID
I've installed the project App Connect. Selected my Project BundleID
Created ProductID's
Switch on the In App Purchase in Xcode
My Code:
Created ProductID's:
`enum IAPProduct: String {
case Consumble = "com.xxxx.xxx.Consumable"
case nonConsumble = "com.xxxx.xxx.TekSeferAl"
}`
My fetch code:
class IAPService: NSObject {
static let shared = IAPService()
private override init() {}
//MARK:- Properties
//MARK:- Private
func getProducts() {
SKPaymentQueue.canMakePayments()
let products: Set = [IAPProduct.Consumble.rawValue,
IAPProduct.nonConsumble.rawValue]
let request = SKProductsRequest(productIdentifiers: products)
request.cancel()
request.delegate = self
request.start()
}
}
Delegate:
extension IAPService: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
response.invalidProductIdentifiers.forEach { product in
print("Invalid: \(product)")
}
response.products.forEach { product in
print("Valid: \(product)")
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("Error for request: \(error.localizedDescription)")
}
}
}
Output
Where am I making a mistake?
The problem is that it seems your products are in the state of "Missing Metadata" in App Store Connect - they need to be in "Ready to Submit" for testing.
You're probably either:
Missing a photo (you can use a blank image for testing)
Haven't signed the the Paid Applications Agreement with Apple
Here's a good blog post that covers setting up products in more detail: Configuring In-app Products is Hard

why non-renewing subscription, shows "This In-App purchase has already been bought. It will be restored for free.", when purchasing item again?

I am implementing the non renewable purchase in my app. I am still using in sandbox mode. After I subscribe for the product, when I again try to subscribe the same product, it gives me an alert saying ‘This In-App purchase has already been bought. It will be restored for free.’. I don’t know how I should enable user to subscribe again.
How can I handle multiple user on same device? If one user has paid for the subscription and another user log in into same device to my application he/she should not get the alert as above.
Code :
import StoreKit
class className: SKProductsRequestDelegate
{
var productIDs: Array<String?> = []
var productsArray: Array<SKProduct?> = []
override func viewDidLoad(){
// product IDs for in-app purchase products
productIDs.append(“monthly_subscription_id”) // Monthly
productIDs.append(“yearly_subscription_id”) // Year
requestProductInfo()
SKPaymentQueue.default().add(self)
}
func requestProductInfo() {
if SKPaymentQueue.canMakePayments() {
let productIdentifiers = NSSet(array: productIDs)
let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers as Set<NSObject> as! Set<String>)
productRequest.delegate = self
productRequest.start()
}
else {
print("Cannot perform In App Purchases.")
}
}
// MARK: SKProductsRequestDelegate method implementation
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if response.products.count != 0 {
for product in response.products {
productsArray.append(product )
}
}
else {
print("There are no products.")
}
if response.invalidProductIdentifiers.count != 0 {
print(response.invalidProductIdentifiers.description)
}
}
// MARK: Buy Subscription button action
#IBAction func btn_purchase_Action(_ sender: Any){
let payment = SKPayment(product: self.productsArray[productIndex]!)
SKPaymentQueue.default().add(payment)
self.transactionInProgress = true
}
}
// MARK: - SKPaymentTransactionObserver
extension className: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
complete(transaction: transaction)
break
case .failed:
fail(transaction: transaction)
break
case .restored:
restore(transaction: transaction)
break
case .deferred:
break
case .purchasing:
break
}
}
}
private func complete(transaction: SKPaymentTransaction){
print("complete...")
SKPaymentQueue.default().finishTransaction(transaction)
}
private func restore(transaction: SKPaymentTransaction){
guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
print("restore... \(productIdentifier)")
SKPaymentQueue.default().finishTransaction(transaction)
}
private func fail(transaction: SKPaymentTransaction){
print("fail...")
if let transactionError = transaction.error as? NSError {
if transactionError.code != SKError.paymentCancelled.rawValue {
print("Transaction Error: \(transaction.error?.localizedDescription)")
}
}
SKPaymentQueue.default().finishTransaction(transaction)
}
}
I could see popup saying in-app purchase is successful, but "updatedTransaction" function is not called when i successfully finish in-app purchase process.
First time in-app purchase is completed but when i try to purchase the same product again it shows the alert that product is already purchased and could restore for free.
From your code it looks like your transaction observer is a view controller.
If the view controller is dismissed before the payment transaction has been processed then you won't get a chance to complete the transaction.
Your payment queue observer should be an object that is instantiated as soon as your app launches and remains in memory for the lifetime of your app.
Creating the payment queue observer in didFinishLaunching and holding a reference to it in your app delegate is one approach that you can use.

Swift In App Purchase Non Consumable

I am developing IAP functionality of non consumable (removing Ads).
I've created an helper for all the operation and everything works fine.
When a user buy/restore the purchase i set this:
let save = UserDefaults.standard
save.set(true, forKey: "Purchase")
save.synchronize()
This works fine as long as the user never delete the app.
I was wondering...
Is there a way to know if the user (after deleting and reinstalling the app) already had made a purchase? so to change the title of the button from "purchase" to "restore"?
For every app, Apple requires that you include a "Restore Purchases" button. This is exactly for the problem you are facing. It will recreate the SKPaymentTransaction for every previously purchased IAP with a "restored" state for the iCloud account that is currently signed in at the App Store, and put on the SKPaymentQueue. Read more...
Maybe it would make sense to have a receipt validation in your code, when the user is interacting in your app. (Apple docs: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Introduction.html)
Just have a look at this small example:
Init your product requests:
if SKPaymentQueue.canMakePayments() {
print("starting IAPS")
let productIdentifiers = Set(["YOUR_IAPP_IDENTIFIER#1", "YOUR_IAPP_IDENTIFIER#1"])
let request = SKProductsRequest(productIdentifiers: productIdentifiers as Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
Set your products in your app and do the receipt validation
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("product request")
let myProduct = response.products
for product in myProduct {
if product.productIdentifier == "YOUR_IAPP_IDENTIFIER#1" {
self.productYear = product
} else if product.productIdentifier == "YOUR_IAPP_IDENTIFIER#2" {
self.productMonth = product
}
print("product added")
print(product.productIdentifier)
print(product.localizedTitle)
print(product.localizedDescription)
print(product.price)
self.receiptValidation()
}
Validation example
func receiptValidation() {
self.logger.log.debug("In receipt validation")
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
do {
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
let receiptString = receiptData.base64EncodedString(options: [])
let dict = ["receipt-data" : receiptString, "password" : "YOUR_PASSWORD"] as [String : Any]
// Getting the receipt data from the JSON file and other logic
// ...
// ...
}
}
}
Please let me know, if you need more clarification on this. Furthermore you can simply restore it with the PaymentQueue (Apple Doc: https://developer.apple.com/documentation/storekit/skpaymentqueue)
If you provide a login system you will know easily , otherwise
no other way you should put buy and restore buttons for payment Or you store this key-value in
device key-chain and read them in first setup ,but you should know from ios 10.3 If you deleted the app the associated key-chain items will be deleted
Note: Apple states that all apps that have IAP should put a restore payment functionality so, If there is a workaround app will be rejected by Apple

Swift - Xcode not finding in app purchase

I am trying to implement a removing ads in app purchase into my application. However, whenever I test it out, it keeps telling me it has not found the product ID. I have triple checked the product ID, and it is correct. I can't seem to figure out the problem. This is what prints to the console:
Product not found: com.myCoolAwesomeApp.mjay.noAds
var product: SKProduct?
var productID = "com.myCoolAwesomeApp.mjay.noAds"
func getPurchaseInfo() {
if SKPaymentQueue.canMakePayments() {
let request = SKProductsRequest(productIdentifiers: NSSet(objects: self.productID) as! Set<String>)
request.delegate = self
request.start()
} else {
productDescription.text = "Please enable In App Purchases in your settings."
}
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
var products = response.products
if (products.count) == 0 {
productDescription.text = "Product not found."
} else {
product = products[0]
productDescription.text = product!.localizedDescription
buyButton.isEnabled = true
}
let invalids = response.invalidProductIdentifiers
for product in invalids {
print("Product not found: \(product)")
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case SKPaymentTransactionState.purchased:
SKPaymentQueue.default().finishTransaction(transaction)
productDescription.text = "Purchase Successful"
buyButton.isEnabled = false
let save = UserDefaults.standard
save.set(true, forKey: "Purchase")
save.synchronize()
case SKPaymentTransactionState.failed:
SKPaymentQueue.default().finishTransaction(transaction)
productDescription.text = "Purchase Failed. Try again later."
default:
break
}
}
}
}
I run into the exact similar problem. Here are some steps that needs to be adressed/checked after reading this technical note from Apple:
Create a unique App ID
Enable in-app purchasing for this App ID
Generate and install a new provisioning profile on your device
Update the bundle ID and code signing profile in Xcode
Check that your project’s .plist Bundle ID match your App ID
Complete your contract, tax, and banking Information. You need a VALID contract
As mentioned by the official doc there is NO need to submit binaries neither submit screenshot of in-app purchase. I have seen a lot of missleading info about this on various blogs.
After checking you have addressed each point in this list, delete your app and reinstall it.
I was basically missing two things on my side: setting up correctly my contract for ios Paid app and i didn't install the new provisioning profile on my device. May be that was your issue?

Resources