苹果内购的凭证验证和解密
最近在搞苹果内购,是使用微信提供的Dount提供的小程序转成APP。苹果内购使用的也是他们封装好的js接口,然后后端在解析我传递的支付凭证的时候他一直解析不成功然后我坚信自己的传递参数没有问题,我就自己使用node写了一个本地服务去验证我的支付凭证,所以就有了这个文章。
服务器端代码 (server.mjs
)
导入必要模块
import express from 'express';
import bodyParser from 'body-parser';
import cors from 'cors';
import fetch from 'node-fetch';
express
: 用于创建一个服务器。bodyParser
: 用于解析JSON格式的请求体。cors
: 用于解决跨域问题。fetch
: 用于发送HTTP请求到Apple的验证服务器。
创建Express应用并配置中间件
const app = express();
const port = 3000;
app.use(cors());
app.use(bodyParser.json());
- 创建一个Express应用实例。
- 设置应用端口为3000。
- 使用
cors
中间件以允许跨域请求。 - 使用
bodyParser
中间件以解析JSON格式的请求体。
定义路由以处理验证请求
app.post('/verifyReceipt', async (req, res) => {
const { receiptData, password } = req.body;
try {
const response = await fetch('https://sandbox.itunes.apple.com/verifyReceipt', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'receipt-data': receiptData,
'password': password
})
});
const result = await response.json();
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
- 定义一个POST路由
/verifyReceipt
来处理客户端发送的验证请求。 - 从请求体中提取
receiptData
和password
。 - 使用
fetch
函数发送POST请求到Apple的沙盒验证服务器https://sandbox.itunes.apple.com/verifyReceipt
。 - 请求体包含Base64编码的收据数据和共享密钥。
- 将Apple返回的结果以JSON格式响应回客户端。
- 如果请求失败,返回500状态码和错误信息。
启动服务器
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});
- 启动服务器,并在指定端口上监听连接请求。
- 要先进行下载这个几个依赖
npm install express body-parser cors node-fetch
- 然后就是启动这个服务
node server.mjs
前端代码(HTML文件)
基本HTML结构和样式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>验证苹果内购凭证</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f0f0f0;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
background-color: white;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.container h1 {
font-size: 24px;
margin-bottom: 20px;
}
.container textarea {
width: 100%;
height: 100px;
margin-bottom: 20px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-family: monospace;
}
.container button {
display: inline-block;
padding: 10px 20px;
background-color: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.container button:disabled {
background-color: #ccc;
}
.container .result {
margin-top: 20px;
white-space: pre-wrap;
word-wrap: break-word;
background-color: #f8f9fa;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-family: monospace;
}
</style>
</head>
<body>
<div class="container">
<h1>粘贴你的内购凭证(transactionReceipt)</h1>
<textarea id="receiptData" placeholder="粘贴你的内购凭证(transactionReceipt)"></textarea>
<button id="verifyButton" onclick="verifyReceipt()">解析凭证</button>
<div id="result" class="result"></div>
</div>
- 设置HTML文档的基本结构和样式,确保页面看起来整洁美观。
JavaScript代码
<script>
async function verifyReceipt() {
const receiptData = document.getElementById('receiptData').value;
const resultElement = document.getElementById('result');
const verifyButton = document.getElementById('verifyButton');
if (!receiptData) {
resultElement.textContent = '请输入base64编码的收据数据';
return;
}
verifyButton.disabled = true;
resultElement.textContent = '解析中...';
try {
const response = await fetch('http://localhost:3000/verifyReceipt', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'receiptData': receiptData,
'password': 'your-shared-secret' // 替换你的秘钥可有可无
})
});
const result = await response.json();
handleReceiptResult(result, resultElement);
} catch (error) {
resultElement.textContent = `Error: ${error.message}`;
}
verifyButton.disabled = false;
}
function handleReceiptResult(result, resultElement) {
const status = result.status;
let message;
switch (status) {
case 0:
message = '验证成功!';
break;
case 21000:
message = 'App Store无法读取你提供的JSON对象。';
break;
case 21002:
message = '收到的数据无效。';
break;
case 21003:
message = '收据无法被验证。';
break;
case 21004:
message = '你提供的共享密钥与帐户文件中的共享密钥不匹配。';
break;
case 21005:
message = '收据服务器当前不可用。';
break;
case 21006:
message = '收据有效,但订阅已过期。';
break;
case 21007:
message = '此收据来自测试环境,但已发送到生产环境进行验证。';
break;
case 21008:
message = '此收据来自生产环境,但已发送到测试环境进行验证。';
break;
case 21010:
message = '收据无效,无法被验证。';
break;
default:
message = `未知错误,状态码:${status}`;
}
resultElement.textContent = `结果: ${JSON.stringify(result, null, 2)}\n\n信息: ${message}`;
}
</script>
</body>
</html>
verifyReceipt
函数:当用户点击“解析凭证”按钮时,获取输入的收据数据,并发送POST请求到本地代理服务器(http://localhost:3000/verifyReceipt
)。显示解析结果或错误信息。handleReceiptResult
函数:处理Apple返回的验证结果,根据状态码显示具体的错误消息。- 根据Apple内购验证API的状态码显示相应的信息,例如状态码
0
表示验证成功,状态码21000
表示App Store无法读取你提供的JSON对象,等等。
- 根据Apple内购验证API的状态码显示相应的信息,例如状态码
作用
-
服务器端代码:
- 接收客户端发送的请求,并将请求转发到Apple的验证服务器。
- 处理Apple返回的结果,并将结果返回给客户端。
- 通过设置CORS和解析JSON请求体,确保请求和响应的正确处理。
-
前端代码:
- 提供用户界面,允许用户输入Base64编码的内购凭证。
- 将用户输入的凭证发送到本地服务器进行验证。
- 处理服务器返回的结果,并在页面上显示详细的验证结果和错误信息。
通过这种方式,用户可以方便地验证Apple内购凭证,并获取详细的验证结果和错误信息。这样可以帮助开发人员和用户了解验证过程中可能出现的问题,并采取相应的措施进行处理。