StoreKit

RSS for tag

Support in-app purchases and interactions with the App Store using StoreKit.

StoreKit Documentation

Post

Replies

Boosts

Views

Activity

How to get signed transaction data from StoreKit 2
Is there any way to get the transaction in its original transaction in a secure format (such as a JWT) using the StoreKit 2 API? I can see the [jsonRepresentation] (https://developer.apple.com/documentation/storekit/transaction/3868410-jsonrepresentation) in the Transaction object, but that is already decoded. The transaction needs to be sent to the backend server for further processing, but there is no way to verify the integrity of the transaction details. For instance, there is no way to know if the transaction details the backend server receives actually belong to that specific user. The backend server can query the App Store API for a specific transaction ID, and then compare the details, but that still does not prove that the transaction the client sent actually belongs to that client. What I'm after is any way to send a transaction to the backend server that has been signed by Apple, which can then be validated. There a few use-cases for this, but the most pressing one is that of recovering purchases and linking new devices to an existing app account. For example: User A installs the client app on device X, which creates an account on the backend server. Assume there is no login-mechanism in place, we just need to rely on their in-app purchase history to identify a user account on new devices (or a re-installation of the app on an existing device). User A purchases a non-consumable or subscription in this app. Now that user has a transaction history that can be retrieved from the App Store API. User A now installs the app on device Y. The backend server creates a new account for this user (since it does not know who that user is, at least not yet). The user triggers a "recover purchases" command in the app, so the app queries the StoreKit 2 API for existing transactions. Store Kit responds with the previous transaction, which the app then sends across to the backend server (currently using jsonRepresentation). The backend server does not have a way to validate that the provided transaction data actually belongs to user A, so simply trusts it, and queries the App Store API with the provided transactionID. The server receives the transaction from the App Store API, and can validate that it in fact is a valid transaction, but still, there is no way to be certain that it belongs to user A. The backend server then merges the newly created account on device Y with the existing account on device X, so device Y now access that same initial account. I feel that there is a problem here. Let's say a malicious user B somehow has access to valid client transaction data from user A. User B installs the app on device Z. User B transmits a a valid jsonRepresentation of a transaction that belongs to user A to the backend server to link their device to the existing account. The server queries the App Store API with the transaction ID, and validates it, and given that this is successful, it simply links device Z to the existing account of user A. Now user B has access to the account of user A. The chances of this happening could be reduced (but not completely avoided) if there was a way to send a signed transaction from a device to the server. That way the backend server could verify the source of the transaction coming from the device, and if it is successful, continue on with any further processing. If there is something in the existing jsonRepresentationof the transaction from the client that I could use for verification, then please point me in the right direction. Yes, all of this may be avoided if the application has login accounts, but that isn't the issue here. Our app does have the ability to log in via 3rd party providers, but that is entirely optional, so the only way we can restore accounts on new devices is to check the App Store transaction history. Any help or suggestions regarding this would be appreciated.
2
0
508
Jun ’24
StoreKit Free Trial Period
I'm offering a free trial period for each of four auto-renewable subscriptions. Does anyone know the best way to detect whether a customer is still in the trial period, and to calculate the remaining trial days? I'm using Storekit 2. I've seen vague answers about using the Transaction purchaseDate and expiry date, but the documentation is incredibly vage as to what those values actually represent when it comes to a free trial period. What does purchase Date actually mean when you're in a free trial? Any help greatly appreciated.
1
0
937
Jun ’24
In App Purchases (Subscription Issue)
Hello, We are facing an issue with our customers on in-app purchasing. One of our customers tried to get a yearly subscription on 23rd May 2024 but could not succeed due to a billing error. But on 19th June 2024, his subscription was purchased automatically for 1 year, and the package expiry date shows 19th June 2025. So why did Apple charge to customer after 1 month without any intimation? Can anybody help here? Thanks
1
0
565
Jun ’24
Subscriptions for teams (in-app purchase)
Is it possible - in appstore - to create a subscriptions model similar to what microsoft teams and google workspace has for team memberships? We want to have a subscription price per user per month (or year). Administrator should be able to add additional users (and add a subscription for the new users) - It doesn't seem to be possible - only option is to have prefix subscriptions with 5 users, 10user, 15 users etc. (we've also looked into consumables - but the lack the option to be reoccurring payments) Thanks in advance?
0
0
294
Jun ’24
Notification Received on Consumable Purchase
Hello, I am encountering an issue where I receive an App Store Server Notification V2 upon the purchase of a consumable item. I have configured the App Store Server Notification V2 endpoint to handle notifications related to subscription products. However, I did not expect to receive notifications for consumable purchases. The notification includes the following signedPayload decoded into the ResponseBodyV2DecodedPayload object with the following values: notificationUUID: 3cd6410b-0c89-4247-aba5-20710e79895e notificationType: null subtype: null The transaction information decoded from the ResponseBodyV2DecodedPayload object is as follows: transactionId: 2000000633622618 webOrderLineItemId: null productId: heart_2 To debug, I called the Get Notification History API of the App Store Server API, and the mentioned notification for the consumable product purchase is not included in the history. Only notifications related to subscription product purchases are retrieved. According to the notification type documentation, there is no explanation for cases where both notificationType and subtype are null, nor is there any mention of receiving notifications for consumable purchases. Therefore, I am uncertain how to interpret and handle this notification. Could you please provide an explanation or guidance on this issue? Thank you. References: https://developer.apple.com/forums/thread/737592 https://developer.apple.com/documentation/appstoreservernotifications/notificationtype
2
0
732
Jun ’24
How to distinguish between subscription purchases made through the app and directly from the App Store
We are wondering if it is possible to distinguish between purchases made through our app and those made directly from the App Store in the subscription process. Does the API information such as Server notifications v2 and Get All Subscription Statuses contain information that can distinguish between these cases? If not, we are thinking of using the appAccountToken included in JWSTransaction to distinguish between them. We are aware that appAccountToken is set when an appAccountToken or applicationUsername is given to an app when purchasing a subscription. Therefore, we believe that the appAccountToken of JWSTransaction is not set when you purchase a subscription directly from the App Store. We think that by setting the appAccountToken for purchases made through our app, we can distinguish whether the subscription was purchased directly from the App Store or not, but is this an appropriate way to do so? Please let us know if there is a case where the appAccountToken is set even if the subscription was purchased directly from the App Store. https://developer.apple.com/documentation/appstoreserverapi/appaccounttoken
1
0
524
Jun ’24
App Store Review Request error Swift 6
I'm fairly new to Swift, so I'm unsure as to what's going wrong with my code and how to fix it. After switching to Swift 6, the view I have with requestReview throws: "Cannot form key path to main actor-isolated property 'requestReview'" As I understand it, this is potentially due to the changes made to @MainActor. However, I am unsure how to go about fixing this error.
3
2
564
Jun ’24
Auto-Renewable Subscription proration
Hello, I studied this and this documents and I find certain contradiction with this discussion. Could you help me out, please? I set up my subscriptions this way: , where Pro (annual or monthly) offers most of the features and Mobile Pro (annual or montly) least of the features. Do I get it right, that: Upgrading: to annual: What happens here? to monthly: [link] The customer’s prorated payment from lower level subscription is refunded to the original payment method. They’re charged for the higher level subscription, which goes into effect immediately. Downgrading: both annual and monthly: [link] They’re charged for the lower level subscription on their next renewal date. They continue using their current subscription until then. Crossgrading: to annual: What happens here? to monthly: [link] ...the crossgrade goes into effect on the customer’s next renewal date. They continue using their current subscription until then. Thanks.
1
0
361
Jun ’24
StoreKit Transaction Price values
Apple added price field on StoreKit's Transaction with Xcode 15.1 and I adopted immediately. What I saw was that when the app was run in debug or TestFlight, the values for price were the same as they were displayed to the user e.g. $29.99 would be returned as 29.99 but in production I got values multiplied by 1000. They do multiply values by 1000 for App Store Server Notifications and APIs though but it is not documented as such for StoreKit's Transaction price field. I am afraid to make this change again and deploy it to production. Has anybody experienced anything like this?
2
0
561
Jun ’24
Subscription Update not working
I am trying to create a subscription with a specific subscriptionPeriod. However, when I try creating a sub, it gives me a 409 "The attribute 'subscriptionPeriod' can not be included in a 'CREATE' request". I have even tried creating a subscription and then updating it https://developer.apple.com/documentation/appstoreconnectapi/subscriptionupdaterequest. But i get the same error.
1
1
404
Jun ’24
Retrieving Zip/Postal Code from Subscriptions and In-App Purchases
Hi, I'm currently working on a application on the server-side that is attempting to generate analytics from our application's user's subscription and in-app purchases. One of the pieces of information that we need is the purchaser's zip/postal code for financial reporting. I noticed Google provides this information in their monthly earnings reports, but Apple seems to omit this field. That being said, is possible to obtain a purchaser's postal/zip code through an api, server notifications, or anything?
0
0
285
Jun ’24
In-App Purchases / Restores Currently Fail in Sandbox Environment
Attempts to purchase an SKProduct or restore all completed transactions via the usual method calls in StoreKit leads to failure in the beta environment with the following error: Error Domain=SKErrorDomain Code=0 "An unknown error occurred" UserInfo={NSLocalizedDescription=An unknown error occurred, NSUnderlyingError=0x300b10210 {Error Domain=ASDErrorDomain Code=500 "(null)" UserInfo={NSUnderlyingError=0x300b10090 {Error Domain=AMSErrorDomain Code=301 "Invalid Status Code" UserInfo={NSLocalizedDescription=Invalid Status Code, AMSURL=https://sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy?REDACTED, AMSStatusCode=500, NSLocalizedFailureReason=The response has an invalid status code}}}}} We've tried with existing sandbox user accounts, and created fresh ones which also fail in a similar way. Our beta testers report the same thing. Nothing has changed in our IAP code, and this started happening about 24-48 hours ago. It seems to be a server-side error - please can someone verify on Apple's side?
2
0
408
Jun ’24
Does SubscriptionStoreView .storeButton(for:.policies) work?
I've added .storeButton(.visible, for:.policies) to my SubscriptionStoreView, and the buttons do appear, but when I tap on them I get a sheet that just says "Terms of Service Unavailable / Somethng went wrong. Try Again.". (similar for Privacy Policy). Is this expected in development? Will these start working correctly in production? (and, more importantly, in App Review?) The docs say that these use the values (i.e. URLs) set in App Store Connect, but that I can override those. This is a new app. Is that wrong, do I need to set the URLs explicitly? Edited to add: the console reports: Failed to fetch terms of service and privacy policy: Error Domain=NSURLErrorDomain Code=-1011 "(null)"
4
1
612
Jun ’24
In App Purchases (Subscriptions)
We recently reduced the subscription price for new customers in the USA from $9.99 to $4.99. However, existing customers are also seeing the new price of $4.99, but when they attempt to purchase the subscription, they are charged the original price of $9.99. Actual Behavior: Existing Customers: See the price as $9.99 and are charged $9.99. New Customers: See the price as $4.99 and are charged $4.99. Can anyone assist with resolving this issue? Thank you!
1
0
372
Jun ’24
403 Forbidden when validating subscriptions
Hi all. Our application uses server-side validation of user subscriptions. After purchasing a subscription, the server keeps track of the subscription renewal statuses. But for the second week already, when requesting information on subscriptions, the server receives 403 Forbidden error. At first we decided that our ip was blocked somewhere and tried to change the ip to a new one. But changing the ip address did not give any results. We keep getting 403 Forbidden error on all requests to check subscriptions from our server. The request goes to the following address /verifyReceipt This method is deprecated and it is necessary to change the method of verification to a new one, but it can't be done quickly. At the moment, there is no information when verifyReceipt will be disabled. And we need to verify subscriptions. Maybe someone has encountered a similar problem? Thanks for the help!
2
0
445
Jun ’24
External Purchase Disclosure Sheet cannot be dismissed
Hello! If one of our users has previously installed a version of the app that doesn't have the External Purchase entitlement, they'll be presented with this automated disclosure sheet on app launch. This automated sheet cannot be dismissed, if you press "LearnMore", the app closes, if you try to swipe it away, the app closes. This completely blocks the user from using the app since they can't get past this disclosure sheet. I'm not allowed to link video recordings here, let me know if there's any way for me to share a recording with steps to reproduce. Thank you!
1
0
297
Jun ’24
Cancel Subscriptions on Behalf of Customer like Netflix (App Store Server API)
I recently had a request from a Product Owner to implement capability like Netflix has to enable users to switch their payment method from App Store Subscription to Credit Card on our website, as per capability that Netflix has in their account management portal. We tested it today with a colleague who was paying for Netflix through iOS in-app subscription: In the Netflix account management pages it showed that he was currently paying via In App Subscription He updated his payment method to Credit Card in their website's account management portal. Almost immediately after adding his Credit Card in his iOS Subscription Management settings (we could see the the subscription had been set to no longer renew, with an expiry date) How is this done - I can't see any API in App Store Server API documentation that gives us a way of cancelling / preventing renewal of subscriptions on behalf of a user... But Netflix can clearly do it somehow...
0
0
522
Jun ’24
Sandbox users always are owning application
Hi! I'm trying to implement a two week free trial for my existing paid ipad app. Following the guidance from the wwdc2022/10007, I'm using AppTransaction.shared and checking the result. I'm getting a verified result, but the appTransaction.originalPurchaseDate is always the same date - 2013-08-01 07:00:00 +0000 / 397033200, even the particular sandbox account user never had a purchase. This makes testing the logical branch of "has this user never purchased this app before" if the app store is always telling us that it's been purchased. (I've been using new sandbox account, so there should be no history) Here's some code that includes hacking around always getting that original purchase date. We're in the final stretches, and wanting to test things that will be closer to actual store behavior (and I'm thinking that always returning a purchased date for an unpurchased app wouldn't be happening) Am I just holding things wrong? Sandbox bug/limitatiin I just have to live with? thanks! ++md class MJAppStore: NSObject { @objc static let shared = MJAppStore() @objc func verifyAppStoreStatus(_ completion: @escaping (MJAppStoreStatus, Error?) -> Void) { Task { do { let status = try await doVerificationThing() completion(status, nil) } catch { completion(.error, error) } } } func doVerificationThing() async throws -> MJAppStoreStatus { do { let result = try await AppTransaction.shared print("TRIAL: survived AppTransaction.shared") switch result { case .unverified(_, _): print("TRIAL: app transaction UNVERIFIED") return .free case .verified(_): let appTransaction = try result.payloadValue // hack around the app store sandbox accounts saying we're purchased even though // we're not really. 2013-08-01 07:00:00 +0000 print("TRIAL: app transaction VERIFIED \(appTransaction.originalPurchaseDate.timeIntervalSinceReferenceDate) -> \(appTransaction.originalPurchaseDate)") if appTransaction.originalPurchaseDate.timeIntervalSinceReferenceDate == 397033200 { return .free } else { return .purchased } } } catch { ...
4
0
552
Jun ’24