ApplePayment.php 6.8 KB
<?php
/**
 * Created by PhpStorm.
 * User: lishuai
 * Date: 2022/2/15
 * Time: 4:23 PM
 */

namespace App\Payment;

use App\Models\MembershipGood;
use App\Models\Order;
use App\Models\User;
use App\Models\UserProfile;
use Carbon\Carbon;
use Firebase\JWT\SignatureInvalidException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Illuminate\Support\Facades\File;

class ApplePayment implements PaymentInterface
{

    const IS_SANDBOX = true;

    const CA_PATH = "/AppleRootCA-G3.pem";

    const VERIFY_URL = 'https://buy.itunes.apple.com/verifyReceipt';

    const SANDBOX_URL = 'https://sandbox.itunes.apple.com/verifyReceipt';

    const PASSWORD = 'cbd66447ac8b463daf6d5498fec5f580';

    public function __construct()
    {

    }



    public function prepare(Order $order)
    {
        // 查询订单对应的产品id
    }

    public function verify(Model $order, $token): bool
    {
        // 1. 验证apple receipt
        // 2. 返回originTransactionId
        // 3. 绑定order 与 原始事务id
        // 4. 验证成功,返回true

        $client = new Client(['headers' => ['Content-Type' => 'application/json']]);
        try{
            $response = $client->post(self::IS_SANDBOX ? self::SANDBOX_URL : self::VERIFY_URL,
                ['json' => ['receipt-data' => $token, 'password' => self::PASSWORD]])->getBody()->getContents();
            $resp = json_decode($response,true);
            Log::debug($response);
            if ($resp['status'] <= 21003) {
                $newToken = str_replace('+', ' ', $token);
                try{
                    $response = $client->post(self::IS_SANDBOX ? self::SANDBOX_URL : self::VERIFY_URL,
                        ['json' => ['receipt-data' => $newToken, 'password' => self::PASSWORD]])->getBody()->getContents();
                    Log::debug('======== 替换+ ========');
                    Log::debug($response);
                }catch (GuzzleException $exception){
                    Log::error($exception->getMessage() . 'Line:' . $exception->getLine());
                }
            }
        }catch (GuzzleException $exception){
            Log::error($exception->getMessage() . 'Line:' . $exception->getLine());
        }

        return false;

    }

    public function notify($all)
    {
        Log::debug(print_r($all,true));

        Log::debug('apple返回的数据:====================');
        $data = json_decode(base64_decode($all['signedPayload']),true);
        Log::debug(print_r($data,true));
    }

    public function notifySandbox($string)
    {
        Log::debug('sandbox返回的数据:====================');

        $components = explode('.',$string);
        if (count($components) < 3){
            Log::error("jwt错误");
            return false;
        }

        $header = json_decode(base64_decode($components[0]),true);

        // 这一步可以省略,不需要验证根证书
        $this->validateAppleRootCa($header);
        $responseBodyPayload = $this->decodeCertificate($string, $header['x5c'][0]);
        Log::debug(print_r($responseBodyPayload,true));
        /**{
        "notificationType": "SUBSCRIBED"
        "subtype": "RESUBSCRIBE"
        "notificationUUID": "99e65e59-c178-4f49-8b83-ea7d916cb568"
        "data": {
        "bundleId": "ink.parlando.parlando"
        "bundleVersion": "13"
        "environment": "Sandbox"
        "signedTransactionInfo": "xxx"
        "signedRenewalInfo": "xxx"
        }
        "version": "2.0"
        "signedDate": 1671451705697
        }
         */

        $signedTransactionInfoString = $responseBodyPayload->data->signedTransactionInfo;
        $components = explode('.',$signedTransactionInfoString);
        $header = json_decode(base64_decode($components[0]),true);
        if (count($components) < 3){
            Log::error("jwt错误");
            return false;
        }
        $signedTransactionInfo = $this->decodeCertificate($string, $header['x5c'][0]);
        Log::debug(print_r($signedTransactionInfo,true));

        $responseBodyPayload->data->signedTransactionInfo = $signedTransactionInfo;
        /**{
        "transactionId": "2000000231419425"
        "originalTransactionId": "2000000229164150"
        "webOrderLineItemId": "2000000017115109"
        "bundleId": "ink.parlando.parlando"
        "productId": "monthly_yiyan_vip"
        "subscriptionGroupIdentifier": "21080623"
        "purchaseDate": 1671451694000
        "originalPurchaseDate": 1671123372000
        "expiresDate": 1671451994000
        "quantity": 1
        "type": "Auto-Renewable Subscription"
        "inAppOwnershipType": "PURCHASED"
        "signedDate": 1671451705700
        "environment": "Sandbox"
        }*/
        $signedRenewalInfoString = $responseBodyPayload->data->signedRenewalInfo;
        $components = explode('.',$signedRenewalInfoString);
        $header = json_decode(base64_decode($components[0]),true);
        if (count($components) < 3){
            Log::error("jwt错误");
            return false;
        }
        $signedRenewalInfo = $this->decodeCertificate($string, $header['x5c'][0]);
        Log::debug(print_r($signedRenewalInfo,true));
        $responseBodyPayload->data->signedRenewalInfo = $signedRenewalInfo;
        /**{
        "originalTransactionId": "2000000229164150"
        "autoRenewProductId": "monthly_yiyan_vip"
        "productId": "monthly_yiyan_vip"
        "autoRenewStatus": 1
        "signedDate": 1671451705673
        "environment": "Sandbox"
        "recentSubscriptionStartDate": 1671451694000
        }*/
        Log::debug(print_r($responseBodyPayload,true));
    }

    private function validateAppleRootCa($header)
    {
        $lastIndex = count($header['x5c']) - 1;

        $certificate = $this->getCertificate($header['x5c'][$lastIndex]);

        if ($certificate != File::get(public_path(self::CA_PATH))) return false;

        return true;
    }

    private function getCertificate($string)
    {
        $certificate = "-----BEGIN CERTIFICATE-----" . PHP_EOL;
        $certificate .= chunk_split($string, 64, PHP_EOL);
        $certificate .= "-----END CERTIFICATE-----" . PHP_EOL;
        return $certificate;
    }

    private function decodeCertificate($string, $appleCertificate)
    {
        $certificate = $this->getCertificate($appleCertificate);

        $cert_object = openssl_x509_read($certificate);
        $pkey_object = openssl_pkey_get_public($cert_object);
        $pkey_array = openssl_pkey_get_details($pkey_object);
        $public_key = $pkey_array["key"];

        try{
            $decode = JWT::decode($string, new Key($public_key, "ES256"));
            return $decode;
        }catch (SignatureInvalidException $exception){
            Log::error("Signature Invalid!");
            return false;
        }
    }
}