Laravel对接Paypal支付以及通知webhook的签名验证


先说下paypal的支付流程,借用一张网上找到的的流程图。
file
大致流程为
1.调用创建订单接口(传入订单金额以及两个return url,成功以及失败的地址),然后将用户重定向到paypal返回的支付链接
2.在return url里捕获订单执行扣款操作
3.回调页面收到paypal的订单完成通知,此时支付完成

paypal v1 Api sdk https://github.com/paypal/PayPal-PHP-SDK
paypal v2 Api SDK https://github.com/paypal/Checkout-PHP-SDK/
目前v1版本的Api接口已经不推荐使用,一开始我在对接v1版本的时候,webhook的验签一直无法通过,随换成v2的接口了。

首先coponser 安装Checkout-PHP-SDK

composer require paypal/paypal-checkout-sdk

创建订单

$request = new OrdersCreateRequest();
        $request->prefer('return=representation');
        $request->body = [
            "intent" => "CAPTURE",
            "purchase_units" => [[
                "reference_id" => $order->order_sn,//订单号
                "amount" => [
                    "value" => $order->amount,//订单金额
                    "currency_code" => $order->currency//币种 usd
                ]
            ]],
            "application_context" => [
                "cancel_url" => route('payment.callback',['success'=>'false']),//用户取消支付重定向页面
                "return_url" => route('payment.callback',['success'=>'true'])//用户同意支付重定向页面(此页面paypal会携带Token、PayerID参数,在这里捕获订单执行扣款操作)
            ]
        ];
        try {
            // Call API with your client and get a response for your call
            $response = $this->client->execute($request);
            $link='';
            if($response->statusCode==201){
                for ($i = 0; $i < count($response->result->links); ++$i)
                {
                    $link = $response->result->links[$i];
                    if($link->rel=='approve'){//link rel =approve 就是将用户重定向的paypal的支付地址
                        break;
                    }
                }
                if($link->rel=='approve'){
                    $order->third_order_sn=$response->result->id;//这里记录下返回的id,回调通知接收到的id就是这个paypal的订单id
                    $order->save();
                    return $link->href;
                }
            }
            throw new \Exception("create order fail");
        }catch (HttpException $ex) {
            Log::error('paypal create order fail:'.$ex->getMessage());
        }

paypal将用户重定向到return url捕获订单执行扣款逻辑

$success = request()->get('success');
        if ($success == 'false') {
            echo 'cancel';die;
        }
        $token = request()->get('token');
        $payerId = request()->get('PayerID');
        if (!isset($success, $token, $payerId)||$success=='false') {
            echo 'pay fail';die;
        }
        $request = new OrdersCaptureRequest($token);
        $request->prefer('return=representation');
        try {
            // Call API with your client and get a response for your call
            $response = $this->client->execute($request);
            // If call returns body in response, you can get the deserialized version from the result attribute of the response
            if($response->result->status=='COMPLETED'){
                echo 'Payment successful!';
            }else{
                echo 'Order processing..';
            }
        }catch (HttpException $ex) {
            echo 'error';
            Log::info('paypal execute fail'.$ex->getMessage());
        }

paypal webhook 通知处理

$dataUrlString = file_get_contents('php://input');
        $json = json_decode($dataUrlString,true);
        if(!empty($json)){
            Log::debug("paypal notify info:\r\n".$dataUrlString);
        }else{
            Log::debug("paypal notify fail:null");
        }
        $webhookId = $this->config->webhookId;//这里的webhookid 在paypal的开发者面板里进入应用添加webhook地址后会有一个webhook id
        $headers = request()->header();
        $headers = array_change_key_case($headers, CASE_UPPER);
        $paypalTransmissionId = $headers['PAYPAL-TRANSMISSION-ID'][0] ?? '';
        $timeStamp = $headers['PAYPAL-TRANSMISSION-TIME'][0] ?? '';
        $crc32 = crc32($dataUrlString);
        if (!$paypalTransmissionId || !$timeStamp || !$webhookId || !$crc32) {
            return false;
        }
        $sigString = "{$paypalTransmissionId}|{$timeStamp}|{$webhookId}|{$crc32}";
        // 通过PAYPAL-CERT-URL头信息去拿公钥
        $publicKey = openssl_pkey_get_public(file_get_contents($headers['PAYPAL-CERT-URL'][0]));
        $details = openssl_pkey_get_details($publicKey);
        $verifyResult = openssl_verify($sigString, base64_decode($headers['PAYPAL-TRANSMISSION-SIG'][0]), $details['key'], 'sha256WithRSAEncryption');
        if ($verifyResult === 1) {
            // 验证成功
            if($json['event_type']=='PAYMENT.CAPTURE.COMPLETED'){//支付完成
                $order_sn = $json['resource']['supplementary_data']['related_ids']['order_id'];//之前存的paypal订单id去查找我们系统里的订单
                $total = $json['resource']['amount']['value'];
                $payNum=$json['resource']['id'];
                $order=OrderServices::getInstance()->getOrderByThirdOrderSn($order_sn);
                if($order){

                    if($order->amount==$total){
                        //标记订单支付成功
                        OrderServices::getInstance()->markOrderPaid($order,$total,$payNum);
                    }
                    echo 'success';

                }else{
                    echo 'Order not found';
                }
            }
        } else {
            Log::info('paypal sign error');
            return 'sign error';
        }

本文地址:https://www.blear.cn/article/laravel-paypal

转载时请以链接形式注明出处

评论
受监管部门要求,个人网站不允许评论功能,评论已关闭,抱歉!