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

The operation could not be completed. (Error 2 in SKErrorDomain.) during subscription upgrade
I have a renewing monthly subscription in my app and recently added upgrade possibilities to yearly and 6 month subscriptions. Those new subscriptions were reviewed, approved and published to App Store. I'm showing a modal for users in the app from where they can upgrade their subscription. Upgrading was tested with real devices on Sandbox and TestFlight. There has been successful purchases through the in app modal in production app, and directly upgrading from App Store. However, for some users there seems to happen a failed transaction with paymentCancelled error code during the upgrade. The IAP is still successful, their subscription is upgraded and they haven't voluntarily canceled the IAP. The localized description of the error is "Toimintoa ei voitu suorittaa. (Virhe 2 kohteessa SKErrorDomain.)" which translates to "The operation could not be completed. (Error 2 in SKErrorDomain.)" These users have various iPhones (iPhone 12 Pro, iPhone 14 Pro, iPhone 15 Pro, iPhone 16 Pro) with up to date iOS versions (>= 18.3.1). I'm receiving DID_CHANGE_RENEWAL_PREF (UPGRADE) server notification of these purchases on my server. I haven't been able to reproduce this error myself. Any ideas why StoreKit might fail the transaction with paymentCancelled error but still successfully upgrade the subscription?
0
0
134
Mar ’25
Does the Apple Store support variable recurring payments like Stripe?
Stripe offers variable payment structures, also known as "irregular recurring payments," which include: Usage-based billing: Charges amounts based on usage during the billing cycle (e.g., minutes used or energy consumed). Quantity-based billing: Charges a pre-agreed amount based on quantity (e.g., number of users in a subscription). Is it possible to implement this type of billing in the Apple Store for apps? How would variations in amounts be handled?
0
0
329
Jan ’25
Multiple subscriptions of the same product
Hello, I have an app where the users can connect their vehicles to an API and record trips and so on. Currently I have one subscription, where the user can add as many vehicles as he wants. But now I want (need) to change this to one vehicle per subscription. So if the user want to add two vehicles, he need to subscribe two times to the same product. How can I handle this? Thanks, Arangoool
0
0
267
Sep ’24
inApps/v2/history/ of AppleStoreServerAPI
https://developer.apple.com/documentation/appstoreserverapi/get-v2-history-_transactionid I would like to inquire about the detailed triggers for updating receipts in this API specification. Recently, I was using this API with sort=DESCENDING&revoked=false to retrieve the expiration date of the most recent receipt and determine the subscription status. However, for some reason, an old receipt with an earlier expiration date appeared as the first receipt, and I would like to know the reason for this. Can you provide information on what specific events or actions trigger the updating of receipts in this API? Also, regarding https://developer.apple.com/documentation/appstoreserverapi/status, will statuses 3 and 4 not be returned in the response unless the billing grace period is enabled in the App Store? -- Japanese こちらのAPI仕様ですが、どのようなトリガーでレシートが更新されるのか詳細に伺いたいです。 先日このAPIを使用してsort=DESCENDING&revoked=falseで一番最初のレシートの有効期限を取得して課金状態か判断していたのですが、どういったわけか一番最初のレシートに古い有効期限のレシートが入ってきたので理由を知りたいです。 また、https://developer.apple.com/documentation/appstoreserverapi/status のステータスはAppleStoreで請求猶予期間を有効化しないと3,4はレスポンスされませんか?
0
0
297
Jan ’25
Unable to make a sandbox purchasing with SubscriptionStoreView
Hi I'm writing my first in-app purchase app, and I'm trying to do some testing with sandbox accounts. I wrote my subscription page use SwiftUI SubscriptionStoreView. And I read the documentation it says : The sandbox account appears in Settings > App Store after the first time you use the device to attempt a purchase in a development-signed app. But I have no idea how to make a sandbox purchasing. Every time I click the subscription button it just making a purchase in xcode environment. Did I missed anything? What can I do to make a sandbox pruchasing?
2
0
482
Oct ’24
Issues with Integration of Promotional Offers in React Native app
Hi All, We are trying to integrate Promotional Offer in our app, We have a React Native app and are using react-native-iap for handling our in app purchases, as per the documentation we are generating signature in our BE and passing the proper details to the function as well, but for subscription request which have offer applied we are getting the apple pop up properly as well with offer details but when trying to subscribe it gives us SKErrroDomain: 12, for subscription without applying offer the subscription goes through but when we apply the offer we get the above error. Our app is currently in Development Stages and has not been sent for review sam for our subscription plans as well. Please let me know what could be the probable cause for this and help us resolve the issue. This is the code snippet of ours for the front end : export const buySubscription = async (subscriptionData: any) => { try { if (subscriptionData.offer_id) { const response = await getSubscriptionSignature( subscriptionData.productId, subscriptionData.offer_id, ); const offerData = response?.data; const offer = { identifier: offerData?.offer_id, keyIdentifier: offerData?.key_id, nonce: offerData?.nonce, signature: offerData?.signature, timestamp: Number(offerData?.timestamp), }; await requestSubscription({ sku: subscriptionData.productId, withOffer: offer, }); } else { await requestSubscription({ sku: subscriptionData.productId }); } } catch (err) { logger.error('Subscription error: ' + JSON.stringify(err)); throw err; } }; and 
from my python Backend which generates the signature:

def generate_signature(self, product_id: str, offer_id: str) -> dict: """ Generate signature for Apple StoreKit promotional offers. Args: product_id: The product identifier from App Store Connect offer_id: The promotional offer identifier Returns: dict: Contains signature and required metadata Reference: https://developer.apple.com/documentation/storekit/in-app_purchase/original_api_for_in-app_purchase/subscriptions_and_offers/implementing_promotional_offers_in_your_app """ try: # Generate UUID without dashes and use as nonce nonce = str(uuid.uuid4()) timestamp = get_current_time_ms() # milliseconds # Create the payload string in exact order required by Apple payload_components = [ self.bundle_id, # App Bundle ID self.key_id, # Key ID from App Store Connect product_id, # Product identifier offer_id, # Promotional offer identifier nonce, # UUID without dashes str(timestamp) # Current timestamp in milliseconds ] payload_str = "\u2063".join(payload_components) # Use Unicode separator logger.debug(f"Signing payload: {payload_str}") # Create SHA256 hash of the payload digest = hashes.Hash(hashes.SHA256()) digest.update(payload_str.encode('utf-8')) payload_hash = digest.finalize() # Sign the hash using ES256 (ECDSA with SHA-256) signature = self.private_key.sign( data=payload_hash, signature_algorithm=ec.ECDSA(hashes.SHA256()) ) # Encode signature in base64 signature_b64 = base64.b64encode(signature).decode('utf-8') logger.info(f"Generated signature for product {product_id} and offer {offer_id}") return { "key_id": self.key_id, # Changed to match Apple's naming "nonce": nonce, # UUID without dashes "timestamp": timestamp, # As integer "signature": signature_b64, # Base64 encoded signature "product_id": product_id, # Changed to match Apple's naming "offer_id": offer_id # Changed to match Apple's naming } except Exception as e: logger.error(f"Failed to generate signature: {str(e)}") raise HTTPException( status_code=500, detail=f"Failed to generate signature: {str(e)}" )
0
0
52
Apr ’25
Puchases / subscriptions issues
Hi, I've some issues on my application about purchases. My app is available for iOS and tvOS. Some part of my code is the same on both target (my purchase code is one of them). The issue is: Some users can't do a purchase on Apple TV (but not all and I can't reproduce) Some users purchased on a device and are not considered as pro on the other device (logged with same apple account). Again it's not all. Here is my store code
1
0
296
Jan ’25
question about App Store Server Notifications
I have 2 subscriptions, monthly and year first:DID_CHANGE_RENEWAL_PREF + DOWNGRADE: Customer downgrades a subscription within the same subscription group; The current subscribe is year;if i change to month;when the subscribe year was expire,Automatic renewal will change to month second:DID_CHANGE_RENEWAL_PREF+UPGRADE: Customer upgrades a subscription within the same subscription group. The current subscribe is month;if i change to year;I need to pay for the annual subscription immediately;and The subscription immediately switches to the annual third:DID_CHANGE_RENEWAL_PREF subtype is None Customer reverts to the previous subscription, effectively canceling their downgrade. what this mean? This is Test Env,month is five minutes;year is one hour ①My current subscription is an annual, startTime:2024-10-02 15:04:58 ,expireTime:2024-10-02 16:04:58 ②first DOWNGRADE to month,at 2024-10-02 15:14:16 ②after 38 minutes,I change to the annual subscribe;at 2024-10-02 15:52:00 in the end,the Notification purchaseDate 2024-10-02 15:52:00;expiresDate 2024-10-02 16:52:00; So When I get NotificationType=DID_CHANGE_RENEWAL_PREF,NotificationSubType=None,Do I need to create a new subscription for the users? Is it the latest notice of purchaseDate and expiresDate, for a year? The appleNotification Payload as follows: JWSTransactionDecodedPayload( originalTransactionId='2000000731045285', transactionId='2000000731088945', webOrderLineItemId='2000000076096676', bundleId='app.xxxx', productId='com.xxxx.365', subscriptionGroupIdentifier='21514251', purchaseDate=1727855520000, 2024-10-02 15:52:00 originalPurchaseDate=1727852699000, 2024-10-02 15:04:59 expiresDate=1727859120000, 2024-10-02 16:52:00 quantity=1, type=<Type.AUTO_RENEWABLE_SUBSCRIPTION: 'Auto-Renewable Subscription'>, rawType='Auto-Renewable Subscription', appAccountToken='fa37b7a2-2b0b-43cb-8fda-a1fb21168efe', inAppOwnershipType=<InAppOwnershipType.PURCHASED: 'PURCHASED'>, rawInAppOwnershipType='PURCHASED', signedDate=1727855526632, 2024-10-02 15:52:06 revocationReason=None, rawRevocationReason=None, revocationDate=None, isUpgraded=None, offerType=None, rawOfferType=None, offerIdentifier=None, environment=<Environment.SANDBOX: 'Sandbox'>, rawEnvironment='Sandbox', storefront='CAN', storefrontId='143455', transactionReason=<TransactionReason.PURCHASE: 'PURCHASE'>, rawTransactionReason='PURCHASE', currency='CAD', price=14990, offerDiscountType=None, rawOfferDiscountType=None) JWSRenewalInfoDecodedPayload( expirationIntent=None, rawExpirationIntent=None, originalTransactionId='2000000731045285', autoRenewProductId='com.xxxx.365', productId='com.xxxx.365', autoRenewStatus=<AutoRenewStatus.ON: 1>, rawAutoRenewStatus=1, isInBillingRetryPeriod=None, priceIncreaseStatus=None, rawPriceIncreaseStatus=None, gracePeriodExpiresDate=None, offerType=None, rawOfferType=None, offerIdentifier=None, signedDate=1727855526632, 2024-10-02 15:52:06 environment=<Environment.SANDBOX: 'Sandbox'>, rawEnvironment='Sandbox', recentSubscriptionStartDate=1727852698000, 2024-10-02 15:04:58 renewalDate=1727859120000, 2024-10-02 16:52:00 currency='CAD', renewalPrice=14990, offerDiscountType=None, rawOfferDiscountType=None, eligibleWinBackOfferIds=None)
0
0
416
Oct ’24
Inquiries about API SERVER Notification
Inquire the types of notifications that can occur in a SANDBOX environment Hello, WWDC 2024 is trying to conduct a test to receive notifications related to ONE_TIME_CHARGE, CONNSUMPTION_REQUEST, CONMSUMPTION_INFO, REFUND, and REFUND_DECLINED as described in the example of purchasing consumables, but as a result of the continuous search, I found that it is difficult to occur except for ONE_TIME_CHARGE. So, in order to verify only the business logic as shown below, we are testing only the business logic without actually calling the API after purchasing the test and saving the signaled Payload that we received in response to ONE_TIME_CHARGE. Can we actually request a refund for the test purchase and receive the corresponding notification and actually send the response? public void handleSignedNotification(String signedNotification) throws Exception { ResponseBodyV2DecodedPayload payload = signedDataVerifier.verifyAndDecodeNotification(signedNotification); NotificationTypeV2 type = payload.getNotificationType(); //For Apple Server Notification, only ONE_TIME_CHARGE notifications are enabled in the test environment, so for testing, change them as below to test whether they are running business logic type = NotificationTypeV2.REFUND; log.info("Apple NotificationType : {}", type); switch (type) { case CONSUMPTION_REQUEST: handleConsumptionRequest(payload); break; case REFUND: handleRefund(payload); break; case REFUND_DECLINED: handleRefundDeclined(payload); break; // For other necessary notifications, just take a log default: log.info("Unhandled notification: {}", type); } } Regarding the call of 'CONSUMPTION_INFO', which is the response of 'CONSUMPTION_REQUEST' Is there a value that WWDC 2024 must include when sending CONMSUMPTION_INFO, which is the response to CONNSUMPTION_REQUEST described in the refund example? I'm going to call the API with only sample provision and consumption like the sample code you introduced in the video. I was told to submit my refund preference within 12 hours, but can I submit it as UNDECLARED at first and use the method to express my intention? When I receive the notification, I will save it in the DB and save it in the administrator page of the service so that the administrator can choose. 2-1. Some of the materials I looked for are told that Apple can proceed with the refund even 12 hours ago, and to express your opinion as soon as I receive the notification, but I wonder if this is correct. If you get a notification as below, you should write whether you used it or not by referring to the consumption information. I think the customer said to check whether the data was provided when applying for a refund. Should I take it out of decodedTransaction, check the value, and just call it NO_PREFERENCE? I'd appreciate it if you could give me some advice. Below is a part of the code I implemented. private void handleConsumptionRequest(ResponseBodyV2DecodedPayload notification) throws Exception { // 1. transaction ID get String signedTransactionInfo = notification.getData().getSignedTransactionInfo(); JWSTransactionDecodedPayload decodedTransaction = signedDataVerifier.verifyAndDecodeTransaction(signedTransactionInfo); String transactionId = decodedTransaction.getTransactionId(); // 2. Extract the relevant transaction (The following example is an in-app payment and will be accumulated in two types of DBs, stored in one of the two) Sample sample = sampleService.findByAppleTransactionId(transactionId); Example example = exampleService.findByAppleTransactionId(transactionId); Boolean canRefund = false; // 3. Check consumption information if (sample != null) { canRefund = checkSampleStatusForApplePurchaseRefund(sample); } else if (example != null) { canRefund = checkExampleStatusForApplePurchaseRefund(example); } // 4. Create Refund Preferences RefundPreference refundPreference = determineRefundPreference(canRefund); // 5. Creating a ConsumptionRequest Object ConsumptionRequest request = new ConsumptionRequest() .refundPreference(refundPreference) .sampleContentProvided(true); log.info("forTest~ canRefund: {}", canRefund); log.info("forTest~ sample: {}", sample.toString()); log.info("forTest~ example: {}", example.toString()); log.info("forTest~ refundPreference: {}", refundPreference); log.info("forTest~ request: {}", request); // 6. Transfer to App Store (annotated with dummy requests that only confirm current business requests are going right) // appStoreServerAPIClient.sendConsumptionData(transactionId, request); }
0
0
302
Feb ’25
Wrong Product.displayPrice?
I am using StoreKit 2 for my products and noticed that users from Ukraine (for example) receive a wrong Product.displayPrice. The App Store works with USD but shows the local currency UAH. Any chance to display the correct currency (USD)?
1
0
54
Apr ’25
Reporting to External Purchase Server API when using alternative PSP in the EU
Dear community, Context My company operates in the European Union, where not so long ago there appeared the possibility to accept an ["Alternative Terms Addendum for Apps in the EU"] (https://developer.apple.com/contact/request/download/alternate_eu_terms_addendum.pdf), which, among others, gives us the possibility to use an alternative payment provider, other than Apple's In App Purchase PSP system (ref: Apple docs). My company did accept it and was granted the StoreKit External Purchase Entitlement (com.apple.developer.storekit.external-purchase) entitlement, with which we integrated a different PSP, so now we want to incorporate the reporting to Apple's External Purchase Server API. We are currently integrating with the External Purchase Server API and have encountered a couple of issues I would appreciate clarification on: Question 1 Is there a way to retrieve an overview or summary of the current subscription states on Apple’s servers as a result of the submitted reports to External Purchase Server API? Specifically, I would like to verify the expected outcomes before the monthly invoice is issued by Apple and to understand the subscription states for the test users I used during this process and for future reference as well. Question 2 In one scenario, I initiated a one-year subscription, and in the middle of its period, I submitted a RENEWAL for one month with a higher price. I expected the request to fail due to overlapping periods and/or pricing conflicts, but both submissions were accepted without error. Do you have an idea about: What happens at the end of the renewed month? Will the subscription continue with the renewed (higher) amount, revert to the original (lower) annual rate, or be canceled? Where can I view the final state and billing plan for that subscription? Thank you for your assistance, we are looking forward for any kind of help or information regarding this topic.
0
0
87
Apr ’25
Invalid currency symbol
Strange issue with currency display in subscription products Hi everyone, I'm facing a strange issue in my app where I use a subscription-based in-app purchase model. The products I created in App Store Connect are all in "Approved" status. I've tested with both RevenueCat and StoreKit, but the result is the same. Here are the products being loaded: Product loaded: weekly_product_id Display name: Weekly Pro Description: Weekly Pro Subscription Price: ₺229,99 Product loaded: annual_product_id Display name: Annual Pro Description: Annual Pro Subscription Price: ₺1.799,99 Even though I can see the correct prices and currency (Turkish Lira) in the Xcode debug console, on my real device the currency appears as Philippine Peso, as shown in the attached screenshot. Interestingly, in the iOS simulator, it's displayed in USD. I've double-checked and my device's region settings are set to Turkey. Any ideas on what could be causing this? And more importantly, how can I fix it? Thanks in advance!
3
0
72
Apr ’25
[macOS] Failed to perform in-line receipt renewal for application at path <path>
Hello, I have a customer who keeps getting an "app is damaged" error for a freshly downloaded app from the Mac App Store. The logs show the following lines: standard 12:58:40.390872+0200 storeuid Receipt Validation (at.EternalStorms.Yoink) Signature Check: PASS Bundle ID Check: PASS Bundle Version Check: PASS GUID Check: PASS Expiration Check: PASS standard 12:58:40.391649+0200 storelegacy StoreLegacy: Failed to perform in-line receipt renewal for application at path /Applications/Yoink.app : '(null)' The Mac in question is running macOS 12 Monterey - curiously, the customer has another Mac with that same system version and there it works just fine. What can be done to make this work again? Thank you, – Matthias
0
0
569
Oct ’24
Transaction.currentEntitlements failing every time
I am shown as being subscribed to our service in the Subscriptions list in settings yet when going to the Storekit2 page in my app it shows me as NOT being subscribed and is unresponsive. I select Restore Subscription, that grays briefly, asks for a password, then returns to blue and nothing else happens. Bouncing back and forth between monthly and yearly likewise gives no response. The Transaction.currentEntitlements seems to be empty so it thinks the user is not subscribed. I have unsubscribed, and resubscribed via the Settings page to no avail.
2
0
48
Apr ’25
In-App Purchase Refund Procedure
Hi everyone, I was just wondering if anyone could point me in the right direction on how to properly handle inquiries about refunding in-app purchases. I've seen multiple posts saying to direct them to Apple's support, but is there no other direct way of handling this? Also, does Apple not provide an API to interface with so you can issue refunds on behalf of the customer? Thanks.
3
0
575
Nov ’24
SKAdNetwork 3.0 postbacks are not being delivered.
Hi To test SkAdNetwork, I installed the profile from the documentation (https://developer.apple.com/documentation/storekit/testing-ad-attributions-with-a-downloaded-profile) on my ios device. and turned off and on Allow Apps to Request to Track to change the idfa value. In this state, I installed the app (bundle id: id436731843) using SKAdNetwork with the following values at April 8th AM 01 (UTC) SkAdnetwork version: 3.0 fidelity fidelity=0, nonce=b3346a51-f7b5-42a2-a508-775a62317c83, timestamp=1744076349671, signature=... fidelity=1, nonce=b3346a51-f7b5-42a2-a508-775a62317c83, timestamp=1744076349672, signature=... itunesitem=436731843 network=87u5trcl3r.skadnetwork sourceapp=1220307907 I was hoping to get a postback after , but after a day, the postback is still not delivered. Any idea why the postback is not delivered? Thank you
0
0
26
Apr ’25
Transitioning a freemium app to StoreKit 2
I'm currently working on transitioning to StoreKit 2. In order to see if my users are legacy users who purchased the app before I implemented an in-app purchase, I am trying to use the original purchase date for the app. Unfortunately, it's returning 0 seconds since 1970. func updateOriginalPurchaseStatus() async throws { let transaction = try await checkVerified(AppTransaction.shared) self.originalPurchaseVersion = transaction.originalAppVersion self.originalPurchaseDate = transaction.originalPurchaseDate } This is from the transaction: [3] = { key = "originalPurchaseDate" value = number (number = 0) } Currently trying to figure out when I actually purchased the app, but it might be as early as 2012. And I likely used a download code.
2
0
236
Mar ’25