我让它在沙盒中运行,几乎要上线了......
应该使用服务器来验证收据。
在服务器上,您可以使用收据数据记录设备 udid,因为收据总是新生成的,并且它可以跨多个设备工作,因为收据总是新生成的。
在设备上不需要存储任何敏感数据,也不应该:)
每当应用程序出现时,都应与商店核对最后一张收据。应用程序调用服务器,服务器与商店进行验证。只要商店返回一个有效的收据应用程序就可以使用该功能。
我开发了一个 Rails3.x 应用来处理服务器端,验证的实际代码如下所示:
APPLE_SHARED_PASS = "enter_yours"
APPLE_RECEIPT_VERIFY_URL = "https://sandbox.itunes.apple.com/verifyReceipt" #test
# APPLE_RECEIPT_VERIFY_URL = "https://buy.itunes.apple.com/verifyReceipt" #real
def self.verify_receipt(b64_receipt)
json_resp = nil
url = URI.parse(APPLE_RECEIPT_VERIFY_URL)
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
json_request = {'receipt-data' => b64_receipt, 'password' => APPLE_SHARED_PASS}.to_json
resp, resp_body = http.post(url.path, json_request.to_s, {'Content-Type' => 'application/x-www-form-urlencoded'})
if resp.code == '200'
json_resp = JSON.parse(resp_body)
logger.info "verify_receipt response: #{json_resp}"
end
json_resp
end
#App Store error responses
#21000 The App Store could not read the JSON object you provided.
#21002 The data in the receipt-data property was malformed.
#21003 The receipt could not be authenticated.
#21004 The shared secret you provided does not match the shared secret on file for your account.
#21005 The receipt server is not currently available.
#21006 This receipt is valid but the subscription has expired.
更新
我的应用被拒绝了,因为元数据没有明确说明有关自动续订订阅的一些信息。
在 iTunes Connect 的元数据中(在您的应用描述中):您
需要清楚且显着地向用户披露以下内容
有关您的自动续订订阅的信息:
- 出版物或服务的标题
- 订阅时长(时间段和/或每个订阅期间的交付次数)
- 订阅价格和每期价格(如果适用)
- 付款将在确认购买时从 iTunes 帐户中扣除
- 订阅会自动续订,除非在当前订阅期结束前至少 24 小时关闭自动续订
- 将在当前周期结束前 24 小时内向帐户收取续订费用,并确定续订费用
- 订阅可以由用户管理,并且可以在购买后转到用户的帐户设置来关闭自动续订
- 在有效订阅期间不允许取消当前订阅
- 指向您的隐私政策和使用条款的链接
- 免费试用期的任何未使用部分(如果提供)将在用户购买该出版物的订阅时被没收。”
更新二
应用再次被拒绝。订阅收据未通过生产 AppStore 验证 url 进行验证。我无法在沙盒中重现此问题,我的应用程序完美无缺。调试此问题的唯一方法是再次提交应用程序以供审核并查看服务器日志。
更新 III
另一个拒绝。与此同时,Apple 记录了另外两种状态:
#21007 This receipt is a sandbox receipt, but it was sent to the production service for verification.
#21008 This receipt is a production receipt, but it was sent to the sandbox service for verification.
在提交应用程序以供审核之前,不应将服务器切换到生产环境
收据验证网址。如果有,则在验证时返回状态 21007。
这次拒绝的内容如下:
应用程序以非标准方式启动应用内购买过程。我们已包含以下详细信息以提供帮助
解释问题并希望您考虑修改并重新提交
你的申请。
iTunes 用户名和密码会在应用程序启动时立即被要求。请参阅随附的屏幕截图以获取更多信息
信息。
我不知道为什么会这样。是否因为先前的交易正在恢复而弹出密码对话框?还是在从应用商店请求产品信息时弹出?
更新四
我在 5 次被拒绝后马上就收到了。我的代码出现了最明显的错误。确实应该确保在将交易交付到应用程序时始终完成交易。
如果交易未完成,它们会被送回应用程序,然后出现奇怪的错误。
需要先发起付款,如下所示:
//make the payment
SKPayment *payment = [SKPayment paymentWithProductIdentifier:productIdentifier];
[[SKPaymentQueue defaultQueue] addPayment:payment];
然后应用程序将很快退出其活动状态,并调用应用程序委托上的此方法:
- (void)applicationWillResignActive:(UIApplication *)application
当应用程序处于非活动状态时,App Store 会弹出其对话框。当应用再次激活时:
- (void)applicationDidBecomeActive:(UIApplication *)application
操作系统通过以下方式传递事务:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased: {
[self completeTransaction:transaction];
break;
}
case SKPaymentTransactionStateFailed: {
[self failedTransaction:transaction];
break;
}
case SKPaymentTransactionStateRestored: {
[self restoreTransaction:transaction];
break;
}
default:
break;
}
}
}
然后完成交易:
//a fresh purchase
- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
[self recordTransaction: transaction];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
看,如何在将收到的交易传递给recordTransaction 后立即调用方法finishTransaction,然后调用应用服务器并与App Store 进行订阅收据验证。像这样:
- (void)recordTransaction: (SKPaymentTransaction *)transaction
{
[self subscribeWithTransaction:transaction];
}
- (void)subscribeWithTransaction:(SKPaymentTransaction*)transaction {
NSData *receiptData = [transaction transactionReceipt];
NSString *receiptEncoded = [Kriya base64encode:(uint8_t*)receiptData.bytes length:receiptData.length];//encode to base64 before sending
NSString *urlString = [NSString stringWithFormat:@"%@/api/%@/%@/subscribe", [Kriya server_url], APP_ID, [Kriya deviceId]];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [[[ASIFormDataRequest alloc] initWithURL:url] autorelease];
[request setPostValue:[[transaction payment] productIdentifier] forKey:@"product"];
[request setPostValue:receiptEncoded forKey:@"receipt"];
[request setPostValue:[Kriya deviceModelString] forKey:@"model"];
[request setPostValue:[Kriya deviceiOSString] forKey:@"ios"];
[request setPostValue:[appDelegate version] forKey:@"v"];
[request setDidFinishSelector:@selector(subscribeWithTransactionFinished:)];
[request setDidFailSelector:@selector(subscribeWithTransactionFailed:)];
[request setDelegate:self];
[request startAsynchronous];
}
以前我的代码仅在我的服务器验证收据后才尝试调用finishTransaction,但到那时交易已经不知何故丢失了。所以请务必尽快finishTransaction。
另一个可能遇到的问题是,当应用程序在沙盒中时,它会调用沙盒 App Store 验证 url,但当它处于审核状态时,它不知何故在世界之间。所以我不得不像这样更改我的服务器代码:
APPLE_SHARED_PASS = "83f1ec5e7d864e89beef4d2402091cd0" #you can get this in iTunes Connect
APPLE_RECEIPT_VERIFY_URL_SANDBOX = "https://sandbox.itunes.apple.com/verifyReceipt"
APPLE_RECEIPT_VERIFY_URL_PRODUCTION = "https://buy.itunes.apple.com/verifyReceipt"
def self.verify_receipt_for(b64_receipt, receipt_verify_url)
json_resp = nil
url = URI.parse(receipt_verify_url)
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
json_request = {'receipt-data' => b64_receipt, 'password' => APPLE_SHARED_PASS}.to_json
resp, resp_body = http.post(url.path, json_request.to_s, {'Content-Type' => 'application/x-www-form-urlencoded'})
if resp.code == '200'
json_resp = JSON.parse(resp_body)
end
json_resp
end
def self.verify_receipt(b64_receipt)
json_resp = Subscription.verify_receipt_for(b64_receipt, APPLE_RECEIPT_VERIFY_URL_PRODUCTION)
if json_resp!=nil
if json_resp.kind_of? Hash
if json_resp['status']==21007
#try the sandbox then
json_resp = Subscription.verify_receipt_for(b64_receipt, APPLE_RECEIPT_VERIFY_URL_SANDBOX)
end
end
end
json_resp
end
所以基本上总是使用生产 URL 进行验证,但如果它返回 21007 代码,则意味着沙盒收据已发送到生产 URL,然后人们只需使用沙盒 URL 再次尝试。这样,您的应用在沙盒和生产模式下的工作方式相同。
最后,Apple 希望我在订阅按钮旁边添加一个 RESTORE 按钮,以处理一个用户拥有多个设备的情况。然后此按钮调用[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];,应用程序将与恢复的事务(如果有)一起交付。
此外,有时测试用户帐户会以某种方式受到污染并且事情停止工作,并且您在订阅时可能会收到“无法连接到 iTunes 商店”的消息。它有助于创建一个新的测试用户。
下面是其余的相关代码:
- (void) restoreTransaction: (SKPaymentTransaction *)transaction
{
[self recordTransaction: transaction];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
- (void) failedTransaction: (SKPaymentTransaction *)transaction
{
if (transaction.error.code == SKErrorPaymentCancelled)
{
//present error to user here
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
祝您有一个流畅的 InAppPurchase 编程体验。 :-)