本文主要讲移动APP实现苹果支付的服务端实现步骤。
苹果应用内支付的流程可参考:
1、 官网说明:https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1
2、中文博客:https://mengkang.net/723.html
简要说明如下:
1、首先客户端先请求苹果支付中心,支付中心返回给客户端一堆加密的数据。
2、然后客户端把这段加密的数据传给服务端。
3、最后由服务端端再去请求苹果支付中心来验证这次购买是否成功。如果验证通过,服务器端对业务逻辑进行处理。
本例实现方式:
客户端和服务端之间采用的websocket + JSON格式数据的通信方式。
服务端业务逻辑部分采用是Perl语言编写。
1、接口参数
{
"order_id":订单号(前端生成,32个字符内,保证唯一性)
"receipt_data":"MIITuwYJKoZIhvcNAQcCoIITrDCCE6g..."支付中心返回给客户端的加密数据
}
2、服务端请求支付中心验证票据
sub apple_check_receipt {
my $receipt_data = $_[0];
my $endpoint_debug = "https://sandbox.itunes.apple.com/verifyReceipt"; #开发环境,采用苹果沙盒地址
if(__PACKAGE__ eq "PRODUCT") {
$endpoint_debug = "https://buy.itunes.apple.com/verifyReceipt"; #生产环境
}
# 构造请求的参数(json格式)
my $apple_receipt;
$apple_receipt->{"receipt-data"} = $receipt_data;
my $header = HTTP::Headers->new( Content_Type => 'application/json; charset=utf8', );
my $json = JSON->new();
my $http_request = HTTP::Request->new( POST => $endpoint_debug, $header, $json->encode($apple_receipt));
#anlyse this response
my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0, SSL_verify_mode => 0x00 });
$ua->timeout(30);
my $response = $ua->request($http_request);
my $json_return;
if ($response->message ne "OK" && $response->is_success ne "1") { #出错,或者timeout了
return "timeout";
} else {
$json_return = $json->decode($response->content());
}
# 验证苹果中心返回的结果
if ($json_return->{status} eq "0") {
my $in_app = $json_return->{receipt}->{in_app}[0];
$result->{transaction_id} = $in_app->{transaction_id};
if (length($result->{transaction_id}) == 0) {
return "transaction_id is null";
} elsif (length($in_app->{product_id}) == 0 || index($in_app->{product_id}, "abc.cdf.x") < 0) {
return "product_id is invalid";
}
# 根据业务需求,验证其他字段
.....
}
return "success";
}
验证票据请求返回结果的内容一般如下:
response: {
environment: "Production",
receipt: {
adam_id: 1155833660,
app_item_id: 1155833660,
application_version: "1",
bundle_id: "**********",
download_id: null,
in_app: [
{
is_trial_period: "false",
original_purchase_date: "2017-02-21 06:25:12 Etc/GMT",
original_purchase_date_ms: "1487658312000",
original_purchase_date_pst: "2017-02-20 22:25:12 America/Los_Angeles",
original_transaction_id: "710000191974963",
product_id: "***********",
purchase_date: "2017-02-21 06:25:12 Etc/GMT",
purchase_date_ms: "1487658312000",
purchase_date_pst: "2017-02-20 22:25:12 America/Los_Angeles",
quantity: "1",
transaction_id: "710000191974963"
}
],
original_application_version: "1",
original_purchase_date: "2017-02-21 06:25:12 Etc/GMT",
original_purchase_date_ms: "1487658312000",
original_purchase_date_pst: "2017-02-20 22:25:12 America/Los_Angeles",
receipt_creation_date: "2017-02-21 06:25:12 Etc/GMT",
receipt_creation_date_ms: "1487658312000",
receipt_creation_date_pst: "2017-02-20 22:25:12 America/Los_Angeles",
receipt_type: "Production",
request_date: "2017-02-21 06:25:22 Etc/GMT",
request_date_ms: "1487658312010",
request_date_pst: "2017-02-20 22:25:22 America/Los_Angeles",
version_external_identifier: 820590861
},
status: "0"
},
每个字段的含义可以参考官网说明:https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1
关键信息是在 in_app这个数组里面,可以根据自身情况,校验相应字段的合法性。
生产环境中还经常出现in_app为空数组的情况,比如下面的结果:
response: {
environment: "Production",
receipt: {
adam_id: 1155833660,
app_item_id: 1155833660,
application_version: "1",
bundle_id: "***********",
download_id: 83024344988508,
in_app: [ ],
original_application_version: "1",
original_purchase_date: "2017-03-26 10:26:17 Etc/GMT",
original_purchase_date_ms: "1490523977000",
original_purchase_date_pst: "2017-03-26 03:26:17 America/Los_Angeles",
receipt_creation_date: "2017-03-26 10:26:17 Etc/GMT",
receipt_creation_date_ms: "1490523977000",
receipt_creation_date_pst: "2017-03-26 03:26:17 America/Los_Angeles",
receipt_type: "Production",
request_date: "2017-03-26 10:26:27 Etc/GMT",
request_date_ms: "1490523977010",
request_date_pst: "2017-03-26 03:26:27 America/Los_Angeles",
version_external_identifier: 821061739
},
status: "0"
},
这种都是不正常的。
另外客户端输入参数里面有order_id(保证唯一性)可以用来防止同一个票据数据receipt_data,重复发送导致服务端重复执行业务逻辑的问题。