下面给大家介绍使用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¤cy=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>