In-App Purchase

RSS for tag

Offer extra content, digital goods, and features directly within your app using in-app purchases.

Posts under In-App Purchase tag

200 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

apple In App Purchase always response 20112
Hi,I am facing the 21002 issue.Have you went through it? If you have resolved it, please dive me a hand. Below is my code,and I Have tested in sand box environment. NSURL receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; if (![[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) { SKReceiptRefreshRequest receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil]; receiptRefreshRequest.delegate = self; [receiptRefreshRequest start]; return; } NSData data = [NSData dataWithContentsOfURL:receiptURL]; / receipt_data/ NSString *receipt_data = [data base64EncodedStringWithOptions:0]; After I test the purchase procedure, use the above receipt-data to verify on the server https://sandbox.itunes.apple.com/verifyReceipt,which always response 21002.I have no idea to deal with it.Thanks.
1
0
178
1d
Does a Delivery App with Commission & Subscription Plans Require IAP?
Hello everyone, I’ve been struggling with App Store Review regarding my delivery app, and I’m hoping someone here can shed light on whether my app actually requires In-App Purchase (IAP) under Apple’s guidelines. Here’s my situation: My app connects riders with delivery jobs. We have two rider payment models: Commission Plan (10%) – Riders don’t pay upfront, but 10% is deducted per completed delivery. Fixed Subscription Plan – Riders pay a fixed monthly fee in advance, which allows them to keep 100% of their delivery earnings. The challenge: Apple Review has rejected my app multiple times, insisting that I must use IAP for these payments. I’ve written back and forth with them several times explaining that: The commission plan isn’t a digital feature, it’s simply a revenue share from physical delivery work. The fixed plan is more like a business service fee for using the platform, not for unlocking content or digital features. Despite this, the Review Team continues to state that my app “requires IAP.” My questions are: Based on Apple’s official guidelines, do apps like mine (delivery/logistics with commission/subscription for riders) really fall under IAP rules? Has anyone here had a similar issue with a marketplace or service-based app? If this does require IAP, what’s the reasoning — since the payments are tied to real-world services, not digital content? I’d appreciate advice, shared experiences, or even clarification from someone who has successfully passed review with a similar model. Thanks in advance!
0
0
187
2d
In-App Purchase Failing
hello, I have an app with In-App purchase set up, it works fine in TestFlight on an iOS device but the download from the App Store gives me a transaction failed message. I tried to replicate in the simulator but from what I've read is that you can't do in-app purchase testing in the simulator. Perhaps I'm wrong here but I've tried multiple sandbox accounts, etc.. and still get an unusual "...password can't be reused..." error (pasted below) in the debug description. I'm curious if anyone else has dealt with this scenario and if so how did you go about researching/identifying the issue and/or resolving it. Partial error message: <SKPaymentQueue: 0x60000000e370>: Payment completed with error: Error Domain=ASDErrorDomain Code=530 "(null)" UserInfo={NSUnderlyingError=0x600000c74750 {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.}"
1
0
145
4d
Implementing In-App Purchases with Capacitor (Currently Using Stripe)
Hello everyone, I’m currently working on an iOS app built with Capacitor(5.0.6) with Cordova and vanilla JS. At the moment, my app uses Stripe to handle payments — on iOS I open a Safari View Controller so users can pay with Apple Pay or their credit card. However, I need to migrate to Apple’s In-App Purchase system to comply with App Store policies. I would really appreciate some guidance or a step-by-step outline on the following: The exact process to enable and configure In-App Purchases in Xcode and App Store Connect for a Capacitor-based app. How to properly set up local testing with StoreKit (or any other recommended approach) before submitting to review. Best practices for integrating In-App Purchases when using RevenueCat, since I’m considering it to simplify the implementation. Any pitfalls or gotchas that I should be aware of during the first submission (for example, the requirement to include the first IAP with a new app version). My goal is to fully comply with Apple’s policies and provide a smooth user experience for iOS users. I’m open to suggestions on frameworks, workflows, or tools that could make this easier. Thank you in advance for any help or examples you can share!
0
0
77
1w
App rejected due to 3.1.1 - Business - Payments - In-App Purchase
Guideline 3.1.1 - Business - Payments - In-App Purchase, as my app currently allows users to purchase digital content (courses) using an external payment system. I am working to comply with Apple’s requirements by integrating In-App Purchase for all digital content within the app. My app contains approximately 100,000 individual courses, and I am seeking guidance on how to effectively implement In-App Purchase for such a large number of items. Creating 100,000 separate In-App Purchase products in App Store Connect seems impractical due to the scale and potential limitations. I would like to explore feasible solutions, such as: Using a subscription model to provide access to all courses. Creating category-based bundles (e.g., Programming, Design) to group courses. Implementing a dynamic purchase system where users can select specific courses after a single In-App Purchase transaction, managed server-side. Could you please advise on the best approach to manage In-App Purchase for 100,000 individual courses? I am happy to provide additional details about my app’s functionality or work with your team to ensure compliance. Please let me know if a consultation or further documentation is needed to proceed. Looking forward to your guidance. Best regards, Shuvajit Maitra
1
0
65
1w
Using Cross-Platform Subscriptions on iOS
If I have a subscription based app that's available on iOS, Android, and the web.I use the appropriate billing systems for each platform (Apple IAP for iOS, Google Play Billing for Android, and my own system for web) What happens when a user subscribes on Android or the web, then logs into the same account on the iOS app? Am I allowed to honor that existing subscription on iOS, or am I required to have them purchase a separate subscription through Apple’s in app purchase system in order to access premium features on the iOS app?
1
0
83
1w
IAP and account subscriptions
If I have an app available on IOS/Android/Web and offer subscriptions in each (Obviously using apple IAP / Google Play Billing when in those contexts) and a user creates an account on Android / Web and pays for that subscription and then logs in with the same account on the App Store, do I need to charge them again for a new subscription, or can I use their account subscription?
0
0
60
1w
IAPs after rejection
Hi everyone, After my app was initially rejected, all my IAPs now show the status “Developer Action Needed.” There’s nothing I need to change on them — they were just returned because it was the first submission and the app itself was rejected. Do I need to do anything with the IAPs before I resubmit, or will they still be attached and reviewed automatically with the next binary? I’ve seen conflicting answers, so I’d like to confirm the correct process. Thanks, Cris
1
0
57
4d
In-App Purchases Returning “Invalid Product ID” After App Store Release
Hello, I’m facing an issue with in-app purchases after my app was approved on the App Store. I’ve created 100 consumable products, all of which have been approved by Apple. The purchases work correctly in both TestFlight and debug mode, but once the app is live on the App Store, I receive an “Invalid Product ID” error. I reached out to Apple Support, but they directed me to ask this question on the forum instead. Could someone please help me resolve this issue? Its been week now, I am losing my customers.
2
0
109
6d
subscriptionPromotionalOffers
问题描述: 创建苹果商品的促销优惠功能异常,查询官方接口文档,发现接口协议没有变更(协议文档:https://api.appstoreconnect.apple.com/v1/subscriptionPromotionalOffers) ,有人了解id字段的格式是什么?应该如何传值? 请求参数: {"data":{"attributes":{"duration":"ONE_WEEK","name":"20250817_test_2055","numberOfPeriods":1,"offerCode":"20250817_test_2055","offerMode":"FREE_TRIAL"},"relationships":{"prices":{"data":[{"id":"e467f67b-3d75-4319-aa6a-adebfc9f80da","type":"subscriptionPromotionalOfferPrices"}]},"subscription":{"data":{"id":"6673898723","type":"subscriptions"}}},"type":"subscriptionPromotionalOffers"},"included":[{"id":"e467f67b-3d75-4319-aa6a-adebfc9f80da","type":"subscriptionPromotionalOfferPrices","relationships":{"territory":{"data":{"id":"AUS","type":"territories"}}}}]} 响应参数: { "errors" : [ { "id" : "49d94648-0a8a-4aba-8856-72e69ac56aca", "status" : "409", "code" : "ENTITY_ERROR.INCLUDED.INVALID_ID", "title" : "The provided entity id is invalid", "detail" : "The provided included entity id 'e467f67b-3d75-4319-aa6a-adebfc9f80da' has invalid format", "source" : { "pointer" : "/included/0/id" } } ] }
0
1
56
1w
Invalid Code Signing Entitlements
Hi, I have a .NET MAUI app which I've added subscriptions to using the Plugin.InAppBilling nuget package. When I tested on TestFlight I got an almost immediate crash. After doing some research I followed advice to add an Entitlements.plist file to Platforms &gt; iOS with the following entry: com.apple.developer.in-app-purchase The distribution provisioning profile I'm using to sign my app has the App ID set to an Identifier which has "In-App Purchase" ticked, but greyed out in it's "Capabilities" section on https://developer.apple.com/. I'm not sure why it's ticked and greyed out, but I assume that means that "In-App Purchase" is enabled. The app runs file locally but when I create an IPA file and add it to Transporter for upload to App Store Connect I get the following error: "Validation failed (409) Invalid Code Signing Entitlements. Your application bundle's signature contains code signing entitlements that are not supported on iOS. Specifically, key 'com.apple.developer.in-app-purchase' in 'Payload/[Removed].app/[Removed]' is not supported. (ID: [Removed])". Here is the structure of my csproj code for creating a IPA file for iOS: The command I use: dotnet publish [Removed]/app.csproj -f:net9.0-ios -c:Release ^ /p:PlatformTarget=Arm64 ^ /p:RuntimeIdentifier=ios-arm64 ^ /p:ServerAddress=[Removed] ^ /p:ServerUser=[Removed] ^ /p:ServerPassword=[Removed] ^ /p:ArchiveOnBuild=true ^ /p:BuildIpa=true I'm stuck trying to figure this out. If you could please point out any issues with what I'm doing or if you have any suggestions to resolve the problem I would very much appreciate it. Thanks, Ben
4
0
312
2w
Can't attach In-App Purchase to Version
I just created my first in-app purchase. I got this message: Your first in-app purchase must be submitted with a new app version. Create your in-app purchase, then select it from the app’s In-App Purchases and Subscriptions section on the version page before submitting the version to App Review. Once your binary has been uploaded and your first-in app purchase has been submitted for review, additional in-app purchases can be submitted from the In-App Purchases section. Learn More However when I go to the new version of the app I am preparing to send for review. There is NO section to attach the in app purchase to that version. Its supposed to be underneath the screenshots and such but its not there no matter what
1
0
50
3w
can not verify receipt
I have three questions about verify receipt I use this api (https://buy.itunes.apple.com/verifyReceipt)to verify receipt is success or not. But since last month, this interface has started to return an error(21002). I see this document (https://developer.apple.com/documentation/appstorereceipts/verifyreceipt) say its Deprecated. My question is, is the error suddenly returned recently because the interface has been deprecated or for some other reason? (I haven't modified my code about this recently) I can not understand this document: (https://developer.apple.com/documentation/appstorereceipts/validating_receipts_on_the_device) Does this mean that in the new version, as long as the app returns a payment success (purchaseDetails.status == PurchaseStatus.purchased), the payment is guaranteed to be successful, and my server does not need to request payment result verification from Apple's server? I try to use this (https://github.com/apple/app-store-server-library-java) to get TransactionInfo, but I dont konw to get Transaction status to know is success or not. my java server code : AppStoreServerAPIClient client = new AppStoreServerAPIClient(encodedKey, keyId, issuerId, bundleId, environment); TransactionInfoResponse response = client.getTransactionInfo(transactionId); (bug i can note get transaction status, how do i konw this Transaction is success or not)
2
0
38
3w
(verifyreceipt) I cannot verify from the server whether the user's iap payment is successful or not
I have three questions about verify receipt I use this api (https://buy.itunes.apple.com/verifyReceipt)to verify receipt is success or not. But since last month, this interface has started to return an error(21002). I see this document (https://developer.apple.com/documentation/appstorereceipts/verifyreceipt) say its Deprecated. My question is, is the error suddenly returned recently because the interface has been deprecated or for some other reason? (I haven't modified my code about this recently) 2. I can not understand this document: (https://developer.apple.com/documentation/appstorereceipts/validating_receipts_on_the_device) Does this mean that in the new version, as long as the app returns a payment success (purchaseDetails.status == PurchaseStatus.purchased), the payment is guaranteed to be successful, and my server does not need to request payment result verification from Apple's server? 3. I try to use this (https://github.com/apple/app-store-server-library-java) to get TransactionInfo, but I dont konw to get Transaction status to know is success or not. my java server code : AppStoreServerAPIClient client = new AppStoreServerAPIClient(encodedKey, keyId, issuerId, bundleId, environment); TransactionInfoResponse response = client.getTransactionInfo(transactionId); (bug i can note get transaction status, how do i konw this Transaction is success or not)
0
0
23
3w
StoreKit 2 Sandbox Testing - Product not found
Hi, I've been unable to successfully test in the sandbox environment for a StoreKit 2 subscription group and can't seem to find the missing piece. I am calling the following line of code: let products = try await Product.products(for: [subscriptionID]) Expected behavior: The product is returned in the products array. Actual result: The array is empty I have done the following: Successfully tested our logic using a storekit configuration file locally in Xcode. Created the Subscription group in App Store Connect. The subscription product is currently "Waiting for Review", but it is our first so will not be approved without being attached to a distribution build review. Created a Sandbox user account in App Store Connect -> Users -> Sandbox Signed into the sandbox user account in Settings -> Developer -> Sandbox Apple Account Signed the Paid Apps Agreement for our organization A few debugging notes: I deleted all apps before installing from Xcode I've tried both locally and in TestFlight builds Restarted my device Verified productID matches the productID in App Store Connect I'm not sure if I'm missing something, but any help would be appreciated. Thanks
1
0
64
3w
IAP working in StoreKitTest on XCODE but not in TestFlight – shows mapped error "Product not available"
Hey everyone, I'm currently preparing an older iOS app for App Store release that includes a non-consumable In‑App Purchase using StoreKit 2. Everything works perfectly in the StoreKitTest environment inside Xcode – the product loads, the purchase flow runs, the transaction verifies. However, when I run the same app through TestFlight, I always get the error: ❌ Product not available - mapped to Here’s what I’ve already checked: ✅ The product ID is correct and matches what’s in App Store Connect (case-sensitive). ✅ The IAP is created in App Store Connect and includes: Title Product ID Price Tier Screenshot for review ✅ The App Store "Paid Applications" agreement is active. ✅ The app is using the correct bundle ID. ✅ I'm using Product.products(for: [productID]) from StoreKit 2. ✅ I’ve implemented fallback and retry logic (e.g. reload after delay). ✅ All IAP logic is wrapped in @MainActor and async-safe. As the App got Rejected on Review, the IAP is also now in the Rejected Status. Now the IAP shows status: 🟠 "Developer Action Required" And App Review rejected the IAP with the message: "Your first In‑App Purchase must be submitted together with a new app version." But if I add the App to the Test again and therefore the IAP, then the app will get Rejected again for App Completeness, IAP does not work... What am I doing wrong here? :) Thanks a lot in advance Cheers, Niklas
1
0
54
3w
IAP receipt validation fails with status code: 21002
I have implemented IAP. The purchases are successful. The refresh receipt is working fine, which then calls the requestDidFinish(_ request: SKRequest) delegate. I'm fetching the receipt url through 'Bundle.main.appStoreReceiptURL'. When I convert the receipt data in base64 string and send it to app store's sandbox api and try to validate the receipt, it fails giving status code : 21002.
2
0
75
2w
[StoreKit1] IAP Works in TestFlight but Fails During App Review (2.1 Rejection)
Hello Apple Developer Team, We're experiencing consistent IAP approval rejections under Guideline 2.1, despite successful TestFlight verification. Here's our detailed situation: Environment StoreKit 1 implementation Tested on iOS 18.5 or 18.6 devices Sandbox environment works perfectly Verification Steps Taken ✅ Confirmed all Product IDs match App Store Connect exactly ✅ Validated 10+ successful TestFlight transactions (attached screenshot samples) ✅ Verified banking/tax agreements are active Objective-C Code (StoreKit1 Implementation) - (void)buyProductId:(NSString *)pid AndSetGameOrderID:(NSString *)orderID{ if([SKPaymentQueue canMakePayments]){ if (!hasAddObserver) { [[SKPaymentQueue defaultQueue] addTransactionObserver:_neo]; hasAddObserver = YES; } self.neoOrderID = orderID; [[NSUserDefaults standardUserDefaults] setValue:orderID forKey:Pay_OrderId_Key]; self.productID = pid; NSArray * product = [[NSArray alloc]initWithObjects:self.productID, nil]; NSSet * nsset = [NSSet setWithArray:product]; SKProductsRequest * request = [[SKProductsRequest alloc]initWithProductIdentifiers:nsset]; request.delegate = self; [request start]; }else{ NSString * Err = @"Pembelian tidak diizinkan. Silakan aktifkan perizinan di pengaturan"; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]); return; } } - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { NSArray * product = response.products; if ([product count] == 0) { [[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo]; hasAddObserver = NO; NSString * Err = [NSString stringWithFormat:@"Err = 01, Item tidak ditemukan %@",self.productID]; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]); return; } SKProduct * p = nil; for (SKProduct * pro in product) { if ([pro.productIdentifier isEqualToString:self.productID]){ p = pro; }else{ [request cancel]; [[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo]; hasAddObserver = NO; NSString * Err = [NSString stringWithFormat:@"Err = 02, %@",self.productID]; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]); return; } } SKMutablePayment * mPayment = [SKMutablePayment paymentWithProduct:p]; mPayment.applicationUsername = [NSString stringWithFormat:@"%@",self.neoOrderID]; if(!hasAddObserver){ [[SKPaymentQueue defaultQueue] addTransactionObserver:_neo]; hasAddObserver = YES; } [[SKPaymentQueue defaultQueue] addPayment:mPayment]; } - (void)request:(SKRequest *)request didFailWithError:(NSError *)error{ [[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo]; hasAddObserver = NO; NSString * Err = [NSString stringWithFormat:@"Err = 0%ld %@", (long)error.code, self.productID]; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]); } - (void)requestDidFinish:(SKRequest *)request{ } - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{ for(SKPaymentTransaction *tran in transaction){ if (SKPaymentTransactionStatePurchased == tran.transactionState){ [self completeTransaction:tran]; }else if(SKPaymentTransactionStateFailed == tran.transactionState){ [self failedTransaction:tran]; } } } - (void)failedTransaction: (SKPaymentTransaction *)transaction { NSString * detail = [NSString stringWithFormat:@"%ld",(long)transaction.error.code]; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [detail UTF8String]); [[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo]; hasAddObserver = NO; [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; } - (void)completeTransaction:(SKPaymentTransaction *)transaction{ NSMutableDictionary * mdic = [NSMutableDictionary dictionary]; NSString * productIdentifier = transaction.payment.productIdentifier; NSData * _recep = nil; NSString * _receipt = @""; if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) { _recep = transaction.transactionReceipt; _receipt = [[NSString alloc]initWithData:_recep encoding:NSUTF8StringEncoding]; } else { _recep = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]; _receipt = [_recep base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; } NSString * gameOrderid = [transaction payment].applicationUsername; if (gameOrderid == nil) { gameOrderid = [[NSUserDefaults standardUserDefaults] objectForKey:Pay_OrderId_Key]; } if(_receipt != nil && gameOrderid != nil){ mdic[@"orderid"] = gameOrderid; mdic[@"productid"] = productIdentifier; mdic[@"receipt"] = _receipt; }else{ [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; return; } NSData * data = [NSJSONSerialization dataWithJSONObject:mdic options:kNilOptions error:nil]; NSString * jsonString = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; if (hasAddObserver) { [[SKPaymentQueue defaultQueue] removeTransactionObserver:_neo]; hasAddObserver = NO; } // UnitySendMessage("GameManager", "IAPPurchaseSuecess", [jsonString UTF8String]); [self verifyReceipt:_recep completion:^(BOOL success, NSDictionary *response) { if (success) { NSLog(@"verify success"); // [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; [self verifySuecessDelTransactions]; } }]; } - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { for(SKPaymentTransaction *tran in queue.transactions){ if (SKPaymentTransactionStatePurchased == tran.transactionState){ [self completeTransaction:tran]; } } } - (void)verifySuecessDelTransactions{ SKPaymentQueue *paymentQueue = [SKPaymentQueue defaultQueue]; NSArray<SKPaymentTransaction *> *transactions = paymentQueue.transactions; if (transactions.count == 0) { return; } for (SKPaymentTransaction *transaction in transactions) { if (transaction.transactionState == SKPaymentTransactionStatePurchased || transaction.transactionState == SKPaymentTransactionStateRestored) { [paymentQueue finishTransaction:transaction]; } } }
1
0
62
3w
ProductView failed to trigger purcahse flow.
Hi, My app has an IAP and the view that let user to purchase is simply a ProductView. The purchase flow should be handled by the ProductView itself. I have tested the app with xcode storekit configuration, xcode run with sandbox account and also TestFlight environment as well. The purchase is triggered and the app feature is unlocked after purchase. However, I keep getting app review team feedback with the following problem: Bug description: the purchase button is greyed out after we tapped on it, however, there's no purchase flow popped up I have tried multiple things. Building with xcode cloud, removing the storekit configuration from the build scheme. But none can get the app review team to get through the problem. The IAP is not available in certain region. In that case, the app will show a message. However, the app review attached an screenshot which shows the product view. The view that allow users to purchase if let product = store.products.first(where: { $0.id == "com.xxx.xxxxxxx" }) { // If the product is available, show the ProductView ProductView(id: product.id) .productViewStyle(.compact) } else { // If the product is not available, show a message Text("In-app purchase is not available in your region.") } The store class @Published private(set) var products: [Product] = [] ... init() { //To handle the parental approval flow getUpdateTransaction() } func getUpdateTransaction() { updates = Task { for await update in StoreKit.Transaction.updates { if let transaction = try? update.payloadValue { await fetchActiveTransactions() await transaction.finish() } } } } Does anyone what can go wrong with ProductView? As this is part of the StoreKit API, I don't know what can go wrong. At least the purchase flow should be covered by it. Also, is sandbox and TestFlight a good way to test IAP? Thanks!
1
0
45
Jul ’25
Problem Generating Signature for Subscription Offers – Error Code 18
I'm successfully using Apple subscriptions in my app, but I'm encountering SKErrorCodeDomain error 18 when trying to apply a subscription offer. I want apply offer code first time only for subscription. Below are details of what i set in appstore and what i have tested. Subscription Offer Details Offer Type: For the first month Customer Eligibility: New, Existing, and Expired Subscribers Code Status: Active Offer Code Creation Steps: App Store Connect → App → Subscription → Select Subscription Product → Offer Codes → Add → Add Custom Codes Signature Generation for Promotional Offers I'm following Apple's documentation to generate a signature: https://developer.apple.com/documentation/storekit/generating-a-signature-for-promotional-offers I’ve constructed the payload as instructed: appBundleId + '\u2063' + keyIdentifier + '\u2063' + productIdentifier + '\u2063' + offerIdentifier + '\u2063' + appAccountToken + '\u2063' + nonce + '\u2063' + timestamp Keys and Identifiers keyIdentifier, issuerId, and .p8 file are obtained from: App Store Connect → Users and Access → Integrations → In-App Purchase Test user created under: App Store Connect → Users and Access → Sandbox → Test Accounts Logged in with this account on the iPhone What I’ve Tried Verified all values used in the payload are correct Tried both seconds and milliseconds for the timestamp (as per documentation, it should be in milliseconds) Tried setting appAccountToken to: a valid UUID an empty string not setting it at all Used Apple’s sample code to generate a signature: https://developer.apple.com/documentation/storekit/generating-a-promotional-offer-signature-on-the-server Verified the generated signature locally, and it validated successfully: https://developer.apple.com/documentation/storekit/generating-a-signature-for-promotional-offers#Validate-locally-and-encode-the-signature Apple’s sample code to generate a signature Downloaded from const express = require('express'); const router = express.Router(); const crypto = require('crypto'); const ECKey = require('ec-key'); const secp256k1 = require('secp256k1'); const uuidv4 = require('uuid/v4'); const KeyEncoder = require('key-encoder'); const keyEncoder = new KeyEncoder('secp256k1'); const fs = require('fs'); function getKeyID() { return "KEYIDXXXXX"; } router.post('/offer', function(req, res) { const appBundleID = req.body.appBundleID; const productIdentifier = req.body.productIdentifier; const subscriptionOfferID = req.body.offerID; const applicationUsername = req.body.applicationUsername; const nonce = uuidv4(); const currentDate = new Date(); const timestamp = currentDate.getTime(); const keyID = getKeyID(); const payload = appBundleID + '\u2063' + keyID + '\u2063' + productIdentifier + '\u2063' + subscriptionOfferID + '\u2063' + applicationUsername + '\u2063'+ nonce + '\u2063' + timestamp; // Get the PEM-formatted private key string associated with the Key ID. // const keyString = getKeyStringForID(keyID); // Read the .p8 file const keyString = fs.readFileSync('./SubscriptionKey_47J5826J8W.p8', 'utf8'); // Create an Elliptic Curve Digital Signature Algorithm (ECDSA) object using the private key. const key = new ECKey(keyString, 'pem'); // Set up the cryptographic format used to sign the key with the SHA-256 hashing algorithm. const cryptoSign = key.createSign('SHA256'); // Add the payload string to sign. cryptoSign.update(payload); /* The Node.js crypto library creates a DER-formatted binary value signature, and then base-64 encodes it to create the string that you will use in StoreKit. */ const signature = cryptoSign.sign('base64'); /* Check that the signature passes verification by using the ec-key library. The verification process is similar to creating the signature, except it uses 'createVerify' instead of 'createSign', and after updating it with the payload, it uses `verify` to pass in the signature and encoding, instead of `sign` to get the signature. This step is not required, but it's useful to check when implementing your signature code. This helps debug issues with signing before sending transactions to Apple. If verification succeeds, the next recommended testing step is attempting a purchase in the Sandbox environment. */ const verificationResult = key.createVerify('SHA256').update(payload).verify(signature, 'base64'); console.log("Verification result: " + verificationResult) // Send the response. res.setHeader('Content-Type', 'application/json'); res.json({ 'keyID': keyID, 'nonce': nonce, 'timestamp': timestamp, 'signature': signature }); }); module.exports = router; Postman request and response Request URL: http://192.168.1.141:3004/offer Request JSON: { "appBundleID":"com.app.bundleid", "productIdentifier":"subscription.product.id", "offerID":"OFFERCODE1", "applicationUsername":"01234b43791ea309a1c3003412bcdaaa09d39a615c379cc246f5f479760629a1" } Response JSON: { "keyID": "KEYIDXXXXX", "nonce": "f98f2cda-c7a6-492f-9f92-e24a6122c0c9", "timestamp": 1753510571664, "signature": "MEYCIQCnA8UGWhTiCF+F6S55Zl6hpjnm7SC3aAgvmTBmQDnsAgIhAP6xIeRuREyxxx69Ve/qjnONq7pF1cK8TDn82fyePcqz" } Xcode Code func buy(_ product: SKProduct) { let discountOffer = SKPaymentDiscount( identifier: "OFFERCODE1", keyIdentifier: "KEYIDXXXXX", nonce: UUID(uuidString: "f98f2cda-c7a6-492f-9f92-e24a6122c0c9")!, signature: "MEYCIQCnA8UGWhTiCF+F6S55Zl6hpjnm7SC3aAgvmTBmQDnsAgIhAP6xIeRuREyxxx69Ve/qjnONq7pF1cK8TDn82fyePcqz", timestamp: 1753510571664) let payment = SKMutablePayment(product: product) payment.applicationUsername = "01234b43791ea309a1c3003412bcdaaa09d39a615c379cc246f5f479760629a1" payment.paymentDiscount = discountOffer SKPaymentQueue.default().add(payment) } Issue Even following instructions to the documentation and attempting various combinations, the offer keeps failing with SKErrorCodeDomain error 18. Has anyone else experienced this? Any suggestions as to what may be amiss or how it can be corrected?
1
0
35
Jul ’25