Rejection by Apple: Guideline 3.1.1 - In-App Purchase ...

I got an app rejection from Apple because of this:


Guideline 3.1.1 - In-App Purchase We noticed that your app contains a payment mechanism other than in-app purchase for digital content or to unlock features or functionality within your app, which is not appropriate for the App Store. In-app purchase is the only valid in-app payment mechanism for digital content.

Note: Continuing to hide functionality within your app or other dishonest acts may result in the removal of your apps from the App Store and termination of your Apple Developer Program membership and all associated memberships.

Next Steps

To resolve this issue, please remove all external or third-party payment mechanisms and implement in-app purchase to facilitate digital good transactions, including unlocking features or functionality within your app.

If you believe your use of an alternative payment mechanism is a permissible use case, please respond directly to this message in Resolution Center with detailed information.


The thing is we only included InApp-purchases. The submitted app had one InApp purchase in it’s first version which is on sale. Then we added two more and submitted the update which got rejected. But we only are using InApp-purchases in the game and nothing else.

Does anyone know about this, had the same rejection maybe and can give some infos what can cause this?

Thx for your help!

Same problem with my app!

Apple rejected my update where I only push some bug fixes (no change to my single and only IAP).

I thought it was a mistake on their side (still waiting for a response after 48h…).

@d.mach,

Do you use 'IAP Badger" to handle your in-app purchase too ?

Maybe the plugin was updated with some bad code detected by Apple review process.

Hope this get fixed quickly!

(I’ll post the response from Apple if I get any)

Thx for your reply. I’m not using “IAP Badger”… just checking if the device is giving back “Android” “Amazon” and if not loading the Apple App Store stuff. Our tests (on device) did work fine.

Thanks for the info.

If it is not ‘IAP Badger’, I guess the problem come from Apple review process or Corona SDK then (using build 2017.3160 btw).

The same version of my app that got rejected by Apple for release was validated for beta.

I tested it and everything works just fine too… So it might be something in the core code

that got detected as “another form of payment”, idk.

Does anybody else got the same issue ?

Any more info from Corona team maybe ?

The best thing for you to do, if you are only using Apple IAP and no other payment systems is to write back to your reviewer and have them explain what is wrong and that you’re only using IAP items.

Without any details, there isn’t anything to go on.

Rob

Thanks @Rob,

So no special change on iOS IAP from Corona I guess.

Waiting for Apple reply then… (I’ll keep you updated)

I did exactly what Rob is suggesting and got back this:

Hello,

Thank you for your reply. Our team has confirmed this app contains non Apple payment methods.

To continue review, it would be appropriate to remove all non Apple payment methods, then resubmit a new binary for review.

Note: Continuing to hide functionality within your app or other dishonest acts may result in the removal of your apps from the App Store and termination of your Apple Developer Program membership and all associated memberships.

Thank you,

App Store Review

Can you post your build.settings?

settings = { splashScreen = { ios = { enable = false, image = "images/splashscreen.jpg" }, android = { enable = false, image = "images/splashscreen.jpg" } }, orientation = { default = "landscapeRight", supported = { "landscapeRight","landscapeLeft" }, }, android = { versionCode="10", versionName="1.0", UIStatusBarHidden = true, usesPermissions = { "com.android.vending.BILLING", "com.android.vending.CHECK\_LICENSE", "android.permission.INTERNET", --"android.permission.CAMERA", "android.permission.WRITE\_EXTERNAL\_STORAGE", "android.permission.ACCESS\_NETWORK\_STATE", "android.permission.READ\_PHONE\_STATE", }, }, iphone = { plist = { MinimumOSVersion = "7.0", NSPhotoLibraryUsageDescription = "This app would like to access the photo library.", NSPhotoLibraryAddUsageDescription = "This app would like to add the photo library.", UIStatusBarHidden=true, skipPNGCrush = true, UILaunchStoryboardName = "LaunchScreen", CFBundleIconFile = "Icon.png", CFBundleIconFiles = { "Icon.png", "Icon@2x.png", "Icon-60.png", "Icon-60@2x.png", "Icon-60@3x.png", "Icon-72.png", "Icon-72@2x.png", "Icon-76.png", "Icon-76@2x.png", "Icon-167.png", "Icon-Small-40.png", "Icon-Small-40@2x.png", "Icon-Small-40@3x.png", "Icon-Small-50.png", "Icon-Small-50@2x.png", "Icon-Small.png", "Icon-Small@2x.png", "Icon-Small@3x.png", }, --launch image files table UILaunchImages = { { -- iPhone 4 Portrait ["UILaunchImageMinimumOSVersion"] = "7.0", ["UILaunchImageName"] = "Default", ["UILaunchImageOrientation"] = "Portrait", ["UILaunchImageSize"] = "{320, 480}" }, { -- iPhone 4 LandscapeLeft ["UILaunchImageMinimumOSVersion"] = "7.0", ["UILaunchImageName"] = "Default", ["UILaunchImageOrientation"] = "LandscapeLeft", ["UILaunchImageSize"] = "{320, 480}" }, { -- iPhone 4 LandscapeRight ["UILaunchImageMinimumOSVersion"] = "7.0", ["UILaunchImageName"] = "Default", ["UILaunchImageOrientation"] = "LandscapeRight", ["UILaunchImageSize"] = "{320, 480}" }, { -- iPhone 5 Portrait ["UILaunchImageMinimumOSVersion"] = "7.0", ["UILaunchImageName"] = "Default-568h", ["UILaunchImageOrientation"] = "Portrait", ["UILaunchImageSize"] = "{320, 568}" }, { -- iPhone 5 LandscapeLeft ["UILaunchImageMinimumOSVersion"] = "7.0", ["UILaunchImageName"] = "Default-568h", ["UILaunchImageOrientation"] = "LandscapeLeft", ["UILaunchImageSize"] = "{320, 568}" }, { -- iPhone 5 LandscapeRight ["UILaunchImageMinimumOSVersion"] = "7.0", ["UILaunchImageName"] = "Default-568h", ["UILaunchImageOrientation"] = "LandscapeRight", ["UILaunchImageSize"] = "{320, 568}" }, { -- iPad Portrait ["UILaunchImageMinimumOSVersion"] = "7.0", ["UILaunchImageName"] = "Default-Portrait", ["UILaunchImageOrientation"] = "Portrait", ["UILaunchImageSize"] = "{768, 1024}" }, { -- iPad LandscapeLeft ["UILaunchImageMinimumOSVersion"] = "7.0", ["UILaunchImageName"] = "Default-Landscape", ["UILaunchImageOrientation"] = "LandscapeLeft", ["UILaunchImageSize"] = "{768, 1024}" }, { -- iPad LandscapeRight ["UILaunchImageMinimumOSVersion"] = "7.0", ["UILaunchImageName"] = "Default-Landscape", ["UILaunchImageOrientation"] = "LandscapeRight", ["UILaunchImageSize"] = "{768, 1024}" }, { -- iPhone 6 Portrait ["UILaunchImageMinimumOSVersion"] = "8.0", ["UILaunchImageName"] = "Default-667h", ["UILaunchImageOrientation"] = "Portrait", ["UILaunchImageSize"] = "{375, 667}" }, { -- iPhone 6 LandscapeLeft ["UILaunchImageMinimumOSVersion"] = "8.0", ["UILaunchImageName"] = "Default-667h", ["UILaunchImageOrientation"] = "LandscapeLeft", ["UILaunchImageSize"] = "{375, 667}" }, { -- iPhone 6 LandscapeRight ["UILaunchImageMinimumOSVersion"] = "8.0", ["UILaunchImageName"] = "Default-667h", ["UILaunchImageOrientation"] = "LandscapeRight", ["UILaunchImageSize"] = "{375, 667}" }, { -- iPhone 6 Plus Portrait ["UILaunchImageMinimumOSVersion"] = "8.0", ["UILaunchImageName"] = "Default-736h", ["UILaunchImageOrientation"] = "Portrait", ["UILaunchImageSize"] = "{414, 736}" }, { -- iPhone 6 Plus LandscapeLeft ["UILaunchImageMinimumOSVersion"] = "8.0", ["UILaunchImageName"] = "Default-Landscape-736h", ["UILaunchImageOrientation"] = "LandscapeLeft", ["UILaunchImageSize"] = "{414, 736}" }, { -- iPhone 6 Plus LandscapeRight ["UILaunchImageMinimumOSVersion"] = "8.0", ["UILaunchImageName"] = "Default-Landscape-736h", ["UILaunchImageOrientation"] = "LandscapeRight", ["UILaunchImageSize"] = "{414, 736}" }, { -- iPad Pro Portrait ["UILaunchImageMinimumOSVersion"] = "9.0", ["UILaunchImageName"] = "Default-Portrait-1336", ["UILaunchImageOrientation"] = "Portrait", ["UILaunchImageSize"] = "{1024, 1366}" }, { -- iPad Pro Landscape Right ["UILaunchImageMinimumOSVersion"] = "9.0", ["UILaunchImageName"] = "Default-Landscape-1336", ["UILaunchImageOrientation"] = "LandscapeRight", ["UILaunchImageSize"] = "{1024, 1366}" }, { -- iPad Pro Landscape Left ["UILaunchImageMinimumOSVersion"] = "9.0", ["UILaunchImageName"] = "Default-Landscape-1336", ["UILaunchImageOrientation"] = "LandscapeLeft", ["UILaunchImageSize"] = "{1024, 1366}" }, }, CFBundleLocalizations = { "English", "German", "French", "Spanish", "Italian", "Norwegian", "Dutch", "Danish", "Swedish", "Finnish", "Russian", "Portuguese", "Turkish", "Japanese", "Chinese", "Korean", "Vietnamese", "Czech", "Polish", "Hindi" }, UIPrerenderedIcon = true, UIApplicationExitsOnSuspend = false, CFBundleDisplayName = "NAME", CFBundleName = "xxx", CFBundleIdentifier = "xxx", CFBundleVersion = "1.3", -- Version Code CFBundleShortVersionString = "1.4", -- Version Name }, }, plugins = { -- key is the name passed to Lua's 'require()' ["CoronaProvider.analytics.flurry"] = { publisherId = "com.coronalabs" }, --[[] ["plugin.google.iap.v3"] = { publisherId = "com.coronalabs", supportedPlatforms = { android=true } }, ["plugin.google.play.services"] = { publisherId = "com.coronalabs" }, --]] }, }

You are commenting out the Google IAP which shouldn’t be included anyway in an iOS build, so I’m confused by this. I’ll ask engineering.

On an unrelated note. I don’t recommend setting these in your build.settings:

CFBundleDisplayName = “NAME”,
CFBundleName = “xxx”,
CFBundleIdentifier = “xxx”,
CFBundleVersion = “1.3”, – Version Code
CFBundleShortVersionString = “1.4”, – Version Name

You should let Corona provide values for them which most are taken from the “Build” dialog. It’s too easy for a change to get missed and always having to keep the version’s up to date when we provide a safe, form-based way to manage these.

Rob

If it can help, here is my build settings:

Note: didn’t change since last Apple validation

settings = { orientation = { default = "landscapeRight", supported = { "landscapeRight", "landscapeLeft" } }, android = { usesPermissions = { "android.permission.INTERNET", "android.permission.ACCESS\_NETWORK\_STATE", --"android.permission.WRITE\_EXTERNAL\_STORAGE", "com.android.vending.BILLING", "com.android.vending.CHECK\_LICENSE", }, googlePlayGamesAppId = "XXXXXXXXXXXX", -- hidden --largeHeap = true }, iphone = { iCloud = true, xcassets = "Images.xcassets", plist = { MinimumOSVersion = "8.0", NSAppTransportSecurity = { NSAllowsArbitraryLoads = true }, UILaunchStoryboardName = "LaunchScreen", UIStatusBarHidden = true, UIPrerenderedIcon = true, -- set to false for "shine" overlay CFBundleDisplayName = "Switchero", CFBundleName = "Switchero" } }, splashScreen = { enable = false }, plugins = { ["CoronaProvider.native.popup.social"] = { publisherId = "com.coronalabs", supportedPlatforms = { android = true } }, ["CoronaProvider.native.popup.activity"] = { publisherId = "com.coronalabs", supportedPlatforms = { iphone = true } }, ["plugin.appodeal"] = { publisherId = "com.coronalabs" }, ["plugin.google.iap.v3"] = { publisherId = "com.coronalabs", supportedPlatforms = { android = true } }, ["plugin.iap\_badger"] = { publisherId = "uk.co.happymongoose" }, ["plugin.gpgs"] = { publisherId = "com.coronalabs", supportedPlatforms = { android=true } }, ["CoronaProvider.gameNetwork.apple"] = { publisherId = "com.coronalabs", supportedPlatforms = { iphone=true } }, ["plugin.iCloud"] = { publisherId = "com.coronalabs", supportedPlatforms = { iphone=true } } }, excludeFiles = { all = { 'Icon.png', 'Images.xcassets' }, ios = { 'Icon-\*dpi.png', 'audio/musics/\*.ogg', 'audio/sounds/\*.ogg' }, android = { 'LaunchScreen.storyboardc', 'audio/musics/\*.m4a', 'audio/sounds/\*.m4a' }, win32 = { 'Icon\*.png', 'Icon\*.icns', 'audio/musics/\*.ogg', 'audio/sounds/\*.ogg' }, macos = { 'Icon\*.png', 'Icon\*.ico', 'audio/musics/\*.ogg', 'audio/sounds/\*.ogg' } }, }

Thx Rob!

Can you please explain this a little bit more? Where do I change the Version Code or Version Name if not including it like this then?

One of the two fields comes from the build dialog:

The “Version” provides one of the two values. The other, which is required to be unique, we make up a safe, unique value. The field from the dialog is the visual version to the user in the store.

Other values are pulled from the Application Name and the provisioning profile.  As long as you update your public version number in the build dialog, this minimizes the work you have to do.

Rob

Regarding the problem I noticed we have called the purchase like this in the code:

if system.getInfo("platformName") == "Android" then                             \_G.store.purchase(product)                         else                             \_G.store.purchase(product)                         end

but it should be this:

if system.getInfo("platformName") == "Android" then                             \_G.store.purchase(product)                         else                             -- Apple                             \_G.store.purchase({product})                         end

As you can see the brackets for the Apple purchase were missing. Could this maybe cause the problem?

We tested the first code on device and everything worked fine, so we missed this.

What do you think?

@d.mach: no, I don’t believe it is. I recently (about a month ago) changed my iap code and removed the braces and it is working fine on both ios and android. The docs also has them without braces now. So, as said, i don’t believe it is.

However, i am preparing for an ios update next week. To avoid a similar rejection, i followed @rob’s advice and created two separate build.settings files (one for each platform). The one for ios excludes the google iap plugin and the amazon iap plugin. I know it is a workaround and not a solution. I also believe it is against the nature of corona (one code base for all platforms). But what can we do?!

We have changed store.purchase() to be smart and if a table is needed, convert it to a table or if a table is provided and the store wants a single item pass the first table item as a string. So that shouldn’t matter.

There isn’t any reason that Apple should be detecting any other billing. We simply can’t include anything Google/Android with an iOS build.  

I’ll bring this up with Engineering to see if they have any thoughts.

Rob

@luaykanaan

Thanks for sharing the info.

Do you mean you had a rejected app validated just by changing the build settings ?

If so, it could be a nice workaround waiting for the fix :slight_smile:

@rob

Thanks for the follow up.

I thought too it might not be related to other IAP stores being included

(especially with ‘supportedPlatforms = { android=true }’ defined).

Hope Engineering will have some insight on this.

PS: still waiting for a reply from Apple on my side…

Apple seems to have issues. This is impacting many native developers too:

https://forums.developer.apple.com/thread/78759

And Apple isn’t being very responsive about it.

Rob

What the…?

This looks bad!

@GoG,

No. I have a planned “submit” at the end of this week. To avoid getting a rejection, I split my build.settings. However, it seems i have mis-understood @rob. I thought he said we should have a separate build.settings for each platform but according to 4 posts up it doesn’t matter.