apple_receipt_service.py

87 阅读2分钟

11.png

22.png

33.png

import json, requests


def request_to_product_check_receipt(receipt, num=0, shareSecret=None):
    """
    苹果订阅支付验单 for 生产环境验单
    """
    num += 1
    try:
        # url = 'https://sandbox.itunes.apple.com/verifyReceipt'
        url = 'https://buy.itunes.apple.com/verifyReceipt'
        if shareSecret:
            data = json.dumps({"receipt-data": receipt, 'password': shareSecret})
        else:
            data = json.dumps({"receipt-data": receipt})
        headers = {"Content-type": "application/json"}
        res = requests.post(url=url, data=data, headers=headers, verify=False).text
        json_object = json.loads(res)
        if json_object["status"] == 21007:
            print("检测到sandbox测试环境支付receipt 测试环境receipt验证--start...")
            res = request_to_sandbox_check_receipt(receipt, 0, shareSecret)
        return res
    except:
        # 仅验证3次,当请求出错时
        res = 'fail'
        if num < 4:
            res = request_to_product_check_receipt(receipt, num=num, shareSecret=shareSecret)
        return res


def request_to_sandbox_check_receipt(receipt, num=0, shareSecret=None):
    """
    苹果订阅支付验单 for 测试环境验单
    """
    num += 1
    try:
        url = 'https://sandbox.itunes.apple.com/verifyReceipt'
        # url = 'https://buy.itunes.apple.com/verifyReceipt'
        if shareSecret:
            data = json.dumps({"receipt-data": receipt, 'password': shareSecret})
        else:
            data = json.dumps({"receipt-data": receipt})
        headers = {"Content-type": "application/json"}
        res = requests.post(url=url, data=data, headers=headers, verify=False).text
        return res
    except:
        # 仅验证3次,当请求出错时
        res = 'fail'
        if num < 4:
            res = request_to_sandbox_check_receipt(receipt, num=num, password=shareSecret)
        return res

from typing import Optional

def verify_appstore_install_receipt(
        receipt_data: str,
        expected_bundle_id: str,
        share_secret: str = None
) -> Optional[str]:
    """
    验证app是否App Store官方安装
    调用Apple接口验证App收据,并返回App本身的 receipt.download_id 用于跟业务entity做关联关系。
    Args:
        receipt_data: 从iOS客户端获取的Base64编码的收据字符串。
        expected_bundle_id: 你的App的Bundle Identifier,用于安全校验。
        share_secret: (可选) App的共享密钥。对于包含自动续订订阅的App是必需的。
    Returns:
        如果所有验证成功,则返回 receipt.download_id (字符串)。api接口层面用于跟VPNUser做强关联
        在任何失败的情况下(网络错误、验证失败、找不到ID等),都返回 None。
    """
    # 1. 调用 Apple 验证接口,并传入共享密钥
    apple_response_str = request_to_product_check_receipt(receipt_data, shareSecret=share_secret)
    if not apple_response_str or apple_response_str == 'fail':
        print("Failed to get a valid response from Apple's verification server.")
        return None
    try:
        apple_response_json = json.loads(apple_response_str)
    except json.JSONDecodeError:
        print("Failed to parse JSON from Apple's response.")
        return None
    # 2. 验证 status
    if apple_response_json.get("status") != 0:
        print(f"Apple verification failed with status: {apple_response_json.get('status')}")
        return None
    # 3. 验证 bundle_id
    receipt_info = apple_response_json.get("receipt", {})
    if receipt_info.get("bundle_id") != expected_bundle_id:
        print(f"Bundle ID mismatch. Expected: {expected_bundle_id}, Got: {receipt_info.get('bundle_id')}")
        return None
    # 4. 查找并返回 download_id
    return f'{receipt_info.get("download_id")}'