ApplePayment.php 10.1 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);
            if ($resp['status'] > 0) {
                Log::debug($response);
                return false;
            }
            $originalTransactionId = $resp['receipt']['pending_renewal_info']['originalTransactionId'];

            // 绑定order 和 originalTransactionId
            $order->pay_number = $originalTransactionId;
            $order->status = Order::PAID;
            $order->save();

            // 修改用户状态
            $profile = UserProfile::query()->find($order->user_id);
            $profile->is_vip = UserProfile::WAIT_VIP;
            $profile->save();
            return true;
        } 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);
        $header = json_decode(base64_decode($components[0]),true);
        // 这一步可以省略,不需要验证根证书
//        $this->validateAppleRootCa($header);
        $responseBodyPayload = $this->decodeCertificate($string, $header['x5c'][0]);
        $signedTransactionInfoString = $responseBodyPayload->data->signedTransactionInfo;
        $components = explode('.',$signedTransactionInfoString);
        $header = json_decode(base64_decode($components[0]),true);
        $signedTransactionInfo = $this->decodeCertificate($signedTransactionInfoString, $header['x5c'][0]);
        $responseBodyPayload->data->signedTransactionInfo = $signedTransactionInfo;
        $signedRenewalInfoString = $responseBodyPayload->data->signedRenewalInfo;
        $components = explode('.',$signedRenewalInfoString);
        $header = json_decode(base64_decode($components[0]),true);
        $signedRenewalInfo = $this->decodeCertificate($signedRenewalInfoString, $header['x5c'][0]);
        $responseBodyPayload->data->signedRenewalInfo = $signedRenewalInfo;

        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": {
                    "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"
                }
                "signedRenewalInfo": {
                    "originalTransactionId": "2000000229164150"
                    "autoRenewProductId": "monthly_yiyan_vip"
                    "productId": "monthly_yiyan_vip"
                    "autoRenewStatus": 1
                    "signedDate": 1671451705673
                    "environment": "Sandbox"
                    "recentSubscriptionStartDate": 1671451694000
                }
            }
            "version": "2.0"
            "signedDate": 1671451705697
        }*/

        switch ($responseBodyPayload->notificationType){
            case "SUBSCRIBED":
                if ($responseBodyPayload->subtype == 'INITIAL_BUY'){  //首次购买

                }

                if ($responseBodyPayload->subtype == 'RESUBSCRIBE'){  //重新订阅
                    // 应该再创建一个订单。。。
                }

                $originalTransactionId = $responseBodyPayload->data->signedTransactionInfo->originalTransactionId;

                $order = Order::query()->where('pay_number', $originalTransactionId)->first();
                if(!$order) {
                    Log::error('没有找到对应的订单,说明apple服务端比客户端的回调快!');
                    return;
                }

                /** 修改订单状态*/
                $order->pay_time = Carbon::now();
                $order->pay_type = $responseBodyPayload->data->signedTransactionInfo->type;
                $order->save();
                /** 给用户加会员*/
                $user = UserProfile::query()->find($order->user_id);
                $user->is_vip = 1;
                $user->create_vip_time = Carbon::createFromTimestampMs($responseBodyPayload->data->signedTransactionInfo->purchaseDate);
                $user->expire_vip_time = Carbon::createFromTimestampMs($responseBodyPayload->data->signedTransactionInfo->expiresDate);
                $user->buy_number += 1;
                $user->buy_amount += $order->pay_amount;
                $user->last_buy_time = Carbon::now();
                $user->save();
                break;
            case "DID_RENEW":
                // 应该再创建一个订单。。。
                $originalTransactionId = $responseBodyPayload->data->signedTransactionInfo->originalTransactionId;
                $order = Order::query()->where('pay_number', $originalTransactionId)->first();
                if(!$order) {
                    Log::error('没有找到对应的订单,说明apple服务端比客户端的回调快!');
                    return;
                }
                /** 给用户加会员*/
                $user = UserProfile::query()->find($order->user_id);
                $user->is_vip = 1;
                $user->expire_vip_time = Carbon::createFromTimestampMs($responseBodyPayload->data->signedTransactionInfo->expiresDate);
                $user->buy_number += 1;
                $user->buy_amount += $order->pay_amount;
                $user->last_buy_time = Carbon::now();
                $user->save();
                break;
            case "EXPIRED":
                // 应该再创建一个订单。。。
                $originalTransactionId = $responseBodyPayload->data->signedTransactionInfo->originalTransactionId;
                $order = Order::query()->where('pay_number', $originalTransactionId)->first();
                if(!$order) {
                    Log::error('没有找到对应的订单,说明apple服务端比客户端的回调快!');
                    return;
                }
                /** 给用户取消会员*/
                $user = UserProfile::query()->find($order->user_id);
                $user->is_vip = 0;
                $user->expire_vip_time = Carbon::createFromTimestampMs($responseBodyPayload->data->signedTransactionInfo->expiresDate);
                $user->save();
                break;
            default:
                Log::debug('特殊通知类型:'.$responseBodyPayload->notificationType.' ====================');
                Log::debug(var_export($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;
        }
    }
}