I think this is good, removing this feature, means we cannot obsessively check every hour how our apps are doing and instead focus on building apps and other mentally healthier alternatives! :)
Post
Replies
Boosts
Views
Activity
So now I have around $2K that was held back from this month's App Store payment, it is "balance carried forward" to next month, and from googling it looks like this is likely due to this cycle of refunds occurring, all from this one guy which triggers an investigation by Apple.
Fortunately it now looks like they are paying that balance out on Oct 31, 2024 (which is not a normal payment date), so I assume they investigated, saw what happened and approved the payment.
A week after we disabled subscriptions in Poland, the guy emailed again, in Polish, translated as "Why can't I buy a subscription, please, I promise I won't cancel it again"
sigh, guess it really was one guy...definitely not enabling it again for Poland if the system allows one individual to keep buying and refunding over and over again! Not only did it temporarily kill my app's numbers (they've bounced back again after the refunds stopped) but caused a delayed payment on top of it! All due to one guy in Poland!
Was surprised today when I saw Poland in our list of top 5 countries for total downloads, switched it to first time downloads and it dropped from 28 to 2. Further confirmation to me that all of the refunds are from the same person who apparently keeps deleting and redownloading the app too.
Hi Kevin,
Thanks for responding! Just realized I forgot to specify this is for iOS and iPadOS. Has happened on multiple versions of iOS 17.
I do not currently know what the error is but am going to add some code that sends my server the error message when it occurs.
The shared files are typically from the Files app, if not there, then at least from the local device, not iCloud or any cloud/network storage.
They arrive as "file://Documents/Inbox/filename", then the file attributes are obtained, then the file is moved to the application support directory. I just realized as I wrote this that there is then a pause as the user has to hit upload before the file is read using
try Data(contentsOf: )
For most users the time delay is minimal, share file, select our app from the list, see our app's share screen and hit upload button immediately, but perhaps this delay has something to do with it. All of this code is synchronous so the move should finish before the user is even shown the button.
I'm checking if creationTime: Date and modificationTime: Date == Date(timeIntervalSince1970: 0), what I set the values to beforehand, to check for failure.
Thanks!
Colin
Hi Kevin, thanks for responding.
Just unexpected that iCloud would be tied to Sign in with Apple. Clients have iCloud disabled via MDM profile to ensure corporate data doesn't leave device to areas their corporation doesn't control/have visibility of, but they did not intend to disable Sign in with Apple as well but don't see a way to not do so when they disable iCloud.
Is iCloud somehow directly tied to Sign in with Apple? Even with iCloud disabled, these users are able to sign in to their Apple ID, buy apps and subscriptions with that ID, etc.
In the end I was paid $1,259 Canadian versus the estimated $1,203 Canadian versus what I expected from the trend proceeds #s which indicated around $1,668 Canadian.
So still wondering about the "missing" $400.
Thanks for your response! I was pondering that too, especially if they were only finding some apps, but they can seem to find all apps and the app icons as well. Even for rather obscure apps for which there's no way they would have done the research to figure out the URL schemes for and of course many (most?) apps don't define a scheme. Them having the app icon too and instantly (no server download latency) tells me they're getting it on device somehow.
Of course I found the answer myself a minute after posting even though I did searches beforehand, see below if you have the same question.
https://download.developer.apple.com/visionOS/visionOS_Logs/Sysdiagnose_Logging_Instructions.pdf
Also getting the following errors when URLSession is called, the device is online though and as stated before, everything works in Safari and URLSession for api4.ipify.org is working and api64 doesn't throw an error but returns the wrong result.
nw_endpoint_copy_association_with_evaluator nw_context_caches_are_shared(context (<nw_context com.apple.CFNetwork.NSURLSession.{9A54EA8A-B2C0-4DC0-A798-D53DA91606CA}{(null)}{Y}{2}{0x0} (private)>), endpoint->context (<nw_context Default Network Context (private)>)) is false
quic_protector_key_update unsupported TLS ciphersuite: 0
Connection 4: received failure notification
Connection 4: failed to connect 1:50, reason 18,446,744,073,709,551,615
Connection 4: encountered error(1:50)
Task .<1> HTTP load failed, 0/0 bytes (error code: 18,446,744,073,709,550,607 [1:50])
Task .<1> finished with error [18,446,744,073,709,550,607] Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={_kCFStreamErrorCodeKey=50, NSUnderlyingError=0x2834909c0 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_NSURLErrorNWPathKey=satisfied (Path is satisfied), interface: en0[802.11], ipv4, dns, _kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask .<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask .<1>"
), NSLocalizedDescription=The Internet connection appears to be offline., NSErrorFailingURLStringKey=https://api6.ipify.org/, NSErrorFailingURLKey=https://api6.ipify.org/, _kCFStreamErrorDomainKey=1}
So now my above polling solution is no longer working on at least iOS 15.7.6 and I am back to where I started. Zero code changes, simply testing the same code base and boom, out of nowhere, buy button no longer initiates the pop up confirmation dialog. It was working on 15.7.6 up until a day or two ago, so the problem was not introduced by the new version. Tried rebooting, app delete and reinstall, etc.
iOS 16.5 (same app code base, no different code paths between iOS versions) continues to work just fine, guess I'm just dropping support for iOS 15 and keeping my fingers crossed that 16 continues working. Or maybe this is an AppStore sandbox issue and has nothing to do with my app.
I put in a paid support request for this, the answer is there is no way to remove it, it began being added I assume in Xcode 14. I guess this explains why older non-updated apps on my device (ones I did not develop) do NOT have it but newer ones (or apps with newer versions) do.
Originally I used an example implementation at a Swift tutorial web site, replacing the store classes from that with the ones in Apple’s StoreKit 2 tutorial ( https://developer.apple.com/documentation/storekit/in-app_purchase/implementing_a_store_in_your_app_using_the_storekit_api ) and now the call to purchase() not popping a dialog is resolved. I’ve gone through the original code I used and this new code and cannot see anything that would make a difference but it works so I’m sticking with it. Perhaps it coincided with an OS update or something else and it’s just a coincidence it works now since I left it unresolved for awhile before coming back to work on it and didn’t bother retesting that the old code still failed.
Only issue now is it’s working except when the subscription expires, the listener isn’t always fired right away (including in Test Flight) so I am now polling the current entitlements list every hour to check if an expiry occurred. Note if you do polling like this you need to account for your app being backgrounded and make sure you’re checking every X minutes based on wall clock time, not app run time, otherwise you might not catch an expiry until way afterwards.
Switching products that I am attempting to purchase doesn't solve it either for me, still returns success & verified showing older times I bought when testing, which have expired as well.
If I go in to AppStore -> Sandbox users and successfully buy a new subscription (which results in currentEntitlements now having valid entries as one would expect), the call to purchase() for that product (as if trying to buy it again during a valid subscription) returns (again without a dialog box popping up) showing a new expiry date in the future, as one would expect from a successful transaction.
I'm guessing this is some sandbox weirdness.
I'll be sending it to TestFlight soon to try if it works there, was hoping to sort it out before submitting!
I forgot to add in my original post, the call to purchase() (which is NOT popping a confirmation to purchase dialog) is returning a successful and verified result....but, the expiry date on the transaction is in the past....
Note: I've XXXX'd out some fields that shouldn't be relevant. Ran this at 6:12 p.m (GMT/UTC) on January 13, 2023, so should be expired....which would explain why later on, in self.updatePurchasedProducts(), Transactions.currentEntitlements is empty. But still no idea why there's a successful transaction and no confirmation dialog in the first place!
{
"bundleId" : "app.XXXXXXXXXXXXXXXXXX",
"deviceVerification" : "Y+Jlapp.XXXXXXXXXXXXXXXXXXJnwgU0qV7YMP6x\/I",
"deviceVerificationNonce" : "42e5c641-2a67-4815-b162-3c15515028ea",
"environment" : "Sandbox",
"expiresDate" : 1673556592000,
"inAppOwnershipType" : "PURCHASED",
"originalPurchaseDate" : 1673369053000,
"originalTransactionId" : "2000000246509209",
"productId" : "XXXXXXXXXXXXXXX",
"purchaseDate" : 1673554432000,
"quantity" : 1,
"signedDate" : 1673559083181,
"subscriptionGroupIdentifier" : "21XXXXXXXXXXXXXXXXXX3",
"transactionId" : "2000000248833618",
"type" : "Auto-Renewable Subscription",
"webOrderLineItemId" : "2000XXXXXXXXXXXXXXXXXX7"
}
Original Purchase Date: 2023-01-10 16:44:13 +0000
Purchase Date: 2023-01-12 20:13:52 +0000
Expiration Date: 2023-01-12 20:49:52 +0000
Signed Date: 2023-01-12 21:31:23 +0000
let result = try await product.purchase()
switch result {
case .success(let verificationResult):
switch verificationResult {
case .verified(let verifiedSuccessfulTransaction):
print(verifiedSuccessfulTransaction)
print("Original Purchase Date: \(verifiedSuccessfulTransaction.originalPurchaseDate ?? Date(timeIntervalSince1970: 0) ) ")
print("Purchase Date: \(verifiedSuccessfulTransaction.purchaseDate ?? Date(timeIntervalSince1970: 0) ) ")
print("Expiration Date: \(verifiedSuccessfulTransaction.expirationDate ?? Date(timeIntervalSince1970: 0) ) ")
print("Signed Date: \(verifiedSuccessfulTransaction.signedDate ?? Date(timeIntervalSince1970: 0) ) ")
await verifiedSuccessfulTransaction.finish()
await self.updatePurchasedProducts()