StoreKit

RSS for tag

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

StoreKit Documentation

Posts under StoreKit subtopic

Post

Replies

Boosts

Views

Activity

Urgent - React Native IAP Issue
While using react-native-iap and being successfully connected with initConnection() I'm not receiving information on subscriptions with requestSubscription(). Attaching the code here, if anyone could assist asap please would be really grateful thanks! Been at it all day and just can't figure. const handleBuySubscription = async (productId) => { try { await requestSubscription({ sku: productId, }); setLoading(false); } catch (error) { setLoading(false); if (error instanceof PurchaseError) { errorLog({ message: [${error.code}]: ${error.message}, error }); } else { errorLog({ message: "handleBuySubscription", error }); } } }; but the requestSubscription({ sku: productId, })
0
0
112
Aug ’25
Same same originalTransactionId, productId, but the transactionIds are different.
We are seeing an issue with a potential duplicate charge issue regarding our app. We have received two transactions with the same originalTransactionId but different transactionIds: Order A: TransactionId: [Redacted_TID_1] OriginalTransactionId: [Redacted_OID_A] ProductId: weekly_subscription PurchaseDate: [Redacted_Timestamp_1] SignedDate: [Redacted_Timestamp_2] Price: [Redacted_Price] CAD Order B: TransactionId: [Redacted_TID_2] OriginalTransactionId: [Redacted_OID_A] ProductId: weekly_subscription PurchaseDate: [Redacted_Timestamp_1] SignedDate: [Redacted_Timestamp_3] Price: [Redacted_Price] CAD Both transactions share the same originalTransactionId, productId, and expiresDate, but the transactionIds are different. We wanna confirm: Were customers charged once or twice for this subscription renewal? If only one charge was made, how should we programmatically determine the “valid” transaction among multiple records with the same originalTransactionId to avoid confusion in our server-side receipt validation? We appreciate any clarification so we can handle this correctly in our backend.
1
0
270
Sep ’25
ExternalPurchaseCustomLink.token(for:) returns nil on one TestFlight device (while isEligible == true) — other device gets SERVICES token
I’m implementing StoreKit External Purchase Custom Links (EU) and so far it is really painful. I am running into a strange, device-specific issue. On 3/4 devices it works. On one device I never get a token at launch nor before a transaction. isEligible is true everywhere. All devices have versions 18.5 and are located in Germany. Info.plist: SKExternalPurchaseCustomLinkRegions is set to EU storefront codes and I have followed every step in the documentation: https://aninterestingwebsite.com/documentation/storekit/externalpurchasecustomlink Good device: At launch → ACQUISITION = nil, SERVICES = token present. Works consistently. Faulty device: At launch → ACQUISITION = nil, SERVICES = nil. Same before transaction. No token ever reaches my server from this device. isEligible is true on both devices. Any experts or help on the matter?
6
0
222
Jan ’26
Product.SubscriptionInfo subscriptionPeriod does not provide the same result between the Sandbox/App Store environment and the StoreKit Testing in Xcode for a "1 week" subscription. "1 week" vs "7 days"
Hello, I noticed the Product.SubscriptionInfo subscriptionPeriod (of type Product.SubscriptionPeriod) is different for the same product between StoreKit Testing in Xcode and the sandbox/App Store (production) environment. For a “1 week” auto-renewable subscription, we get the following: StoreKit Testing in Xcode: 1 week gives a subscriptionPeriod with value of 1 and a unit of Product.SubscriptionPeriod.Unit.week Sandbox/App Store: 1 week gives a subscriptionPeriod with value of 7 and a unit of Product.SubscriptionPeriod.Unit.day This created issues in my app because I used the localizedDescription of a Product.SubscriptionPeriod to display a text similar to “$4.99 per week”. This is what I obtain with the StoreKit Testing in Xcode, but in the Sandbox/App Store environment, it displays “$4.99 per day” (because the subscriptionPeriod is “7 Days” and the unit is then .day). Obviously, this is not what I wanted to display. Other periods like “1 month”, “2 months”, “3 months”, “6 months, and “1 year”, the period provided by both StoreKit Testing and Sandbox/App Store correspond to the period unit specified in App Store Connect. In addition, I want to report that for a weekly subscription/offer or a 2 weeks offer, Product.SubscriptionInfo.subscriptionPeriod or Product.SubscriptionOffer.period == .weekly or .everyTwoWeeks is always false. We observe the following: With Sandbox or App Store live production: 1 week, Product.SubscriptionInfo.subscriptionPeriod == .weekly is false (because it’s “7 days”) 1 week, Product.SubscriptionOffer.period == .weekly is false (because it’s “7 days”) 2 weeks (offer), Product.SubscriptionInfo.subscriptionPeriod == .everyTwoWeeks is false (because it’s “14 days”) 2 weeks (offer), Product.SubscriptionOffer.period == .everyTwoWeeks is false (because it’s “14 days”) But with an Xcode StoreKit configuration file: 1 week, Product.SubscriptionInfo.subscriptionPeriod == .weekly is true (because it’s “1 week”) 1 week, Product.SubscriptionOffer.period == .weekly is true (because it’s “1 week”) 2 weeks, Product.SubscriptionInfo.subscriptionPeriod == . everyTwoWeeks is true (because it’s “2 weeks”) 2 weeks, Product.SubscriptionOffer.period == . everyTwoWeeks is true (because it’s “2 weeks”) So in sandbox and production, .weekly and .everyTwoWeeks is never possible. If someone from Apple could check the feedback FB19605865 🙂 Thank you Regards, Axel, @alpennec Code: do { let productIDs: [String] = ["revenueSocks_weekly_trial"] let products: [StoreKit.Product] = try await Product.products (for: productIDs) let weeklySubscription: StoreKit.Product = products.first! let displayPrice: String = weeklySubscription.displayPrice // For a weekly subscription in App Store Connect // With an Xcode StoreKit configuration file: subscriptionPeriod unit is Week (week), value is 1 → "1 Week" // With the Sandbox + App Store: subscriptionPeriod unit is Day (.day), value is 7 → "7 Days" let unitString: String = weeklySubscription.subscription!.subscriptionPeriod.unit.localizedDescription print("\(displayPrice) per \(unitString.localizedLowercase)") // StoreKit configuration file → "$4.99 per week" // Sandbox + App Store → "$4.99 per day" } catch { print(error) }
0
0
107
Nov ’25
Python App. Sandbox testing IAP Auto Renewal Subscription
I have created a Python app and built it with pyinstaller and codesigned everything. Now I want to Sandbox test it. In my appstore connect account i have created a subscriptions id. I read that if I am logged out from the AppStore and have codesigned my .app file with a Developer Certificate i should be able to run the app on my local mac and when i click on the "Buy" button it should connect to my app store connect setup. I have implemented StoreKit in my app and use a storekit_bridge to combine the .swift code with my python app. However when i run the app. I get this: "25-07-24 21:01:12,557 - FEC - WARNING - StoreKit: fetchProducts returned empty result 2025-07-24 21:01:12,557 - FEC - INFO - StoreKit fetch_products returned: {"products": []} 2025-07-24 21:01:12,557 - FEC - ERROR - StoreKit: Failed to parse product info: No products returned from JSON" And no login screen appears where I should be able to enter my Sandbox email adress and password. Anyone here who has experience with a Python app combined with In App Purchases? Hope someone can help me out with this.
0
0
152
Jul ’25
sandbox account isn't logging in on purchase window
I don't know if I am posting this in the right place. I am using xcode's phone simulator and I have setup my sandbox account on appstoreconnect under users and access/sandbox/test accounts then in my app on the simulator when I tap the subscribe button to purchase my product the a window pops up for in app purchases and I get a login prompt for my sandbox credentials, but no matter how many times I enter them after tapping ok all I get is a blank login prompt. also not this a brand new sandbox account and I've only changed the password 3 times, that seems to be important because its inconsistent with some of the errors I am getting on the error log here is error log. Purchase did not return a transaction: Error Domain=ASDErrorDomain Code=530 "(null)" UserInfo={NSUnderlyingError=0x600000d09080 {Error Domain=AMSErrorDomain Code=100 "Authentication Failed The authentication failed." UserInfo={NSMultipleUnderlyingErrorsKey=( "Error Domain=AMSErrorDomain Code=2 "Password reuse not available for account The account state does not support password reuse." UserInfo={NSDebugDescription=Password reuse not available for account The account state does not support password reuse., AMSDescription=Password reuse not available for account, AMSFailureReason=The account state does not support password reuse.}", "Error Domain=AMSErrorDomain Code=0 "Authentication Failed Encountered an unrecognized authentication failure." UserInfo={NSDebugDescription=Authentication Failed Encountered an unrecognized authentication failure., AMSDescription=Authentication Failed, AMSFailureReason=Encountered an unrecognized authentication failure.}" ), AMSDescription=Authentication Failed, NSDebugDescription=Authentication Failed The authentication failed., AMSFailureReason=The authentication failed.}}, client-environment-type=Sandbox}
0
0
120
May ’25
Sandbox apple in app purchases not working
Received error that does not have a corresponding StoreKit Error: Error Domain=AMSErrorDomain Code=305 "Purchase Failed Server canceled the purchase More details: Error Domain=AMSErrorDomain Code=305 "Purchase Failed Server canceled the purchase" UserInfo={AMSFailureReason=Server canceled the purchase, AMSURL=https://sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy?guid=00008110-000A4DC10E51401E, AMSDescription=Purchase Failed, AMSStatusCode=200, AMSServerPayload={ "cancel-purchase-batch" = 1; customerMessage = "Unable to process your request."; dialog = { defaultButton = ok; explanation = "Please try again later.\n\n[Environment: Sandbox]"; initialCheckboxValue = 1; isFree = 1; "m-allowed" = 0; message = "Unable to process your request."; okButtonString = OK; }; failureType = ""; "m-allowed" = 0; metrics = { actionUrl = "sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy"; asnState = 0; dialogId = "MZCommerce.SystemError"; eventType = dialog; message = "Unable to process your re"; mtEventTime = "2025-07-28 12:34:22 Etc/GMT"; mtTopic = "xp_its_main"; options = ( OK ); }; pings = ( ); }, NSDebugDescription=Purchase Failed Server canceled the purchase} Received error that does not have a corresponding StoreKit Error: Error Domain=ASDErrorDomain Code=500 "(null)" UserInfo={client-environment-type=Sandbox, storefront-country-code=IND, NSUnderlyingError=0x1276116e0 {Error Domain=AMSErrorDomain Code=305 "Purchase Failed Server canceled the purchase" UserInfo={AMSFailureReason=Server canceled the purchase, AMSURL=https://sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy?guid=00008110-000A4DC10E51401E, AMSDescription=Purchase Failed, AMSStatusCode=200, AMSServerPayload={ "cancel-purchase-batch" = 1; customerMessage = "Unable to process your request."; dialog = { defaultButton = ok; explanation = "Please try again later.\n\n[Environment: Sandbox]"; initialCheckboxValue = 1; isFree = 1; "m-allowed" = 0; message = "Unable to process your request."; okButtonString = OK; }; failureType = ""; "m-allowed" = 0; metrics = { actionUrl = "sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy"; asnState = 0; dialogId = "MZCommerce.SystemError"; eventType = dialog; message = "Unable to process your re"; mtEventTime = "2025-07-28 12:34:22 Etc/GMT"; mtTopic = "xp_its_main"; options = ( OK ); }; pings = ( ); }, NSDebugDescription=Purchase Failed Server canceled the purchase}}} Purchase did not return a transaction: Error Domain=ASDErrorDomain Code=500 "(null)" UserInfo={client-environment-type=Sandbox, storefront-country-code=IND, NSUnderlyingError=0x1276116e0 {Error Domain=AMSErrorDomain Code=305 "Purchase Failed Server canceled the purchase" UserInfo={AMSFailureReason=Server canceled the purchase, AMSURL=https://sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy?guid=00008110-000A4DC10E51401E, AMSDescription=Purchase Failed, AMSStatusCode=200, AMSServerPayload={ "cancel-purchase-batch" = 1; customerMessage = "Unable to process your request."; dialog = { defaultButton = ok; explanation = "Please try again later.\n\n[Environment: Sandbox]"; initialCheckboxValue = 1; isFree = 1; "m-allowed" = 0; message = "Unable to process your request."; okButtonString = OK; }; failureType = ""; "m-allowed" = 0; metrics = { actionUrl = "sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy"; asnState = 0; dialogId = "MZCommerce.SystemError"; eventType = dialog; message = "Unable to process your re"; mtEventTime = "2025-07-28 12:34:22 Etc/GMT"; mtTopic = "xp_its_main"; options = ( OK ); }; pings = ( ); }, NSDebugDescription=Purchase Failed Server canceled the purchase}}}
3
0
232
Jul ’25
Payment screen not loading for app subscription
Hi All i have developed an app but when I go to download the paywall appears as it’s subscription only but it shows no prices for the subscription I get an endless swirl. I checked the product id etc and it’s correct but the subcription on my App Store Connect shows as approved. is there another status it needs to reach before it becomes like like ready for sale? https://apps.apple.com/gb/app/gameaware-pro/id6751592563
0
0
128
Sep ’25
Can StoreKit products be observed with ObservableObject? Can I get notified when a users subscription has lapsed without polling Transaction.currentEntitlements?
I have an auto-renewable subscription. I have two methods helping me keep track of when they are expired @MainActor public func isPurchased(product: Product) async -> Bool { guard let state = await product.currentEntitlement else { return false } switch state { case .unverified(_, _): return false case .verified(let transaction): await transaction.finish() return isTransactionRelevant(transaction) } } private func isTransactionRelevant(_ transaction: Transaction) -> Bool { if let revocationDate = transaction.revocationDate { logger.error("Transaction verification failed: Transaction was revoked on \(revocationDate)") return false } if let expirationDate = transaction.expirationDate, expirationDate < Date() { logger.error("Transaction verification failed: Transaction expired on \(expirationDate)") return false } if transaction.isUpgraded { logger.error("Transaction verification failed: Transaction was upgraded") return false } logger.info("Transaction verification succeeded") return true } I also have this that I can call to get the latest state of purchases @MainActor public func updateStoreKitSubscriptionStatus() async { var currentProductsPurchased: [Product] = [] for await result in Transaction.currentEntitlements { if case .verified(let transaction) = result { if isTransactionRelevant(transaction) { if let product = products.first( where: { $0.id == transaction.productID }) { currentProductsPurchased.append(product) } } await transaction.finish() } } self.purchasedProducts = currentProductsPurchased } Right now when a subscription expires the user needs to manually do some action that triggers updateStoreKitSubscriptionStatus() as it appears that expirations do not come through in Transaction.updates. I am surprised there does not seem to be a better way. Does StoreKit not notify you somewhere that an auto-renewable subscription has expired? Can you observe it in an ObservableObject? Or do I need to just frequently poll Transaction.currentEntitlements even if I dont expect frequent updates?
0
0
107
Jul ’25
Sandbox refund testing "Cannot connect" error
I'm running into a refund issue when testing in-app purchases in sandbox mode. I'm able to successfully purchase items but roughly 1 out of 3 times, when the refund window pops up, it says it cannot connect even though our webhook servers can verify the transaction id sent from apple. My internet connection is good and I've varied the wait period from purchase to refund from 15mins to 2 hours. Any help would be greatly appreciated.
1
0
159
Sep ’25
Advanced Commerce API – 5000000 "Unknown error" when calling Migrate API
We've been approved for the Advanced Commerce API. After receiving the approval, we completed the initial setup requirements (submitted a generic product ID and shared our subscriptions page deep link). When testing the Migrate Subscription to Advanced Commerce endpoint in the sandbox, we receive a 5000000 error. What could be the reason/s for it? There is no information, and the API call we make includes all required parameters. Could it be related to the fact that our new generic product ID status is "Missing Metadata"? Is there a way to understand what the issue is on Apple's side?
0
0
128
Oct ’25
StoreKit2 originalTransactionId
I would like to inquire about the originalTransactionId of StoreKit2. Users who purchase auto-renewal subscription products To re-purchase the same subscription item after cancellation and prior to refund If you receive a refund after cancellation and re-purchase the same subscription item If you do not renew immediately after expiration and re-purchase the same subscription after a long period of time I would like to ask if 1, 2, and 3 all use the same value as the original Transaction Id at the initial subscription. In the case of 3, if you re-purchase more than a few days after the last subscription purchase, please let me know if there are any detailed conditions such as the original Transaction Id not maintained.
0
0
73
Nov ’25
Subscribe button does nothing in App Review, but In
Hello, My app "MyCourses" (bundle id: com.ahmedbaqer.mycourses) was rejected under Guideline 2.1 because "No action followed when we tapped the button to subscribe to a course" on iPadOS 26.1. When I run the same code (version 1.0.0 (11)) from Xcode on a real device using a StoreKit configuration file (In App Purchase.storekit), the purchase flow works correctly: When I tap the "Subscribe via Apple" button, the App Store purchase sheet appears. The purchase completes successfully and unlocks the course. I use the in_app_purchase Flutter plugin and queryProductDetails to load products. However, when I install the build via TestFlight (and in App Review), tapping the same "Subscribe via Apple" button does nothing – which matches the behavior described by App Review. From my logs it looks like queryProductDetails is returning an empty productDetails list in that environment. For In‑App Purchases: I created 7 non‑consumable products in App Store Connect. Their Product IDs exactly match the IDs in my In App Purchase.storekit file (used only for local Xcode testing). All IAPs are now in "Waiting for Review" status and are linked to the iOS app version 1.0.0 (11) in the “In‑App Purchases and Subscriptions” section. At the time of the original review, some IAPs were in "Developer Action Needed / Rejected" state, so I suspect queryProductDetails may have returned no products and the reviewer saw no action after tapping the button. My questions: When IAP products are in "Waiting for Review" and linked to the app version, should queryProductDetails return them during App Review / TestFlight, or do they need to be fully approved first? Is there any additional configuration required so that the subscribe button reliably shows the App Store purchase sheet for reviewers (for example, any specific StoreKit / sandbox settings)? Are there recommended best practices to show a clearer error state when queryProductDetails returns no products, so that App Review understands this is a configuration / IAP-status issue rather than a UI bug? Any guidance from Apple engineers or other developers who faced a similar situation would be greatly appreciated. Thanks in advance.
0
0
233
Nov ’25
Verification failed with status INVALID_CHAIN_LENGTH
我正在通过集成app-store-server-library-java来实现 iap服务端校验。我参照了官网提供的Verification Usage 的代码,运行的时候异常信息如下: at com.apple.itunes.storekit.verification.ChainVerifier.verifyChainWithoutCaching(ChainVerifier.java:98) at com.apple.itunes.storekit.verification.ChainVerifier.verifyChain(ChainVerifier.java:71) at com.apple.itunes.storekit.verification.SignedDataVerifier.decodeSignedObject(SignedDataVerifier.java:186) at com.apple.itunes.storekit.verification.SignedDataVerifier.verifyAndDecodeTransaction(SignedDataVerifier.java:72) 我的代码如下: import com.apple.itunes.storekit.model.ResponseBodyV2DecodedPayload; import com.apple.itunes.storekit.verification.SignedDataVerifier; import com.apple.itunes.storekit.verification.VerificationException; import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.DecodedJWT; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.Base64; import java.util.Set; public class ExampleVerification { public static void main(String[] args) throws FileNotFoundException { String bundleId = "com.example"; Environment environment = Environment.SANDBOX; Set<InputStream> rootCAs = Set.of( new FileInputStream("AppleRootCA-G3.cer"), new FileInputStream("AppleRootCA-G2.cer") ); Long appAppleId = null; // appAppleId must be provided for the Production environment SignedDataVerifier signedPayloadVerifier = new SignedDataVerifier(rootCAs, bundleId, appAppleId, environment, true); String appTransactionJWS = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IkFwcGxlX1hjb2RlX0tleSIsIng1YyI6WyJNSUlCeXpDQ0FYR2dBd0lCQWdJQkFUQUtCZ2dxaGtqT1BRUURBakJJTVNJd0lBWURWUVFERXhsVGRHOXlaVXRwZENCVVpYTjBhVzVuSUdsdUlGaGpiMlJsTVNJd0lBWURWUVFLRXhsVGRHOXlaVXRwZENCVVpYTjBhVzVuSUdsdUlGaGpiMlJsTUI0WERUSTFNRFl3TXpFeE1UQXdNRm9YRFRJMk1EWXdNekV4TVRBd01Gb3dTREVpTUNBR0ExVUVBeE1aVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEVpTUNBR0ExVUVDaE1aVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCTnZZZ3o1MW1CbEMweE5McW9rMUJCcithRWJEb1ZEeVkyaVRsejZsK1JjYVR4QStVY2ptMjBESTNncFFlM280a2doRGxSbGowdEo1enBGUHgyQWR2VCtqVERCS01CSUdBMVVkRXdFQlwvd1FJTUFZQkFmOENBUUF3SkFZRFZSMFJCQjB3RzRFWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRBT0JnTlZIUThCQWY4RUJBTUNCNEF3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUloQU40bUJWTHBoZkpjYjdweHF2b09XcjkyK1czYU5LRG9pazV5Vk9BT0NEVmxBaUFYWVF0czJubWZGMStGYzlSODJHXC96QWhaVU00aDNTXC9VdFE4Q1lPS2p3ZlE9PSJdfQ.eyJhcHBsaWNhdGlvblZlcnNpb24iOiIxIiwib3JpZ2luYWxQdXJjaGFzZURhdGUiOjAsImJ1bmRsZUlkIjoiYnJpZ2h0LnVuaWhhbmQuY24iLCJhcHBUcmFuc2FjdGlvbklkIjoiMCIsImRldmljZVZlcmlmaWNhdGlvbiI6IlRYdGRvMWZtNDhQVDdXUUh5cHU4K2l3TW55YmNoTTNNeG5XUnhOR1JqSFhQQnVqMXdUaldcL05zN3JtUmJlQTd3IiwicmVjZWlwdFR5cGUiOiJYY29kZSIsIm9yaWdpbmFsQXBwbGljYXRpb25WZXJzaW9uIjoiMSIsInJlcXVlc3REYXRlIjoxNzYxMDM1OTMzNTE3LCJvcmlnaW5hbFBsYXRmb3JtIjoiaU9TIiwicmVjZWlwdENyZWF0aW9uRGF0ZSI6MTc2MTAzNTkzMzUxNywiZGV2aWNlVmVyaWZpY2F0aW9uTm9uY2UiOiI1ZDhmNzM5Mi01N2YwLTQyM2YtOTMzNy1hZDQ0YTk5MDM4Y2EifQ.2ZO5xsx-yywP4IyaDz4KQ3mq181ZGwlX2uANSm-kHq50KIdMMUDveMsCrcZmHdzLH2rpfPsXKaIMdM25Hdcuuw"; DecodedJWT unverifiedJWT = JWT.decode(appTransactionJWS); String header = unverifiedJWT.getHeader(); System.out.println(new String(Base64.getDecoder().decode(header))); try { signedPayloadVerifier.verifyAndDecodeTransaction(appTransactionJWS); } catch (VerificationException e) { e.printStackTrace(); } } } 查看了ChainVerifier.java 源代码,发现 private static final int EXPECTED_CHAIN_LENGTH = 3; // <--- 关键常量 // ... PublicKey verifyChainWithoutCaching(String[] certificates, boolean performRevocationChecking, Date effectiveDate) throws VerificationException { // ... 解析证书代码 ... if (parsedCertificates.size() != EXPECTED_CHAIN_LENGTH) { throw new VerificationException(VerificationStatus.INVALID_CHAIN_LENGTH); // <--- 抛出异常点 } // ... 后续验证代码 ... } appTransactionJWS是来自客户端的沙盒环境。 我发现沙盒环境的jws总是包含一个证书,而后端验证又必须要求三个证书,请问这个问题如何解决。
0
0
88
Oct ’25
StoreKit JWT Verification Failing - Expired Certificate in x5c Chain Body
Starting around October 12, 2025 at 19:51 UTC, we're seeing intermittent failures when verifying StoreKit transaction JWTs. The issue appears to be related to certificate expiration in the x5c chain in the JWT tokens provided by Apple. What We're Seeing Some JWTs are being signed with different certificates. Some work, some fail certificate validation. Old Certificate (Expired - causing failures): Subject: Prod ECC Mac App Store and iTunes Store Receipt Signing Issuer: Apple Worldwide Developer Relations Certification Authority (G6) Serial: 166451396673336810269824643773700992094 Valid From: 2023-09-12 19:51:53 UTC Valid To: 2025-10-11 19:51:52 UTC ❌ EXPIRED New Certificate (Valid - working): Subject: Prod ECC Mac App Store and iTunes Store Receipt Signing Issuer: Apple Worldwide Developer Relations Certification Authority (G6) Serial: 95385247725814954943813376527885434295 Valid From: 2025-09-19 19:44:51 UTC Valid To: 2027-10-13 17:47:23 UTC ✓ VALID Current Status Most JWTs use the new valid certificate. Some JWTs still use the expired certificate. This appears inconsistent/random. I don't know if it's an issue with some of Apple's servers, or an issue with StoreKit on-device cache, but seems to me like a bug on Apple's side either way. Are we missing something? Is this a known issue? Any guidance or timeline from Apple would be greatly appreciated, as this is blocking legitimate paying users.
0
0
291
Oct ’25
"StoreKit Testing in Xcode" certificate is not trusted on iOS 26
Hello. I have setup a StoreKit testing in the app that was and still is perfectly working on iOS 18. Unfortunately when run on iOS 26 the following error gets printed in the console after calling Transaction.currentEntitlement(for:) method: Failed to verify certificate chain due to client recoverable failure: Error Domain=NSOSStatusErrorDomain Code=-67843 "“StoreKit Testing in Xcode” certificate is not trusted" UserInfo={NSLocalizedDescription=“StoreKit Testing in Xcode” certificate is not trusted, NSUnderlyingError=0x109de7e10 {Error Domain=NSOSStatusErrorDomain Code=-67843 "Certificate 0 “StoreKit Testing in Xcode” has errors: Root is not trusted;" UserInfo={NSLocalizedDescription=Certificate 0 “StoreKit Testing in Xcode” has errors: Root is not trusted;}}} I'm not seeting any StoreKit Testing certificates in phone's certificate trust settings. This test was performed on iOS 26.0 (23A341) with app built in Xcode 16.4. FB20339145
1
0
176
Nov ’25
Best practice to prevent users from switching between subscriptions in the same group?
Hello everyone, For example, our app currently has one subscription group in App Store Connect with 5 plans (2 annual, 2 monthly, and 1 quarterly). By default, users can go into Apple Subscriptions in Settings and freely switch between all of these plans. However, our business requirement is to only allow users to stay on one annual plan and one quarterly plan. We don’t want them to switch to the other plans. My questions are: Is there any best practice or recommended approach to restrict subscription switching within the same group? Would removing the unwanted products from sale be the correct approach, or are there any risks/downsides with this method? Has anyone faced a similar situation and found a practical solution? Any guidance or shared experience would be greatly appreciated. Thanks!
4
0
164
Sep ’25
Unexpected notificationType in App Store Server Notifications V2 when free trial ends
In my app’s IAP products, before enabling free trials, the App Store Server Notifications V2 callbacks all returned the correct notificationType. For auto-renewable subscriptions, when they were about to expire, the notificationType was either DID_RENEW or EXPIRED. A small number of cases(DID_FAIL_TO_RENEW) failed to renew due to billing issues, which was expected. However, after I enabled a 7-day free trial for the auto-renewable products, I noticed that in the App Store Server Notifications V2 callbacks, almost all users (except those who manually turned off auto-renewal) received notificationType = DID_FAIL_TO_RENEW. According to the documentation, DID_FAIL_TO_RENEW indicates a billing issue renewal failure, but in this case it seems like all renewals are being marked as failed. I’ve observed that for users who cancel during the free trial, the callbacks look normal: first a DID_CHANGE_RENEWAL_STATUS notification, then an EXPIRED notification when the trial ends. That flow seems correct. However, for users who do not cancel the trial, almost all callbacks show DID_FAIL_TO_RENEW. Does this mean every remaining user has a billing issue? I also noticed on the Developer Forums that other developers have reported the same issue — receiving a large number of DID_FAIL_TO_RENEW notifications specifically when free trials convert to paid subscriptions: https://aninterestingwebsite.com/forums/search?q=DID_FAIL_TO_RENEW Could someone clarify: under normal circumstances, when a free trial transitions to a paid subscription and fails, should the App Store Server Notifications V2 notificationType indeed be DID_FAIL_TO_RENEW, or should it be a different type(e.g. EXPIRED)? And is it expected behavior that almost all free trial conversions return DID_FAIL_TO_RENEW?
3
0
195
Sep ’25
Urgent - React Native IAP Issue
While using react-native-iap and being successfully connected with initConnection() I'm not receiving information on subscriptions with requestSubscription(). Attaching the code here, if anyone could assist asap please would be really grateful thanks! Been at it all day and just can't figure. const handleBuySubscription = async (productId) =&gt; { try { await requestSubscription({ sku: productId, }); setLoading(false); } catch (error) { setLoading(false); if (error instanceof PurchaseError) { errorLog({ message: [${error.code}]: ${error.message}, error }); } else { errorLog({ message: "handleBuySubscription", error }); } } }; but the requestSubscription({ sku: productId, })
Replies
0
Boosts
0
Views
112
Activity
Aug ’25
Same same originalTransactionId, productId, but the transactionIds are different.
We are seeing an issue with a potential duplicate charge issue regarding our app. We have received two transactions with the same originalTransactionId but different transactionIds: Order A: TransactionId: [Redacted_TID_1] OriginalTransactionId: [Redacted_OID_A] ProductId: weekly_subscription PurchaseDate: [Redacted_Timestamp_1] SignedDate: [Redacted_Timestamp_2] Price: [Redacted_Price] CAD Order B: TransactionId: [Redacted_TID_2] OriginalTransactionId: [Redacted_OID_A] ProductId: weekly_subscription PurchaseDate: [Redacted_Timestamp_1] SignedDate: [Redacted_Timestamp_3] Price: [Redacted_Price] CAD Both transactions share the same originalTransactionId, productId, and expiresDate, but the transactionIds are different. We wanna confirm: Were customers charged once or twice for this subscription renewal? If only one charge was made, how should we programmatically determine the “valid” transaction among multiple records with the same originalTransactionId to avoid confusion in our server-side receipt validation? We appreciate any clarification so we can handle this correctly in our backend.
Replies
1
Boosts
0
Views
270
Activity
Sep ’25
ExternalPurchaseCustomLink.token(for:) returns nil on one TestFlight device (while isEligible == true) — other device gets SERVICES token
I’m implementing StoreKit External Purchase Custom Links (EU) and so far it is really painful. I am running into a strange, device-specific issue. On 3/4 devices it works. On one device I never get a token at launch nor before a transaction. isEligible is true everywhere. All devices have versions 18.5 and are located in Germany. Info.plist: SKExternalPurchaseCustomLinkRegions is set to EU storefront codes and I have followed every step in the documentation: https://aninterestingwebsite.com/documentation/storekit/externalpurchasecustomlink Good device: At launch → ACQUISITION = nil, SERVICES = token present. Works consistently. Faulty device: At launch → ACQUISITION = nil, SERVICES = nil. Same before transaction. No token ever reaches my server from this device. isEligible is true on both devices. Any experts or help on the matter?
Replies
6
Boosts
0
Views
222
Activity
Jan ’26
Product.SubscriptionInfo subscriptionPeriod does not provide the same result between the Sandbox/App Store environment and the StoreKit Testing in Xcode for a "1 week" subscription. "1 week" vs "7 days"
Hello, I noticed the Product.SubscriptionInfo subscriptionPeriod (of type Product.SubscriptionPeriod) is different for the same product between StoreKit Testing in Xcode and the sandbox/App Store (production) environment. For a “1 week” auto-renewable subscription, we get the following: StoreKit Testing in Xcode: 1 week gives a subscriptionPeriod with value of 1 and a unit of Product.SubscriptionPeriod.Unit.week Sandbox/App Store: 1 week gives a subscriptionPeriod with value of 7 and a unit of Product.SubscriptionPeriod.Unit.day This created issues in my app because I used the localizedDescription of a Product.SubscriptionPeriod to display a text similar to “$4.99 per week”. This is what I obtain with the StoreKit Testing in Xcode, but in the Sandbox/App Store environment, it displays “$4.99 per day” (because the subscriptionPeriod is “7 Days” and the unit is then .day). Obviously, this is not what I wanted to display. Other periods like “1 month”, “2 months”, “3 months”, “6 months, and “1 year”, the period provided by both StoreKit Testing and Sandbox/App Store correspond to the period unit specified in App Store Connect. In addition, I want to report that for a weekly subscription/offer or a 2 weeks offer, Product.SubscriptionInfo.subscriptionPeriod or Product.SubscriptionOffer.period == .weekly or .everyTwoWeeks is always false. We observe the following: With Sandbox or App Store live production: 1 week, Product.SubscriptionInfo.subscriptionPeriod == .weekly is false (because it’s “7 days”) 1 week, Product.SubscriptionOffer.period == .weekly is false (because it’s “7 days”) 2 weeks (offer), Product.SubscriptionInfo.subscriptionPeriod == .everyTwoWeeks is false (because it’s “14 days”) 2 weeks (offer), Product.SubscriptionOffer.period == .everyTwoWeeks is false (because it’s “14 days”) But with an Xcode StoreKit configuration file: 1 week, Product.SubscriptionInfo.subscriptionPeriod == .weekly is true (because it’s “1 week”) 1 week, Product.SubscriptionOffer.period == .weekly is true (because it’s “1 week”) 2 weeks, Product.SubscriptionInfo.subscriptionPeriod == . everyTwoWeeks is true (because it’s “2 weeks”) 2 weeks, Product.SubscriptionOffer.period == . everyTwoWeeks is true (because it’s “2 weeks”) So in sandbox and production, .weekly and .everyTwoWeeks is never possible. If someone from Apple could check the feedback FB19605865 🙂 Thank you Regards, Axel, @alpennec Code: do { let productIDs: [String] = ["revenueSocks_weekly_trial"] let products: [StoreKit.Product] = try await Product.products (for: productIDs) let weeklySubscription: StoreKit.Product = products.first! let displayPrice: String = weeklySubscription.displayPrice // For a weekly subscription in App Store Connect // With an Xcode StoreKit configuration file: subscriptionPeriod unit is Week (week), value is 1 → "1 Week" // With the Sandbox + App Store: subscriptionPeriod unit is Day (.day), value is 7 → "7 Days" let unitString: String = weeklySubscription.subscription!.subscriptionPeriod.unit.localizedDescription print("\(displayPrice) per \(unitString.localizedLowercase)") // StoreKit configuration file → "$4.99 per week" // Sandbox + App Store → "$4.99 per day" } catch { print(error) }
Replies
0
Boosts
0
Views
107
Activity
Nov ’25
Python App. Sandbox testing IAP Auto Renewal Subscription
I have created a Python app and built it with pyinstaller and codesigned everything. Now I want to Sandbox test it. In my appstore connect account i have created a subscriptions id. I read that if I am logged out from the AppStore and have codesigned my .app file with a Developer Certificate i should be able to run the app on my local mac and when i click on the "Buy" button it should connect to my app store connect setup. I have implemented StoreKit in my app and use a storekit_bridge to combine the .swift code with my python app. However when i run the app. I get this: "25-07-24 21:01:12,557 - FEC - WARNING - StoreKit: fetchProducts returned empty result 2025-07-24 21:01:12,557 - FEC - INFO - StoreKit fetch_products returned: {"products": []} 2025-07-24 21:01:12,557 - FEC - ERROR - StoreKit: Failed to parse product info: No products returned from JSON" And no login screen appears where I should be able to enter my Sandbox email adress and password. Anyone here who has experience with a Python app combined with In App Purchases? Hope someone can help me out with this.
Replies
0
Boosts
0
Views
152
Activity
Jul ’25
sandbox account isn't logging in on purchase window
I don't know if I am posting this in the right place. I am using xcode's phone simulator and I have setup my sandbox account on appstoreconnect under users and access/sandbox/test accounts then in my app on the simulator when I tap the subscribe button to purchase my product the a window pops up for in app purchases and I get a login prompt for my sandbox credentials, but no matter how many times I enter them after tapping ok all I get is a blank login prompt. also not this a brand new sandbox account and I've only changed the password 3 times, that seems to be important because its inconsistent with some of the errors I am getting on the error log here is error log. Purchase did not return a transaction: Error Domain=ASDErrorDomain Code=530 "(null)" UserInfo={NSUnderlyingError=0x600000d09080 {Error Domain=AMSErrorDomain Code=100 "Authentication Failed The authentication failed." UserInfo={NSMultipleUnderlyingErrorsKey=( "Error Domain=AMSErrorDomain Code=2 "Password reuse not available for account The account state does not support password reuse." UserInfo={NSDebugDescription=Password reuse not available for account The account state does not support password reuse., AMSDescription=Password reuse not available for account, AMSFailureReason=The account state does not support password reuse.}", "Error Domain=AMSErrorDomain Code=0 "Authentication Failed Encountered an unrecognized authentication failure." UserInfo={NSDebugDescription=Authentication Failed Encountered an unrecognized authentication failure., AMSDescription=Authentication Failed, AMSFailureReason=Encountered an unrecognized authentication failure.}" ), AMSDescription=Authentication Failed, NSDebugDescription=Authentication Failed The authentication failed., AMSFailureReason=The authentication failed.}}, client-environment-type=Sandbox}
Replies
0
Boosts
0
Views
120
Activity
May ’25
Sandbox apple in app purchases not working
Received error that does not have a corresponding StoreKit Error: Error Domain=AMSErrorDomain Code=305 "Purchase Failed Server canceled the purchase More details: Error Domain=AMSErrorDomain Code=305 "Purchase Failed Server canceled the purchase" UserInfo={AMSFailureReason=Server canceled the purchase, AMSURL=https://sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy?guid=00008110-000A4DC10E51401E, AMSDescription=Purchase Failed, AMSStatusCode=200, AMSServerPayload={ "cancel-purchase-batch" = 1; customerMessage = "Unable to process your request."; dialog = { defaultButton = ok; explanation = "Please try again later.\n\n[Environment: Sandbox]"; initialCheckboxValue = 1; isFree = 1; "m-allowed" = 0; message = "Unable to process your request."; okButtonString = OK; }; failureType = ""; "m-allowed" = 0; metrics = { actionUrl = "sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy"; asnState = 0; dialogId = "MZCommerce.SystemError"; eventType = dialog; message = "Unable to process your re"; mtEventTime = "2025-07-28 12:34:22 Etc/GMT"; mtTopic = "xp_its_main"; options = ( OK ); }; pings = ( ); }, NSDebugDescription=Purchase Failed Server canceled the purchase} Received error that does not have a corresponding StoreKit Error: Error Domain=ASDErrorDomain Code=500 "(null)" UserInfo={client-environment-type=Sandbox, storefront-country-code=IND, NSUnderlyingError=0x1276116e0 {Error Domain=AMSErrorDomain Code=305 "Purchase Failed Server canceled the purchase" UserInfo={AMSFailureReason=Server canceled the purchase, AMSURL=https://sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy?guid=00008110-000A4DC10E51401E, AMSDescription=Purchase Failed, AMSStatusCode=200, AMSServerPayload={ "cancel-purchase-batch" = 1; customerMessage = "Unable to process your request."; dialog = { defaultButton = ok; explanation = "Please try again later.\n\n[Environment: Sandbox]"; initialCheckboxValue = 1; isFree = 1; "m-allowed" = 0; message = "Unable to process your request."; okButtonString = OK; }; failureType = ""; "m-allowed" = 0; metrics = { actionUrl = "sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy"; asnState = 0; dialogId = "MZCommerce.SystemError"; eventType = dialog; message = "Unable to process your re"; mtEventTime = "2025-07-28 12:34:22 Etc/GMT"; mtTopic = "xp_its_main"; options = ( OK ); }; pings = ( ); }, NSDebugDescription=Purchase Failed Server canceled the purchase}}} Purchase did not return a transaction: Error Domain=ASDErrorDomain Code=500 "(null)" UserInfo={client-environment-type=Sandbox, storefront-country-code=IND, NSUnderlyingError=0x1276116e0 {Error Domain=AMSErrorDomain Code=305 "Purchase Failed Server canceled the purchase" UserInfo={AMSFailureReason=Server canceled the purchase, AMSURL=https://sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy?guid=00008110-000A4DC10E51401E, AMSDescription=Purchase Failed, AMSStatusCode=200, AMSServerPayload={ "cancel-purchase-batch" = 1; customerMessage = "Unable to process your request."; dialog = { defaultButton = ok; explanation = "Please try again later.\n\n[Environment: Sandbox]"; initialCheckboxValue = 1; isFree = 1; "m-allowed" = 0; message = "Unable to process your request."; okButtonString = OK; }; failureType = ""; "m-allowed" = 0; metrics = { actionUrl = "sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy"; asnState = 0; dialogId = "MZCommerce.SystemError"; eventType = dialog; message = "Unable to process your re"; mtEventTime = "2025-07-28 12:34:22 Etc/GMT"; mtTopic = "xp_its_main"; options = ( OK ); }; pings = ( ); }, NSDebugDescription=Purchase Failed Server canceled the purchase}}}
Replies
3
Boosts
0
Views
232
Activity
Jul ’25
Payment screen not loading for app subscription
Hi All i have developed an app but when I go to download the paywall appears as it’s subscription only but it shows no prices for the subscription I get an endless swirl. I checked the product id etc and it’s correct but the subcription on my App Store Connect shows as approved. is there another status it needs to reach before it becomes like like ready for sale? https://apps.apple.com/gb/app/gameaware-pro/id6751592563
Replies
0
Boosts
0
Views
128
Activity
Sep ’25
Can StoreKit products be observed with ObservableObject? Can I get notified when a users subscription has lapsed without polling Transaction.currentEntitlements?
I have an auto-renewable subscription. I have two methods helping me keep track of when they are expired @MainActor public func isPurchased(product: Product) async -> Bool { guard let state = await product.currentEntitlement else { return false } switch state { case .unverified(_, _): return false case .verified(let transaction): await transaction.finish() return isTransactionRelevant(transaction) } } private func isTransactionRelevant(_ transaction: Transaction) -> Bool { if let revocationDate = transaction.revocationDate { logger.error("Transaction verification failed: Transaction was revoked on \(revocationDate)") return false } if let expirationDate = transaction.expirationDate, expirationDate < Date() { logger.error("Transaction verification failed: Transaction expired on \(expirationDate)") return false } if transaction.isUpgraded { logger.error("Transaction verification failed: Transaction was upgraded") return false } logger.info("Transaction verification succeeded") return true } I also have this that I can call to get the latest state of purchases @MainActor public func updateStoreKitSubscriptionStatus() async { var currentProductsPurchased: [Product] = [] for await result in Transaction.currentEntitlements { if case .verified(let transaction) = result { if isTransactionRelevant(transaction) { if let product = products.first( where: { $0.id == transaction.productID }) { currentProductsPurchased.append(product) } } await transaction.finish() } } self.purchasedProducts = currentProductsPurchased } Right now when a subscription expires the user needs to manually do some action that triggers updateStoreKitSubscriptionStatus() as it appears that expirations do not come through in Transaction.updates. I am surprised there does not seem to be a better way. Does StoreKit not notify you somewhere that an auto-renewable subscription has expired? Can you observe it in an ObservableObject? Or do I need to just frequently poll Transaction.currentEntitlements even if I dont expect frequent updates?
Replies
0
Boosts
0
Views
107
Activity
Jul ’25
Sandbox refund testing "Cannot connect" error
I'm running into a refund issue when testing in-app purchases in sandbox mode. I'm able to successfully purchase items but roughly 1 out of 3 times, when the refund window pops up, it says it cannot connect even though our webhook servers can verify the transaction id sent from apple. My internet connection is good and I've varied the wait period from purchase to refund from 15mins to 2 hours. Any help would be greatly appreciated.
Replies
1
Boosts
0
Views
159
Activity
Sep ’25
isEligibleForIntroOffer(for groupID: String) aways return ture
Product.SubscriptionInfo.isEligibleForIntroOffer(for: "21340582") In the production environment, I have already used the intro offer for this group, but this method still returns true
Replies
1
Boosts
0
Views
110
Activity
May ’25
Advanced Commerce API – 5000000 "Unknown error" when calling Migrate API
We've been approved for the Advanced Commerce API. After receiving the approval, we completed the initial setup requirements (submitted a generic product ID and shared our subscriptions page deep link). When testing the Migrate Subscription to Advanced Commerce endpoint in the sandbox, we receive a 5000000 error. What could be the reason/s for it? There is no information, and the API call we make includes all required parameters. Could it be related to the fact that our new generic product ID status is "Missing Metadata"? Is there a way to understand what the issue is on Apple's side?
Replies
0
Boosts
0
Views
128
Activity
Oct ’25
StoreKit2 originalTransactionId
I would like to inquire about the originalTransactionId of StoreKit2. Users who purchase auto-renewal subscription products To re-purchase the same subscription item after cancellation and prior to refund If you receive a refund after cancellation and re-purchase the same subscription item If you do not renew immediately after expiration and re-purchase the same subscription after a long period of time I would like to ask if 1, 2, and 3 all use the same value as the original Transaction Id at the initial subscription. In the case of 3, if you re-purchase more than a few days after the last subscription purchase, please let me know if there are any detailed conditions such as the original Transaction Id not maintained.
Replies
0
Boosts
0
Views
73
Activity
Nov ’25
Subscribe button does nothing in App Review, but In
Hello, My app "MyCourses" (bundle id: com.ahmedbaqer.mycourses) was rejected under Guideline 2.1 because "No action followed when we tapped the button to subscribe to a course" on iPadOS 26.1. When I run the same code (version 1.0.0 (11)) from Xcode on a real device using a StoreKit configuration file (In App Purchase.storekit), the purchase flow works correctly: When I tap the "Subscribe via Apple" button, the App Store purchase sheet appears. The purchase completes successfully and unlocks the course. I use the in_app_purchase Flutter plugin and queryProductDetails to load products. However, when I install the build via TestFlight (and in App Review), tapping the same "Subscribe via Apple" button does nothing – which matches the behavior described by App Review. From my logs it looks like queryProductDetails is returning an empty productDetails list in that environment. For In‑App Purchases: I created 7 non‑consumable products in App Store Connect. Their Product IDs exactly match the IDs in my In App Purchase.storekit file (used only for local Xcode testing). All IAPs are now in "Waiting for Review" status and are linked to the iOS app version 1.0.0 (11) in the “In‑App Purchases and Subscriptions” section. At the time of the original review, some IAPs were in "Developer Action Needed / Rejected" state, so I suspect queryProductDetails may have returned no products and the reviewer saw no action after tapping the button. My questions: When IAP products are in "Waiting for Review" and linked to the app version, should queryProductDetails return them during App Review / TestFlight, or do they need to be fully approved first? Is there any additional configuration required so that the subscribe button reliably shows the App Store purchase sheet for reviewers (for example, any specific StoreKit / sandbox settings)? Are there recommended best practices to show a clearer error state when queryProductDetails returns no products, so that App Review understands this is a configuration / IAP-status issue rather than a UI bug? Any guidance from Apple engineers or other developers who faced a similar situation would be greatly appreciated. Thanks in advance.
Replies
0
Boosts
0
Views
233
Activity
Nov ’25
Verification failed with status INVALID_CHAIN_LENGTH
我正在通过集成app-store-server-library-java来实现 iap服务端校验。我参照了官网提供的Verification Usage 的代码,运行的时候异常信息如下: at com.apple.itunes.storekit.verification.ChainVerifier.verifyChainWithoutCaching(ChainVerifier.java:98) at com.apple.itunes.storekit.verification.ChainVerifier.verifyChain(ChainVerifier.java:71) at com.apple.itunes.storekit.verification.SignedDataVerifier.decodeSignedObject(SignedDataVerifier.java:186) at com.apple.itunes.storekit.verification.SignedDataVerifier.verifyAndDecodeTransaction(SignedDataVerifier.java:72) 我的代码如下: import com.apple.itunes.storekit.model.ResponseBodyV2DecodedPayload; import com.apple.itunes.storekit.verification.SignedDataVerifier; import com.apple.itunes.storekit.verification.VerificationException; import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.DecodedJWT; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.Base64; import java.util.Set; public class ExampleVerification { public static void main(String[] args) throws FileNotFoundException { String bundleId = "com.example"; Environment environment = Environment.SANDBOX; Set<InputStream> rootCAs = Set.of( new FileInputStream("AppleRootCA-G3.cer"), new FileInputStream("AppleRootCA-G2.cer") ); Long appAppleId = null; // appAppleId must be provided for the Production environment SignedDataVerifier signedPayloadVerifier = new SignedDataVerifier(rootCAs, bundleId, appAppleId, environment, true); String appTransactionJWS = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IkFwcGxlX1hjb2RlX0tleSIsIng1YyI6WyJNSUlCeXpDQ0FYR2dBd0lCQWdJQkFUQUtCZ2dxaGtqT1BRUURBakJJTVNJd0lBWURWUVFERXhsVGRHOXlaVXRwZENCVVpYTjBhVzVuSUdsdUlGaGpiMlJsTVNJd0lBWURWUVFLRXhsVGRHOXlaVXRwZENCVVpYTjBhVzVuSUdsdUlGaGpiMlJsTUI0WERUSTFNRFl3TXpFeE1UQXdNRm9YRFRJMk1EWXdNekV4TVRBd01Gb3dTREVpTUNBR0ExVUVBeE1aVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEVpTUNBR0ExVUVDaE1aVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCTnZZZ3o1MW1CbEMweE5McW9rMUJCcithRWJEb1ZEeVkyaVRsejZsK1JjYVR4QStVY2ptMjBESTNncFFlM280a2doRGxSbGowdEo1enBGUHgyQWR2VCtqVERCS01CSUdBMVVkRXdFQlwvd1FJTUFZQkFmOENBUUF3SkFZRFZSMFJCQjB3RzRFWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRBT0JnTlZIUThCQWY4RUJBTUNCNEF3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUloQU40bUJWTHBoZkpjYjdweHF2b09XcjkyK1czYU5LRG9pazV5Vk9BT0NEVmxBaUFYWVF0czJubWZGMStGYzlSODJHXC96QWhaVU00aDNTXC9VdFE4Q1lPS2p3ZlE9PSJdfQ.eyJhcHBsaWNhdGlvblZlcnNpb24iOiIxIiwib3JpZ2luYWxQdXJjaGFzZURhdGUiOjAsImJ1bmRsZUlkIjoiYnJpZ2h0LnVuaWhhbmQuY24iLCJhcHBUcmFuc2FjdGlvbklkIjoiMCIsImRldmljZVZlcmlmaWNhdGlvbiI6IlRYdGRvMWZtNDhQVDdXUUh5cHU4K2l3TW55YmNoTTNNeG5XUnhOR1JqSFhQQnVqMXdUaldcL05zN3JtUmJlQTd3IiwicmVjZWlwdFR5cGUiOiJYY29kZSIsIm9yaWdpbmFsQXBwbGljYXRpb25WZXJzaW9uIjoiMSIsInJlcXVlc3REYXRlIjoxNzYxMDM1OTMzNTE3LCJvcmlnaW5hbFBsYXRmb3JtIjoiaU9TIiwicmVjZWlwdENyZWF0aW9uRGF0ZSI6MTc2MTAzNTkzMzUxNywiZGV2aWNlVmVyaWZpY2F0aW9uTm9uY2UiOiI1ZDhmNzM5Mi01N2YwLTQyM2YtOTMzNy1hZDQ0YTk5MDM4Y2EifQ.2ZO5xsx-yywP4IyaDz4KQ3mq181ZGwlX2uANSm-kHq50KIdMMUDveMsCrcZmHdzLH2rpfPsXKaIMdM25Hdcuuw"; DecodedJWT unverifiedJWT = JWT.decode(appTransactionJWS); String header = unverifiedJWT.getHeader(); System.out.println(new String(Base64.getDecoder().decode(header))); try { signedPayloadVerifier.verifyAndDecodeTransaction(appTransactionJWS); } catch (VerificationException e) { e.printStackTrace(); } } } 查看了ChainVerifier.java 源代码,发现 private static final int EXPECTED_CHAIN_LENGTH = 3; // <--- 关键常量 // ... PublicKey verifyChainWithoutCaching(String[] certificates, boolean performRevocationChecking, Date effectiveDate) throws VerificationException { // ... 解析证书代码 ... if (parsedCertificates.size() != EXPECTED_CHAIN_LENGTH) { throw new VerificationException(VerificationStatus.INVALID_CHAIN_LENGTH); // <--- 抛出异常点 } // ... 后续验证代码 ... } appTransactionJWS是来自客户端的沙盒环境。 我发现沙盒环境的jws总是包含一个证书,而后端验证又必须要求三个证书,请问这个问题如何解决。
Replies
0
Boosts
0
Views
88
Activity
Oct ’25
StoreKit JWT Verification Failing - Expired Certificate in x5c Chain Body
Starting around October 12, 2025 at 19:51 UTC, we're seeing intermittent failures when verifying StoreKit transaction JWTs. The issue appears to be related to certificate expiration in the x5c chain in the JWT tokens provided by Apple. What We're Seeing Some JWTs are being signed with different certificates. Some work, some fail certificate validation. Old Certificate (Expired - causing failures): Subject: Prod ECC Mac App Store and iTunes Store Receipt Signing Issuer: Apple Worldwide Developer Relations Certification Authority (G6) Serial: 166451396673336810269824643773700992094 Valid From: 2023-09-12 19:51:53 UTC Valid To: 2025-10-11 19:51:52 UTC ❌ EXPIRED New Certificate (Valid - working): Subject: Prod ECC Mac App Store and iTunes Store Receipt Signing Issuer: Apple Worldwide Developer Relations Certification Authority (G6) Serial: 95385247725814954943813376527885434295 Valid From: 2025-09-19 19:44:51 UTC Valid To: 2027-10-13 17:47:23 UTC ✓ VALID Current Status Most JWTs use the new valid certificate. Some JWTs still use the expired certificate. This appears inconsistent/random. I don't know if it's an issue with some of Apple's servers, or an issue with StoreKit on-device cache, but seems to me like a bug on Apple's side either way. Are we missing something? Is this a known issue? Any guidance or timeline from Apple would be greatly appreciated, as this is blocking legitimate paying users.
Replies
0
Boosts
0
Views
291
Activity
Oct ’25
"StoreKit Testing in Xcode" certificate is not trusted on iOS 26
Hello. I have setup a StoreKit testing in the app that was and still is perfectly working on iOS 18. Unfortunately when run on iOS 26 the following error gets printed in the console after calling Transaction.currentEntitlement(for:) method: Failed to verify certificate chain due to client recoverable failure: Error Domain=NSOSStatusErrorDomain Code=-67843 "“StoreKit Testing in Xcode” certificate is not trusted" UserInfo={NSLocalizedDescription=“StoreKit Testing in Xcode” certificate is not trusted, NSUnderlyingError=0x109de7e10 {Error Domain=NSOSStatusErrorDomain Code=-67843 "Certificate 0 “StoreKit Testing in Xcode” has errors: Root is not trusted;" UserInfo={NSLocalizedDescription=Certificate 0 “StoreKit Testing in Xcode” has errors: Root is not trusted;}}} I'm not seeting any StoreKit Testing certificates in phone's certificate trust settings. This test was performed on iOS 26.0 (23A341) with app built in Xcode 16.4. FB20339145
Replies
1
Boosts
0
Views
176
Activity
Nov ’25
Best practice to prevent users from switching between subscriptions in the same group?
Hello everyone, For example, our app currently has one subscription group in App Store Connect with 5 plans (2 annual, 2 monthly, and 1 quarterly). By default, users can go into Apple Subscriptions in Settings and freely switch between all of these plans. However, our business requirement is to only allow users to stay on one annual plan and one quarterly plan. We don’t want them to switch to the other plans. My questions are: Is there any best practice or recommended approach to restrict subscription switching within the same group? Would removing the unwanted products from sale be the correct approach, or are there any risks/downsides with this method? Has anyone faced a similar situation and found a practical solution? Any guidance or shared experience would be greatly appreciated. Thanks!
Replies
4
Boosts
0
Views
164
Activity
Sep ’25
Unexpected notificationType in App Store Server Notifications V2 when free trial ends
In my app’s IAP products, before enabling free trials, the App Store Server Notifications V2 callbacks all returned the correct notificationType. For auto-renewable subscriptions, when they were about to expire, the notificationType was either DID_RENEW or EXPIRED. A small number of cases(DID_FAIL_TO_RENEW) failed to renew due to billing issues, which was expected. However, after I enabled a 7-day free trial for the auto-renewable products, I noticed that in the App Store Server Notifications V2 callbacks, almost all users (except those who manually turned off auto-renewal) received notificationType = DID_FAIL_TO_RENEW. According to the documentation, DID_FAIL_TO_RENEW indicates a billing issue renewal failure, but in this case it seems like all renewals are being marked as failed. I’ve observed that for users who cancel during the free trial, the callbacks look normal: first a DID_CHANGE_RENEWAL_STATUS notification, then an EXPIRED notification when the trial ends. That flow seems correct. However, for users who do not cancel the trial, almost all callbacks show DID_FAIL_TO_RENEW. Does this mean every remaining user has a billing issue? I also noticed on the Developer Forums that other developers have reported the same issue — receiving a large number of DID_FAIL_TO_RENEW notifications specifically when free trials convert to paid subscriptions: https://aninterestingwebsite.com/forums/search?q=DID_FAIL_TO_RENEW Could someone clarify: under normal circumstances, when a free trial transitions to a paid subscription and fails, should the App Store Server Notifications V2 notificationType indeed be DID_FAIL_TO_RENEW, or should it be a different type(e.g. EXPIRED)? And is it expected behavior that almost all free trial conversions return DID_FAIL_TO_RENEW?
Replies
3
Boosts
0
Views
195
Activity
Sep ’25
how does auto sync App server price to IAP price
After I configured a consumable subscription(In App Purchases) on the apple platform, when I change the price in the App's server, is there any existing apple Api support to change the price on the apple platform? Of course, I know I need to resubmit.
Replies
0
Boosts
0
Views
75
Activity
Jun ’25