Xamarin Forms iOS自动续订订阅:收据解密与本地验证咨询
Xamarin Forms iOS自动续订订阅问题解答
1. 购买验证是否为强制要求?
Apple规则明确:仅解锁应用内功能、不提供外部内容的自动续订订阅,并非强制要求服务器端验证,客户端验证是被允许的。你遇到的审核被拒大概率是审核人员的误解,申诉时可明确说明订阅仅用于解锁应用内功能、无外部内容交付,同时引用Apple官方客户端验证的文档依据。
2. 如何解密App Store收据?
App Store收据是PKCS #7格式的签名容器,在Xamarin Forms iOS中可借助BouncyCastle库(NuGet可直接安装)完成解密与解析,核心实现如下:
步骤1:获取设备收据数据
using Foundation; using UIKit; public byte[] GetReceiptData() { var receiptUrl = NSBundle.MainBundle.AppStoreReceiptUrl; if (receiptUrl == null || !NSFileManager.DefaultManager.FileExists(receiptUrl.Path)) { // 触发App Store刷新收据 var request = new SKReceiptRefreshRequest(); request.Start(); return null; } return NSData.FromUrl(receiptUrl).ToArray(); }
步骤2:解析PKCS #7收据并验证签名
using Org.BouncyCastle.Cms; using Org.BouncyCastle.X509; using Org.BouncyCastle.Security; public string ParseReceipt(byte[] receiptData) { var cms = new CmsSignedData(receiptData); // 用Apple根证书验证签名(需提前将AppleRootCA-G3.cer嵌入项目) var signerInfos = cms.GetSignerInfos(); var certs = cms.GetCertificates("Collection"); foreach (var signerInfo in signerInfos.GetSigners()) { var cert = certs.GetCertificate(signerInfo.SignerID); var validator = new SignerInformationVerifier(signerInfo.SignerID, cert); if (!signerInfo.Verify(validator)) { throw new SecurityException("收据签名验证失败"); } } // 提取收据JSON内容 var content = (CmsProcessableByteArray)cms.SignedContent; return System.Text.Encoding.UTF8.GetString((byte[])content.Content); }
3. 如何不使用服务器进行购买验证?
客户端验证需完成核心流程:获取并解析收据、检查订阅状态(过期时间、自动续订状态等),示例代码如下:
using Newtonsoft.Json.Linq; public bool ValidateSubscriptionLocally() { var receiptData = GetReceiptData(); if (receiptData == null) return false; try { var receiptJson = ParseReceipt(receiptData); var receiptObj = JObject.Parse(receiptJson); var latestReceiptInfos = receiptObj["latest_receipt_info"] as JArray; if (latestReceiptInfos == null || latestReceiptInfos.Count == 0) return false; // 获取最新的订阅记录 var latestSubscription = latestReceiptInfos.OrderByDescending(x => x["expires_date_ms"]).First(); var expiresDateMs = long.Parse(latestSubscription["expires_date_ms"].ToString()); var currentTimeMs = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds; // 验证订阅是否未过期 return currentTimeMs < expiresDateMs; } catch { return false; } }
提示:客户端验证存在被篡改的风险,若应用对安全性要求较高,仍建议结合服务器端验证;但对于仅解锁基础内功能的场景,客户端验证完全满足合规要求。
内容的提问来源于stack exchange,提问作者jho
相关产品推荐
相关产品推荐

