苹果内购的凭证验证和解密(前端和本地node服务)

苹果内购的凭证验证和解密

最近在搞苹果内购,是使用微信提供的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来处理客户端发送的验证请求。
  • 从请求体中提取receiptDatapassword
  • 使用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的验证服务器。
    • 处理Apple返回的结果,并将结果返回给客户端。
    • 通过设置CORS和解析JSON请求体,确保请求和响应的正确处理。
  • 前端代码

    • 提供用户界面,允许用户输入Base64编码的内购凭证。
    • 将用户输入的凭证发送到本地服务器进行验证。
    • 处理服务器返回的结果,并在页面上显示详细的验证结果和错误信息。

通过这种方式,用户可以方便地验证Apple内购凭证,并获取详细的验证结果和错误信息。这样可以帮助开发人员和用户了解验证过程中可能出现的问题,并采取相应的措施进行处理。

  • 45
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于Java苹果内购回调signedPayload的解密,可以使用Java代码中的Base64解码和RSA解密方法进行处理。示例如下: ```java import java.security.KeyFactory; import java.security.PublicKey; import java.security.spec.X509EncodedKeySpec; import javax.crypto.Cipher; import org.apache.commons.codec.binary.Base64; public class AppleIAPUtils { // 苹果支付公钥 private static final String APPLE_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhdEEU6HmI6QHyUZ6x+ZDyt9CErA8gVrJ+Lw2yVUjz6uMS7VUgHl1cXZ+lEC28ycg2R+sC/DLKPssI/aD+LbEJ3pT1T/lfsNcX9Zflcjhq3YSNb1YZJ/1JKbd+j/0W0zQztRtCYa5PQm9fH/VeO8a5D46hijwCpBFJlnY4+L77v4z5G2HbX5h5g8RUvS46VPi4Cwz7e36oyY88Yv4/f4/dhJx9Z+SxLge3q3p+rnFJnpvgbOhHEtYl2tRfn/h/TZhNc1ULX0a0oQaR7/SHjkkTpnctoA+ud71NqEMNz1HzxIpylsX9FgO8WyVrJLf6V7ML9Q2hjGcZSdA0wIDAQAB"; /** * 对signedPayload进行解密 * * @param signedPayload 苹果服务器返回的加密字符串 * @return 解密后的字符串 */ public static String decryptSignedPayload(String signedPayload) throws Exception { // 先进行Base64解码 byte[] payloadBytes = Base64.decodeBase64(signedPayload); // 使用RSA算法解密 Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); PublicKey publicKey = getPublicKeyFromX509(); cipher.init(Cipher.DECRYPT_MODE, publicKey); byte[] resultBytes = cipher.doFinal(payloadBytes); // 返回解密后的字符串 return new String(resultBytes); } /** * 获取苹果支付公钥 */ private static PublicKey getPublicKeyFromX509() throws Exception { byte[] keyBytes = Base64.decodeBase64(APPLE_PUBLIC_KEY); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePublic(keySpec); } } ``` 以上代码实现了对苹果内购回调signedPayload进行解密的过程,其中使用了Base64解码和RSA解密两种算法。注意,在实际使用过程中需要替换APPLE_PUBLIC_KEY为自己应用的公钥。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值