In-app Purchases from iMessage app extension - ios

I created an app that uses in-app purchases, I already have set up the in-app purchases on iTunes and I even tested it using a sandbox user and all of this is working perfectly.
now that I included an iMessage app extension, the same in-app purchase products return invalidProduct Id, but continues working well inside the main app.
could be because the bundle id?
mainapp: com.whatever.bundleid
app iMessage: com.whatever.bundleid.iMessage
iMessage extension: com.whatever.bundleid.iMessage.MessagesExtension
btw, i'm using the SwiftyStoreKit pod.
if let inappIndentifier = stickerPackage.inAppPurchaseId {
SwiftyStoreKit.retrieveProductsInfo([inappIndentifier]) { result in
if let product = result.retrievedProducts.first {
let priceString = product.localizedPrice!
completion(true, priceString)
} else if let _ = result.invalidProductIDs.first {
completion(false, nil)
} else {
print("Error: \(result.error)")
completion(false, nil)
}
}
}

I made a huge mistake, instead of creating an iMessage extension, I've created an iMessage app extension.
Once I removed the "iMessage app" the in-app purchases started working correctly.

Related

How can I get IAP receipt If user uninstall the app and install it again? SWIFT

If I purchase something and check the purchase receipt I can get the encoded value of it by this:
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
do {
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
print(receiptData)
let receiptString = receiptData.base64EncodedString(options: [])
print("receiptString \(receiptString)")
// Read receiptData
}
catch { print("Couldn't read receipt data with error: " + error.localizedDescription) }
}
But If I purchase something, then delete the app and reinstall it again and check the receipt, I can't get anything with the upper code. It just get the appStoreReceiptURL and go straight away without entering do {} catch. How can I get the receipt in this scenario?
If you attempt to restore purchase , the user will not be refunded the purchased amount, however you would be able to verify that user purchased the product, and will also receive receipt that contants all the information about his previous purchase, therefore you can safely verify the purchase in your App.
In App Store Review Guides is clearly stated that
Any credits or in-game currencies purchased via in-app purchase may
not expire, and you should make sure you have a restore mechanism for
any restorable in-app purchases.
That means you should provide some mechanism (e.g. Restore Purchase Button) anyway, regardless of scenario when the user uninstalls Application. In your case, you can provide the Restore button directly on your Login screen. Check some inspiration on Restore purchase button in Apple Human Interface guidelines.
I you want to restore purchase directly with Apple API after user taps the Restore Purchase button, you can use:
SKPaymentQueue.default().restoreCompletedTransactions()
,which triggers Apple servers, checks the users Apple ID and gets all of the app purchases they have made before and returns you transaction that you observe with SKPaymentTransactionObserver with this code:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
if transaction.transactionState == .restored {
//here is your restored transaction
}
}
}
If you however want to get hold of the new receipt with all receipt data, including previous purchases, I recommend not using directly Apple API, but using SwiftyStoreKit library : link on github . Code example below:
func restoreYourPurchases( completion: #escaping (Result<Bool,Error>) -> Void) {
SwiftyStoreKit.restorePurchases(atomically: true) { results in
var restoreSuccessfull = false
if results.restoreFailedPurchases.count > 0 {
print("Restore Failed: \(results.restoreFailedPurchases)")
}
else if results.restoredPurchases.count > 0 {
print("Restore Success: \(results.restoredPurchases)")
for purchase in results.restoredPurchases {
if purchase.productId == "yourID" {
let timeOfPurchase = purchase.originalPurchaseDate
}
}
}
else {
print("Nothing to Restore")
completion(.success(false))
}
}
}

presentCodeRedemptionSheet() is not working

I am creating offer codes with auto-renewable In-app purchases in swift. But when i use presentCodeRedemptionSheet(), it does not show any redemption page to redeem offer code. I am using below code, but nothing is happening on tapping the button.
let paymentQueue = SKPaymentQueue.default()
if #available(iOS 14.0, *) {
DispatchQueue.main.async {
paymentQueue.presentCodeRedemptionSheet()
}
}
Are you trying on the simulator?
This command only works on a real device not in the simulator.

In app purchase testing in iOS

I am using Xcode 8.0, Swift 3.0 and testing in app purchases in my iPad. I want to test in app purchases using sandbox user.
There is no account added in device's Setting
The Problem is I am not getting product list in response of product request code.
Please take a look on my code:
let PRODUCT_ID_MY_PRODUCT = "com.company.ProjectName.MyProduct"
// The ProducID in this code and ProducID on iTunes are the SAME. ✔️
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if productID == nil {
productID = PRODUCT_ID_MY_PRODUCT
}
SKPaymentQueue.default().add(self)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
startPurchases()
}
func startPurchases() {
if (SKPaymentQueue.canMakePayments())
{
let productIDs = NSSet(object: self.productID!)
let productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productIDs as! Set<String>)
productsRequest.delegate = self
productsRequest.start()
}
}
// Delegate Methods for SKProductsRequest
func productsRequest (_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let count : Int = response.products.count
// THE PROBLEM IS HERE.. I AM GETTING COUNT IS ZERO.. MEANS response.products returning null ARRAY
if (count>0) {
let validProducts = response.products
for aProduct in validProducts {
print(aProduct.productIdentifier)
}
} else {
DispatchQueue.main.async(execute: {
UIAlertView(title: "Purchase !", message: "Product not available", delegate: nil, cancelButtonTitle: "OK").show()
return
})
}
}
So..... That's the problem: I am getting response.products null (no data in array) so Please help me to find the solution. You can see the comments in code:
// THE PROBLEM IS HERE.. I AM GETTING COUNT IS ZERO.. MEANS response.products returning null ARRAY
I created products over iTunes Connect. You can see the image below. All the products are in "Ready to Submit" state.
There is some warning on iTunes
Your first In-App Purchase must be submitted with a new app version.
Select it from the app’s In-App Purchases section and click Submit.
Once your binary has been uploaded and your first In-App Purchase
has been submitted for review, additional In-App Purchases can be
submitted using the table below.
And
I also created Sendbox user for testing In-App Purchases. See the image below:
Did I miss something? Or what is the error? And where is error? I want to test in app purchases using sandbox user
I fixed this. There are some points need to careful about. See below:
Make sure your developer account did the paid application contract. see image below:
Create Products on the iTunes Connect.
Implement In-App-Purchases code and configuration settings.
Create one build with Distribution profile.
Upload build on store. Add build to current version. Add In-App-Purchases to the version on iTunes Connect.
Then try to test, if still not then submit app once then cancel it. then after try to test on your device.
Make sure when you test with sandbox user, you need to sign-out from your already logged in account from device settings, and login with sandbox ID.
some screenshots might be helpful.
please check these settings
capabilities --> In-App purchase --> set to "ON"
and at developer.apple.com--> enable In-App purchase for App ID.
and please test app on Device instead of simulator.

Call from App Extension (iMessage)

I'm working on iMessage app extension for my app and i was wondering if its possible to initiate a phone call from App Extension?
I tried using the following code but it takes me (deeplink) to containing app.
cell.didTapCallNowButton = { cell in
if let phoneNumber = cell.Model.phone,
let url = URL(string: "telprompt:\(phone)") {
self.extensionContext?.open(url, completionHandler: nil)
}
}
Here is the answer from pdm 'Apple Staff' on Apple developer forum:
No. iMessage apps can only open URLs in their parent app.
https://forums.developer.apple.com/message/174112#174112

In-App Purchase Works on iPhone but not iPad

I'm having an issue with In-App purchases on iOS. I have 5 In-App Purchases in a game, all of which work exactly as expected when tested on an iPhone 6 (iOS 8.3). When I go to test on iPad Air 2 (iOS 8.2), all IAPs fail immediately. Has anyone else experienced this problem? Is there some code that is specific to iPad that I have to add?
EDIT: Strangely, updating the iPad to iOS 8.3 fixed the problem. Any ideas as to why this issue is occurring? Should I change my app to only support iOS 8.3 and above?
To test the app, I'm using TestFlight, the same network connection, and the same Apple ID.
The code I'm using for In-App Purchases is Below:
func inApp() {
if (SKPaymentQueue.canMakePayments())
{
var productID:NSSet = NSSet(object: product_id);
var productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as Set<NSObject>);
productsRequest.delegate = self;
productsRequest.start();
}else{
displayAlert()
}
}
func buyProduct(product: SKProduct){
var payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment);
}
func productsRequest (request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
var count : Int = response.products.count
if (count>0) {
var validProducts = response.products
var validProduct: SKProduct = response.products[0] as! SKProduct
if (validProduct.productIdentifier == product_id) {
println(validProduct.localizedTitle)
println(validProduct.localizedDescription)
println(validProduct.price)
buyProduct(validProduct);
} else {
println(validProduct.productIdentifier)
}
} else {
displayAlert()
}
}
func request(request: SKRequest!, didFailWithError error: NSError!) {
self.displayAlert()
}
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
for transaction:AnyObject in transactions {
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction{
switch trans.transactionState {
case .Purchased:
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
if product_id == "com.shv.FrenzyTenLives" {
defaults.setInteger(10, forKey: "totalLives")
} else if product_id == "com.shv.FrenzyFiveLives" {
defaults.setInteger(5, forKey: "totalLives")
} else if product_id == "com.shv.FrenzyInfiniteLives" {
defaults.setBool(true, forKey: "infiniteLives")
} else if product_id == "com.shv.FrenzyShield" {
defaults.setInteger(5, forKey: "shieldValue")
} else if product_id == "com.shv.FrenzyRemoveAds" {
defaults.setBool(true, forKey: "adsRemoved")
adBanner.hidden = true
}
break;
case .Failed:
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
self.displayAlert()
break;
case .Restored:
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
if product_id == "com.shv.FrenzyTenLives" {
defaults.setInteger(10, forKey: "totalLives")
} else if product_id == "com.shv.FrenzyFiveLives" {
defaults.setInteger(5, forKey: "totalLives")
} else if product_id == "com.shv.FrenzyInfiniteLives" {
defaults.setBool(true, forKey: "infiniteLives")
} else if product_id == "com.shv.FrenzyRemoveAds" {
defaults.setBool(true, forKey: "adsRemoved")
adBanner.hidden = true
}
break;
default:
break;
}
}
}
}
Usually, when one device will not perform in app purchase, it's a setting under restrictions.
If you've checked and disabled that restriction or all restrictions - you can often fix this by signing out of the App Store entirely and powering down the device. When it starts cleanly, you can log in again. When you sign in, be sure to buy something (a free song of the week, a free app or even a paid app) to get through the verification questions. Once that's done, recheck an IAP (in-app purchase) to be sure things are functional.
As a last resort, you might need to contact Apple Support for Apple ID - but most times you can fix this without needing their help.
You should check the following things.
Make sure you can answer “Yes” to each of these questions:
Have you enabled In-App Purchases for your App ID?
Have you checked Cleared for Sale for your product?
Does your project’s .plist Bundle ID match your App ID?
Have you generated and installed a new provisioning profile for the new App ID?
Have you configured your project to code sign using this new provisioning profile?
Are you using the full product ID when when making an SKProductRequest?
Have you waited several hours since adding your product to iTunes Connect?
Have you tried deleting the app from your device and reinstalling?
Is your device jailbroken? If so, you need to revert the jailbreak for IAP to work.
If you answered “No” to any one of these questions, there’s your problem.
You should visit following links definitely you will get solution.
https://www.innofied.com/in-app-purchase-working-ios-solution/
Without a definition of "fail immediately" my best guess is that you were not logged in to a valid sandbox testing account on the iPad and in the midst of updating the iOS version this got reconciled, which is why it works on 8.3.
The easiest way to get IAP testing to work is to log out of iTunes:
Settings -> App and iTunes Store -> Tap apple ID and log out.
Once you've logged out, try to make an IAP and you will be prompted to log in - once you enter valid test account credentials, the IAP will download - if it doesn't, post your log files here. Note that you can find or create testing accounts inside iTunes Connect.
IAP is an extremely complicated subject with numerous points of failure - knowing that your IAPs work on one device but not another points to a configuration issue specific to the device on which the IAP failed.

Resources