iOS应用内购(IAP)促销优惠报错:该优惠仅面向当前/过往订阅用户
应用内购促销优惠初始化SKPaymentDiscount时报错:"This offer is available only to current or previous subscribers of this app"
问题背景
已在App Store Connect完成应用内购(IAP)促销优惠配置,通过后端生成签名(signature)、随机数(nonce)和时间戳(timestamp)后,初始化SKPaymentDiscount方法时触发如下错误:
This offer is available only to current or previous subscribers of this app.
[Environment: Xcode]
开发参考Apple官方文档实现,相关代码如下:
PrepareForOffer 代码
func prepareOffer(usernameHash: String, productIdentifier: String, offerIdentifier: String, completion: @escaping (SKPaymentDiscount) -> Void) { let applicationUserName = UUID().uuidString.lowercased() let param:[String:Any] = ["productIdentifier": productIdentifier, "applicationUsername": applicationUserName, "offerID": offerIdentifier] param.printJson() APIManager.sharedInstance.usersAPIManager.GetSignatureData(params: param, success: { (responseObject) in self.purchase = Mapper<IAPModel>().map(JSON: responseObject as [String: Any]) // Create offer var uuid = UUID(uuidString: self.purchase?.nonce ?? "") let discountOffer = SKPaymentDiscount(identifier: offerIdentifier, keyIdentifier: "5WWL777LG5", nonce: uuid ?? UUID(), signature: self.purchase?.signature ?? "", timestamp: (self.purchase?.timestamp ?? 0) as NSNumber) // Pass offer in completion block completion(discountOffer) }) { (error) in print(error) } }
BuyProduct 代码
func buyProduct(product: SKProduct, forApplicationUsername usernameHash: String, withOffer offer: SKPaymentDiscount) { print("buyProduct", offer.debugDescription.description) // Create payment object let payment = SKMutablePayment(product: product) // Apply username and offer to the payment payment.applicationUsername = usernameHash payment.paymentDiscount = offer // Add payment to paymentQueue InAppPurchaseManager.shared.paymentQueue.add(payment) }

排查与解决步骤
1. 确认优惠的受众配置
先检查App Store Connect里的优惠类型:
- 如果配置的是挽留优惠或订阅升级/降级优惠,这类优惠仅对现有/曾订阅过的用户开放,测试时必须用有订阅记录的沙盒账号(可以先订阅基础套餐,再测试优惠);
- 如果是给新用户的优惠,要选择“新订阅用户”类型(比如介绍优惠、新用户定向促销优惠)。
2. 修复applicationUsername不一致问题
代码里存在关键参数不匹配的问题:
prepareOffer中生成了新的UUID作为applicationUsername传给后端生成签名;- 但
buyProduct里却用了外部传入的usernameHash赋值给payment.applicationUsername。
Apple要求生成签名时的applicationUsername必须和发起支付时的完全一致,否则会触发权限校验错误。修改方案:
把prepareOffer里的applicationUserName换成传入的usernameHash,保证前后参数统一,修改后的代码片段:
func prepareOffer(usernameHash: String, productIdentifier: String, offerIdentifier: String, completion: @escaping (SKPaymentDiscount) -> Void) { // 直接使用传入的usernameHash,避免前后参数不一致 let applicationUserName = usernameHash let param:[String:Any] = ["productIdentifier": productIdentifier, "applicationUsername": applicationUserName, "offerID": offerIdentifier] param.printJson() APIManager.sharedInstance.usersAPIManager.GetSignatureData(params: param, success: { (responseObject) in self.purchase = Mapper<IAPModel>().map(JSON: responseObject as [String: Any]) // 增加nonce有效性校验,不要直接用默认UUID guard let nonceStr = self.purchase?.nonce, let uuid = UUID(uuidString: nonceStr) else { print("后端返回的nonce无效") return } let discountOffer = SKPaymentDiscount(identifier: offerIdentifier, keyIdentifier: "5WWL777LG5", nonce: uuid, signature: self.purchase?.signature ?? "", timestamp: (self.purchase?.timestamp ?? 0) as NSNumber) completion(discountOffer) }) { (error) in print(error) } }
3. 验证后端签名逻辑
- 确认后端生成签名时使用的
applicationUsername、productIdentifier、offerIdentifier和客户端传入的完全一致; - 检查后端用的签名密钥(Key ID和私钥)是否和App Store Connect中配置的优惠密钥匹配;
- 确保时间戳是秒级UNIX时间戳,且和客户端初始化
SKPaymentDiscount的时间差不超过5分钟。
4. 检查沙盒账号状态
如果用沙盒账号测试:
- 若优惠面向老用户,确认账号之前订阅过该产品(即使已过期);
- 可以在测试设备的「设置」->「Apple ID」->「媒体与购买项目」->「查看账户」->「订阅」里确认账号的订阅记录。
内容的提问来源于stack exchange,提问作者Imran
相关产品推荐
相关产品推荐

