thinkphp6 实现PC端对接paypal支付

下面给大家介绍使用PayPal Orders v2 API和PayPal JavaScript SDK,集成的paypal支付实现方法。

1.编写paypal接口类,具体的请查看paypal对接文档:Orders

<?php
/*paypal服务端接口类*/
use HttpClient;
use think\facade\Log;
use think\facade\Cache;

class Paypal{

	protected $client_id 		= "";			//项目API客户端ID
	protected $client_secret	= "";			//指的是项目API机密密钥
	protected $endpoint 		= '';			//接口请求地址
	protected $authorization	= '';			//API Authorization接口返回的OAuth令牌
	protected $soft_descriptor 	= 'xxxxx';	//软描述符,用于买方的信用卡对账单中显示商家信息

	public function __construct($config)
	{
		$this->endpoint 	= $config['endpoint'];
		$this->client_id 	= $config['client_id'];
		$this->client_secret= $config['secret'];

		$auth = Cache::store('redis')->get('paypal_access_token');

		if(!empty($auth)){
			$this->authorization = $auth;
		}
		else{
			$this->oauthToken();
		}
	}

	/**
	 * https://developer.paypal.com/api/rest/authentication/
	 * 
	 * 获取OAuth2.0 访问令牌
	 * (expires_in:paypal那边确认过期时间是9个小时,每次请求该接口都会生成新的令牌)
	 * 
	 * 获取API凭据
	 * 
	 */
	private function oauthToken(){

		$url = $this->endpoint.'/v1/oauth2/token';

		$this->authorization = '';

		$httpClient = new HttpClient();

		//必须拼接成字符串,不能用数组,否则接口会报错
		$param = "grant_type=client_credentials";

		$header = ['Authorization'	=> 'Basic '.base64_encode($this->client_id . ':' . $this->client_secret),
					'content-type'	=> 'application/x-www-form-urlencoded'
		];

		$response = $httpClient->setTimeout(20)->setHeader($header)->post($url,$param);

		if($httpClient->getCode()=='200'){

			$result = json_decode($response,true);

			if(isset($result['access_token'])){

				$this->authorization = 'Bearer '.$result['access_token'];

				//OAuth令牌凭证保存在redis
				Cache::store('redis')->set('paypal_access_token','Bearer '.$result['access_token'],$result['expires_in']-40);
			}
			else{
				Log::error('paypal 获取API凭据接口token获取失败:'.$response);
			}
		}
		else{
			Log::error('paypal 获取API凭据接口返回失败:'.$response.'; HTTP status codes:'.$httpClient->getCode().';error_msg:'.$httpClient->getErrorMsg());
		}
	}
	/**
	 * 
	 * https://developer.paypal.com/docs/api/orders/v2/#orders_create
	 * 
	 * Creates an order
	 * 
	 * @param float $amount  订单金额
	 * @param string $ordersn 自己平台的订单编号
	 * @param int $uid 买家ID
	 * 
	 */
	public function createOrder($amount,$ordersn,$uid){

		$url = $this->endpoint.'/v2/checkout/orders';
		$result = [ 
			"code" 	=> 500,
			"msg" 	=> "",
		];

		if(empty($this->authorization)){

			$result['msg'] = 'paypal authorized error';

			return $result;
		}

		$httpClient = new HttpClient();

		$header = ['authorization'	=> $this->authorization,
					'content-type'	=> 'application/json'
		];

		$param['intent'] = 'CAPTURE';
		$param['purchase_units'][] = ['amount'			=>['currency_code'=> 'USD','value'=>$amount],
									'description'		=>$ordersn,
									'soft_descriptor'	=>'CFMCHINA',
									'custom_id'			=>$uid
									];
	
		$param = json_encode($param);

		$response = $httpClient->setTimeout(20)->setHeader($header)->post($url,$param);

		if($httpClient->getCode()=='200' || $httpClient->getCode()=='201'){

			Log::info('paypal v2/checkout/orders接口成功:'.$response.';HTTP status codes:'.$httpClient->getCode());

			$re = json_decode($response,true);

			$result['code'] = 200;
			$result['data'] = $re;
		}
		else{
			Log::error('paypal v2/checkout/orders接口失败:'.$response.';HTTP status codes:'.$httpClient->getCode());

			$re = json_decode($response,true);

			$result['msg'] = $re['message'] ? $re['message'] : "Requesting PayPal's interface failed, please check the network!";
		}

		return $result;
	}

	/**
	 * https://developer.paypal.com/docs/api/orders/v2/#orders_capture
	 * 
	 * @param $orderID paypal提供的订单ID
	 */
	public function capture($orderID){

		$url = $this->endpoint."/v2/checkout/orders/{$orderID}/capture";
		$result = [ 
			"code" 	=> 500,
			"msg" 	=> "",
		];

		if(empty($this->authorization)){

			$result['msg'] = 'paypal authorized error';

			return $result;
		}

		$httpClient = new HttpClient();

		$header = ['authorization'	=> $this->authorization,
					'content-type'	=> 'application/json'
		];

		$response = $httpClient->setTimeout(20)->setHeader($header)->post($url);

		if($httpClient->getCode()=='200' || $httpClient->getCode()=='201'){

			Log::error("paypal v2/checkout/orders/{$orderID}/capture接口成功:".$response.';HTTP status codes:'.$httpClient->getCode());

			$re = json_decode($response,true);

			$result['code'] = 200;
			$result['data'] = $re;
		}
		else{
			Log::error("paypal v2/checkout/orders/{$orderID}/capture接口失败:".$response.';HTTP status codes:'.$httpClient->getCode());

			$re = json_decode($response,true);

			$result['msg'] = $re['message'] ? $re['message'] : "Requesting PayPal's interface failed, please check the network 2!";
		}

		return $result;
	}
}
?>

2.编写curl请求类

<?php
class HttpClient
{

    protected $_headers = [
        'accept'        => 'application/json',
        'accept-charset'=> 'utf-8'
    ];
    protected $_timeout = 10;        //超时时间
    protected $_auth = '';
    protected $_errorMsg;
    protected $_httpcode = 0;       //response返回的http code


    protected function _getHeaders($headers)
    {
        $arr = [];
        foreach ($headers as $k => $v) {
            $arr[] = $k . ': ' . $v;
        }
        return $arr;
    }

    /**
     * 设置请求头
     * @return object 返回自己对象$this, 可以用于链式操作
     */
    public function setHeader($headers=[])
    {
        $this->_headers = array_merge($this->_headers, $headers);

        return $this;
    }
    /**
     * 设置过期时间(单位:秒)
     */
    public function setTimeout($val){

        $this->_timeout=intval($val);

        return $this;
    }

    public function setBaseAuth($user,$pwd){
        $this->_auth=$user.':'.$pwd;
        return $this;
    }

    /**
     * @param boolean $returnHeader: 是否返回response头部信息
     */
    public function request($url, $params=null, $method = 'GET', $returnHeader = 0,$headers=null)
    {
        $headers =$this->_getHeaders( $headers? $headers:$this->_headers);

        $ci = curl_init();
        //curl_setopt($ci, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
        curl_setopt($ci, CURLOPT_HEADER, $returnHeader);
        curl_setopt($ci, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ci, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ci, CURLOPT_TIMEOUT, $this->_timeout);

        if($this->_auth){
            curl_setopt($ci, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
            curl_setopt($ci, CURLOPT_USERPWD, $this->_auth);
        }

        $ifSSL = strpos($url,"https://");
        if($ifSSL !==false){
            curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, false);        //不验证 SSL 对等证书
        }

        switch ($method) {
            case 'POST':
                curl_setopt($ci, CURLOPT_POST, TRUE);
                curl_setopt($ci, CURLOPT_POSTFIELDS, $params); 
                break;
            case 'PUT':
                curl_setopt($ci, CURLOPT_CUSTOMREQUEST, "PUT");
                curl_setopt($ci, CURLOPT_POSTFIELDS, $params);
                break;
            case 'PATCH':
                curl_setopt($ci, CURLOPT_CUSTOMREQUEST, "PATCH");
                curl_setopt($ci, CURLOPT_POSTFIELDS, $params);
                break;
            case 'DELETE':
                curl_setopt($ci, CURLOPT_CUSTOMREQUEST, "DELETE");
                break;
            case 'OPTION':
                curl_setopt($ci, CURLOPT_CUSTOMREQUEST, "OPTION");
                break;
            case 'GET':
                if($params){
                    $url.=strpos($url,'?')===false?'?':'&';
                    $url.= is_array($params)?http_build_query($params):$params;
                }
                break;
        }
        
        curl_setopt($ci, CURLOPT_URL, $url);
        //当需要通过curl_getinfo来获取发出请求的header信息时,该选项需要设置为true
        curl_setopt($ci, CURLINFO_HEADER_OUT, true);

        $response = curl_exec($ci);

        if (FALSE === $response) {

            $this->_errorMsg = "class HttpClient request url '{$url}' error. ". curl_error($ci);  
        }
        else{
            $this->_httpcode = curl_getinfo($ci, CURLINFO_HTTP_CODE);
        }
        
        curl_close($ci);
        return $response;
    }

    /**
     * 返回最近一次 cURL 操作的文本错误详情
     * 
     */
    public function getErrorMsg(){

        return $this->_errorMsg;
    }
    public function getCode(){
        return $this->_httpcode;
    }

    public function post($url, $params=null, $headers=null)
    {
        return $this->request($url, $params, 'POST',0,$headers);
    }

    public function postform($url,$parmas=null){
        return $this->request($url, $parmas, 'POST',0,['content-type'=>'application/x-www-form-urlencoded']);

    }

    public function get($url, $params=null, $headers=null)
    {
        return $this->request($url, $params, 'GET',0,$headers);
    }

    public function put($url, $params=null, $headers=null)
    {
        return $this->request($url, $params, 'PUT',0,$headers);
    }

    public function patch($url, $params=null, $headers=null)
    {
        return $this->request($url, $params, 'PATCH',0,$headers);
    }

    public function delete($url, $params=null, $headers=null)
    {
        return $this->request($url, $params, 'DELETE',0,$headers);
    }

    public function option($url, $params=null, $headers=null)
    {
        return $this->request($url, $params, 'OPTION',0,$headers);
    }
}

3.编写paypal支付控制器,用于前端调用

<?php
/**
 * paypal支付相关控制器
 * 
 */
namespace app\home\controller;

use app\home\HomeController;
use think\facade\Db;
use think\facade\Cache;

class Pay extends HomeController
{
    /**
     * 调用接口创建paypal订单
     * paypal createOrder 
     * @return mixed
     */
    public function paypalCreateorder()
    {
        $ordersn = trim(input('ordersn'),'');//自己系统的订单编号

        if($ordersn==''){
            return json(['code'=>-1,'msg'=>"invalid order param."]);
        }

        $order = Db::name('order')->field('status,uid')->where('ordersn',$ordersn)->find();

        if(!$order){
            return json(['code'=>-1,'msg'=>"Can't find Order."]);
        }

        $amount = Cache::store('redis')->get('paypal_'.$ordersn);

        if(empty($amount)){

            return json(['code'=>-1,'msg'=>"The parameter is invalid, please refresh the page."]);
        }

        $paypal = new \Paypal(config('paypal'));

        $rs = $paypal->createOrder($amount,$ordersn,$order['uid']);

        return json($rs);
    }

    /**
     * 调用接口paypal订单验证
     *  paypal capture 
     * @return mixed
     */
    public function paypalCapture()
    {
        $id = trim(input('id'),'');     //paypal的 v2/checkout/orders接口返回的订单ID

        if($id==''){
            return json(['code'=>-1,'msg'=>"The order'id is required."]);
        }

        $paypal = new \Paypal(config('paypal'));

        $rs = $paypal->capture($id);

        return json($rs);
    }

}

4.编写前端代码

<div id="payment-box">
    <div id="paypal-button"></div>
</div>

<script type="text/javascript">
    //支付完成(COMPLETED)后回调操作
    function callBackPayment(url){
        location.href = url;
    }
</script>
<!--sandbox &buyer-country=US -->
<script src="https://www.paypal.com/sdk/js?client-id=你在paypal上申请的client_id&currency=USD"></script>
<script>
    paypal.Buttons({
        // Call your server to set up the transaction
        createOrder: function(data, actions) {
            return fetch('/pay/paypalCreateorder?ordersn=你的订单编号', {
                method: 'post'
            }).then(function(res) {
                return res.json();
            }).then(function(returnData) {
                console.log('returnData',returnData);
                if(returnData.code==200){
                    return returnData.data.id;
                }
                else{
                    alert(returnData.msg);
                }
            });
        },

        // Call your server to finalize the transaction
        onApprove: function(data, actions) {
            return fetch('/pay/paypalCapture?id=' + data.orderID, {
                method: 'post'
            }).then(function(res) {
                return res.json();
            }).then(function(orderData) {
                console.log('onApprove orderData',orderData);

                if(orderData.code==200){
                    if(orderData.data.status == "COMPLETED"){
                        callBackPayment("{url('order/paysuccess')}");
                    }
                }
                else{
                    console.log('onApprove',orderData.msg);
                }

                // Three cases to handle:
                //   (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
                //   (2) Other non-recoverable errors -> Show a failure message
                //   (3) Successful transaction -> Show confirmation or thank you

                // This example reads a v2/checkout/orders capture response, propagated from the server
                // You could use a different API or structure for your 'orderData'
                /*var errorDetail = Array.isArray(orderData.details) && orderData.details[0];

                if (errorDetail && errorDetail.issue === 'INSTRUMENT_DECLINED') {
                    return actions.restart(); // Recoverable state, per:
                    // https://developer.paypal.com/docs/checkout/integration-features/funding-failure/
                }

                if (errorDetail) {
                    var msg = 'Sorry, your transaction could not be processed.';
                    if (errorDetail.description) msg += '\n\n' + errorDetail.description;
                    if (orderData.debug_id) msg += ' (' + orderData.debug_id + ')';
                    return alert(msg); // Show a failure message (try to avoid alerts in production environments)
                }

                // Successful capture! For demo purposes:
                console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
                var transaction = orderData.purchase_units[0].payments.captures[0];
                alert('Transaction '+ transaction.status + ': ' + transaction.id + '\n\nSee console for all available details');*/
            });
        }

    }).render('#paypal-button');
</script>

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值