Hellow.
I have integrated the In-App Purchase feature into my app using StoreKit2.
I execute the product purchase request using Product.purchase(options:).
https://developer.apple.com/documentation/storekit/product/3791971-purchase
The development is complete, and testers are currently testing the app.
They are not using Xcode.
They are testing either with a sandbox test account by distributing the archive through debugging methods, or by downloading the app from TestFlight.
I would like to test the scenario where Product.purchase(options:) returns .pending on a sandbox test account or TestFlight.
I understand that Product.purchase(options:) returns .pending if the "Ask to Buy" option is enabled on a child account.
However, there is no "Ask to Buy" option in the sandbox test account settings.
When I test with a child account that has "Ask to Buy" enabled on TestFlight, Product.purchase(options:) does not return .pending but instead returns .userCancelled.
I am wondering how I can test the case where Product.purchase(options:) returns .pending in a sandbox test account or on TestFlight.
Thank you.
StoreKit
RSS for tagSupport in-app purchases and interactions with the App Store using StoreKit.
Post
Replies
Boosts
Views
Activity
Hello,
I am currently integrating with the iTunes Sandbox environment, specifically with APIs like:
https://sandbox.itunes.apple.com/inApps/v1/subscriptions/12345
However, I consistently receive a 503 - Http/1.1 Service Unavailable error in response. Below is the typical response I get:
<html><body><b>Http/1.1 Service Unavailable</b></body></html>
I've tried sending requests from different IP addresses and servers, but all attempts result in the same 503 error. Could anyone provide guidance on whether this is a temporary issue with the sandbox environment or if there is something I need to adjust in my setup?
Any help would be greatly appreciated!
Thank you.
The in-app payment system is confusing.
In-app payment is initially reviewed without being attached (however, in-app purchase is applied)
Once the review is complete (up to in-app payment), the in-app payment module is attached for testing
It is reviewed with the attached module
Isn't this the logic? If there is another logic, please let me know. Thank you.
My server receives App Store Server Notification v2 notifications. Recently, it has been receiving notifications for offer codes under offers that have been deactivated. The custom code value may be the same in the active offer as the deactivated offer.
When a user redeems the custom offer code for a deactivated offer, the notification payload's offerIdentifier value now contains the UUID associated with that offer in the URL in App Store Connect
(like https://appstoreconnect.apple.com/apps/my-app-id/distribution/subscriptions/my-subscription-id/pricing/offer-codes/offer-uuid)
The notification payload contains:
{"offerIdentifier": "offer-uuid"}
instead of
{"offerIdentifier": "<My offer identifier from App Store Connect>"}
Is it possible for a deactivated offer to be redeemed? If not, is this a known issue?
We need the actual offer identifier to understand which offers users are redeeming. Is this replacement with a UUID a known issue?
Issue:
Streamlined purchasing cannot be turned off because your latest approved binary doesn’t include the required StoreKit APIs.
For this, I tried selecting the option "Turn Off Streamlined Purchasing" in App Store Connect, and I received this error.
What I’ve Done So Far:
I have added the StoreKit file in Xcode.
Still, I get the error when submitting my app binary.
In app capability added in xcode
My Setup:
React Native
Xcode version
StoreKit2 integration using React Native IAP sdk (react-native-iap)
Code:
Here is the code I am using to set up the in-app purchase system:
RNIap.setup({ storekitMode: 'STOREKIT_HYBRID_MODE' });
const connection = await RNIap.initConnection();
if (connection) {
if (isAndroid) {
await RNIap.flushFailedPurchasesCachedAsPendingAndroid();
} else {
await RNIap.clearTransactionIOS();
}
if (isIos) {
await RNIap.IapIosSk2.sync()
.then(() => {})
.catch((error) => {
console.log('Error IapIosSk2 IAP:', error);
});
}
await getAllSubscriptionList();
await getPlanStructure();
//For Subscription
await RNIap.requestSubscription({
sku: sku,
subscriptionOffers: [{ sku, offerToken }],
});
//For Consumable Products
await RNIap.requestPurchase({
sku: sku,
andDangerouslyFinishTransactionAutomaticallyIOS: false,
});
}
Steps I’ve Taken to Troubleshoot:
Cleaned and rebuilt the project.
Checked App Store Connect settings for in-app purchases.
Question:
How can I resolve this error? Are there additional steps required to correctly enable StoreKit2 in a React Native project?
Additional Info:
Any advice or guidance on this issue would be greatly appreciated.
The error below started today 10/17/2024 (on iOS 18.1 beta 5/6/7 at least) in StoreKit sandbox testing. Outside code/app, I can't even login to the test account in settings->AppStore->Sandbox Account (goes through email/phone confirmation and then silently fails).
Tried a different password for a new sandbox test account with no success...Anyone experiencing this?
P.S. Status page (https://developer.apple.com/system-status/) shows some related outage from two days ago, but no current problem.
Error: Purchase did not return a transaction: Error Domain=ASDErrorDomain Code=530 "(null)" UserInfo={NSUnderlyingError=0x3009ca040 {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=306 "Reached max retry count Task reached max retry count (3 / 3);" UserInfo={AMSDescription=Reached max retry count, AMSURL=..., NSDebugDescription=Reached max retry count Task reached max retry count (3 / 3);, AMSFailureReason=Task reached max retry count (3 / 3);, AMSStatusCode=200}" ), AMSDescription=Authentication Failed, NSDebugDescription=Authentication Failed The authentication failed., AMSFailureReason=The authentication failed.}}, storefront-country-code=USA, client-environment-type=Sandbox}
We have an application that currently supports iOS 16, and in order to disable Streamlined Purchasing, the latest approved binary must include the necessary StoreKit APIs.
The StoreKit API includes the PurchaseIntent, which is available from iOS 16.4.
Our question is: Can we disable Streamlined Purchasing for our app? If yes, how will it work for devices running iOS versions earlier than 16.4?
Hello team, why do I occasionally encounter (java. lang. TimeException: com. apple. itunes. storekit. verification. Verification Exception: Verification failed with status INVALID_CHAIN) when calling (BaseAppStoreServerAPIClient. getTransactionInfo) using (app-store-ser-library 3.1.0) in Java
I have 6 Mac App Store apps. They're all upfront paid, with no IAP, and they've all used the same on-device Mac App Store receipt validation code for years, which returns 173 in main() if there's not a valid receipt. Incidentally, the apps are entirely Objective-C.
I've just learned that if I compile an app with Xcode 16 and the macOS 15 SDK, I get the alert "exit(173) Not Available" when the app returns 173 on macOS 15 Sequoia. The rest of the alert text says, "The exit(173) API is no longer available. You can use Transaction.all or AppTransaction.shared to verify in-app purchases instead."
I have several questions:
Why was this done?
Where is this behavior change documented?
What are my options, given the above description of my apps?
We are utilizing the StoreKit external purchase link within the app and have configured the necessary keys in the entitlements according to the documentation. The Info.plist file has also been updated with the required key and a single destination URL, following the guidelines from this documentation. However, when we click the link in the app, it redirects to the default browser, and the in-app system disclosure sheet does not appear.
Should the in-app disclosure sheet appear automatically, or do we need to design and implement it ourselves?
What happens after 12 renewals? Does the subscription expire completely? The next day when I try to manage settings for my sandbox account it says it cannot connect.
I cannot see the status of subscription in:
Settings-> App Store -> Sandbox Account -> Manage
Logging out and logging in of my regular account does not fix that.
When I login with a different non-testflight sandbox account I can finally edit those settings. There are some missing details in documentation explaining testflight sandbox accounts. Do these accounts stop working after 12 auto-renewals? I need more specific details in order to ensure subscriptions will work properly during production.
Hello folks.
Hope you have a great day.
I have a question on your Get Notification History API.
Reference : https://developer.apple.com/documentation/appstoreserverapi/get_notification_history
According to NotificationHistoryRequest document( https://developer.apple.com/documentation/appstoreserverapi/notificationhistoryrequest )
startDate parameter allows past 180 days from current date.
However my test result is little bit different.
Could you help me this case?
I'll give detailed request/response during testing.
Request body
{"startDate":1714364482159,"endDate":1729048882326,"notificationType":"REFUND","notificationSubtype":null,"transactionId":null,"onlyFailures":null}
Response
Header
server: daiquiri/5
date: Wed, 16 Oct 2024 03:21:38 GMT
content-type: application/json
content-length: 110
x-apple-jingle-correlation-key: SBDV5UOM7YGOJCYS2M4S2X67IE
x-apple-request-uuid: 90475ed1-ccfe-0ce4-8b12-d3392d5fdf41
b3: 90475ed1ccfe0ce48b12d3392d5fdf41-06ee3a4acafcff41
x-b3-traceid: 90475ed1ccfe0ce48b12d3392d5fdf41
x-b3-spanid: 06ee3a4acafcff41
apple-seq: 0.0
apple-tk: false
apple-originating-system: CommerceGateway
x-responding-instance: CommerceGateway:20104:::
apple-timing-app: 8 ms
strict-transport-security: max-age=31536000; includeSubDomains
x-daiquiri-instance: daiquiri:47578001:st44p00it-hyhk15014801:7987:24RELEASE221:daiquiri-amp-commerce-clients-ext-002-st
body
{"errorCode":4000012,"errorMessage":"Invalid request. The start date is earlier than the allowed start date."}
As you see, request sent at Wed, 16 Oct 2024 03:21:38 GMT,
and start date is 1714364482159 which is Mon, 29 April 2024 04:21:22 (milli second).
The difference between the two dates is 170 days.
Could you help me this this case?
If I misunderstood on your API, please let me know.
Thank you.
I am currently exploring the implementation of win-back offer in my app to encourage lapsed subscribers to re-subscribe. I plan to use the automatic win-back offer sheet that display to eligible customers upon app launch, as described in the documentation. However, I am unclear about the specific iOS version requirements needed to support this feature effectively.
Could someone clarify the minimum iOS, iPadOS, and macOS versions required for these automatic win-back offer sheet?
We are currently upgrading our server to IPv6, and when we tested it, we found that the following API, which verifies receipts when charging, resulted in an error.
verifyReceipt API
https://buy.itunes.apple.com/verifyReceipt
https://sandbox.itunes.apple.com/verifyReceipt
This is currently a deprecated API, so when we tried curling the URL to the "App Store Server API" and using nslookup, we also got an error.
App Store Server API
https://api.storekit.itunes.apple.com
https://api.storekit-sandbox.itunes.apple.com
The error was that the address could be retrieved from the domain in IPv4, but the address could not be retrieved in IPv6.
"https://www.apple.com" and other sites could be retrieved without any problems in IPv6.
Is this API not compatible with IPv6?
If this is not possible, the only way to verify receipts and the legitimacy of charges seems to be to perform verification on the device,
but as this requires a lot of work, I would like to know if there is a better way.
We have not been receiving notifications for Apple's recurring monthly charges and user refunds since 4 PM on October 10th.
apple Id:1325419855
server notification endpoint: https://xyks-open.yuanfudao.com/leo-cm-third-pay/api/apple-iap/pay-sub-notify/2
Server notification version: version1
Additionally, we have another app, which is also using server notification version1, and it is able to receive notifications.
Hello,
We have received three CONSUMPTION_REQUEST notifications for the same transactionId.
We sent a response to each CONSUMPTION_REQUEST notification to the API and received a 202 Accepted response.
Why do we receive multiple CONSUMPTION_REQUEST notifications even though we have sent a ConsumptionRequest to the Apple Store Server API? Do we need to respond to each CONSUMPTION_REQUEST for the same transactionId?
Thanks
We have subscriptions for our app that are in the "waiting for review" state. The subscriptions page has the message "Your first subscription must be submitted with a new app version. Create your subscription, then select it from the app’s In-App Purchases and Subscriptions section on the version page before submitting the version to App Review."
However I don't see any section in the app to enter this. It seems to be a similar issue to what is being described at https://stackoverflow.com/questions/73098652/app-store-connect-in-app-purchase-and-subscriptions-section-missing?rq=2, but I have all requirements fulfilled; all compliance and tax forms have been completed, and I have made changes to my subscriptions in response to app review.
I did add these subscriptions to my app in a previous round in the approval process, and I thought that was why it wasn't appearing now. However, my app is now live and users are not seeing the subscriptions options when they try to enter the app (which had previously worked in sandbox mode)
Hello,
We’re implementing subscription validation on our server using Apple’s latest APIs and have run into a few issues and uncertainties. We are transitioning away from the deprecated /verifyReceipt endpoint and are now using the /inApps/v1/subscriptions/{transactionId} API. However, we have questions about how to handle transaction validation, particularly around older transactions and notification behavior in the sandbox environment. Additionally, we are facing challenges with Apple Server Notifications, especially regarding when we should expect to be notified of subscription cancellations or expirations.
Below are the specific questions we hope the community can help us with:
Question 1:
We are using the inApps/v1/subscriptions/{transactionId} API to retrieve the subscription status and validate subscriptions on our server side, since the /verifyReceipt endpoint has been deprecated. This API always returns the last transaction for a subscription. Should we only validate the latest transaction, and is it guaranteed that even if we provide an older transaction ID, the API will always return the most up-to-date transaction information?
https://developer.apple.com/documentation/appstoreserverapi/get_all_subscription_statuses/
https://developer.apple.com/documentation/appstorereceipts/verifyreceipt
Question 2:
In the sandbox environment, we encountered an empty response when trying to validate a transaction ID. We are attempting to validate older transactions made by the user to keep our system and store subscriptions in sync. Could you provide guidance on why this might be happening and how we can validate older transactions?
Question 3:
We are using Apple Server Notifications to track subscription renewals and changes. However, when a user disables auto-renewal, we do not receive a webhook notification when the subscription actually expires or is canceled. We only get notified when the user changes their renewal preferences. Should we expect a separate notification when the subscription fully expires or is canceled, or is this behavior expected?
https://developer.apple.com/documentation/appstoreservernotifications/notificationtype
The application has changed from paid purchase to free use. We need to obtain the previous player's purchase records to unlock the paid content.
//Here is the code for the client to obtain the player's payment information:
NSURL * receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]){
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
NSString *receiptUrlString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
NSLog(@"requestAppStoreReceipt receiptUrlString = %@", receiptUrlString);
} else {
// 如果凭证为空,则再发一次凭证请求
SKReceiptRefreshRequest *refreshReceiptRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{}];
refreshReceiptRequest.delegate = self;
[refreshReceiptRequest start];
NSLog(@"requestAppStoreReceipt 如果凭证为空,则再发一次凭证请求!");
}
//The following is the server-side parsing code:
Path filePath = Path.of("SubscriptionKey_ABCDEFGHIJ.p8");
String encodedKey = Files.readString(filePath);
Environment environment = Environment.SANDBOX;
AppStoreServerAPIClient client = new AppStoreServerAPIClient(encodedKey, keyId, issuerId, bundleId, environment);
String appReceipt = "MIIcs...";
ReceiptUtility receiptUtil = new ReceiptUtility();
String transactionId = receiptUtil.extractTransactionIdFromTransactionReceipt(transactionReceipt);
System.out.println(transactionId);
if (transactionId != null) {
long now = System.currentTimeMillis();
TransactionHistoryRequest request = new TransactionHistoryRequest()
.sort(TransactionHistoryRequest.Order.DESCENDING)
.revoked(false)
.productTypes(List.of(TransactionHistoryRequest.ProductType.CONSUMABLE));
HistoryResponse response = null;
List<String> transactions = new LinkedList<>();
do {
String revision = response != null ? response.getRevision() : null;
response = client.getTransactionHistory(transactionId, revision, request, GetTransactionHistoryVersion.V2);
transactions.addAll(response.getSignedTransactions());
} while (response.getHasMore());
Set<InputStream> rootCAs = Set.of(
new FileInputStream("AppleComputerRootCertificate.cer"),
new FileInputStream("AppleIncRootCertificate.cer"),
new FileInputStream("AppleRootCA-G2.cer"),
new FileInputStream("AppleRootCA-G3.cer")
);
Long appAppleId = 1234567899L; // appAppleId must be provided for the Production environment
System.out.println(transactions.size());
SignedDataVerifier signedPayloadVerifier = new SignedDataVerifier(rootCAs, bundleId, appAppleId, environment, true);
for (String notificationPayload : transactions) {
try {
AppTransaction payload = signedPayloadVerifier.verifyAndDecodeAppTransaction(notificationPayload);
System.out.println(payload);
} catch (VerificationException e) {
e.printStackTrace();
}
}
}
//Return result analysis:
JWSTransactionDecodedPayload{originalTransactionId='2000000641683476', transactionId='2000000641683476', webOrderLineItemId='null', bundleId='', productId='', subscriptionGroupIdentifier='null', purchaseDate=1719566962000, originalPurchaseDate=1719566962000, expiresDate=null, quantity=1, type='Consumable', appAccountToken=null, inAppOwnershipType='PURCHASED', signedDate=1728885967093, revocationReason=null, revocationDate=null, isUpgraded=null, offerType=null, offerIdentifier='null', environment='Sandbox', storefront='CHN', storefrontId='143465', transactionReason='PURCHASE', price=6000, currency='CNY', offerDiscountType='null', unknownFields=null}
We have develop according to the following document on our end:
https://developer.apple.com/documentation/foundation/nsbundle/1407276-appstorereceipturl#4098404
https://developer.apple.com/documentation/appstoreserverapi/get_transaction_info/
https://developer.apple.com/documentation/appstoreserverapi/data_types
We would like to know if the solutions in the document can be used to solve the problems we encountered?
Is there a problem with our method of parsing bills that prevents us from obtaining the necessary information?
<SKPaymentQueue: 0x30217e480>: Payment completed with error: Error Domain=ASDServerErrorDomain Code=3904 "优惠已不再提供" UserInfo={storefront-country-code=CHN, AMSServerErrorCode=3904, client-environment-type=Sandbox, NSLocalizedFailureReason=优惠已不再提供}