官方文档
https://developer.paypal.com/docs/integration/direct/payments/paypal-payments/#create-paypal-payment
准备工作
先要在官网注册PayPal账号,然后打开沙箱账号管理。
能看到有自动创建的2个沙箱账号,选中PERSONAL账号点击Profile,设置自定义密码(记录该账号和密码)。
然后点击左边菜单栏DASHBOARD-My Apps & Credentials创建沙箱应用.
然后回到沙箱账号管理选中BUSSINESS账号,点击Profile,点击API Credentials,点击REST Apps里的App name。
进入应用页面后,记录下Client ID和Secret。
支付流程
payment相关接口的支付流程为:
1、获取accesstoken
2、商户根据订单填充订单信息(事例index.php中create_payment()相关操作)
3、填充完后将数据POST到'/v1/payments/payment'
接口(事例index.php中create_payment()相关操作)
4、根据返回值,跳转到返回值中links里的’rel’=‘approval_url’对应的地址中(事例index.php中create_payment()相关操作)
5、用户(登陆准备工作时记录的PERSONAL账号)在该网页完成付款确认点击继续则跳转到执行payment的链接(create_payment()中设置的’redirect_urls’中的’return_url’)并把payerid和paymentid设置在url的参数中
6、执行payment的链接中,商户需要获取到这2个参数后调用'/v1/payments/payment/{paymentid}/execute'
接口,完成支付流程。(事例index.php中excute()相关操作)
事例
下面是简单封装了一下的payment相关操作的接口调用。访问index.php,create_payment函数构造订单信息,授权成功跳转localhost?a=excute&PayerID=xxx&paymentid=xxx,excute函数调用paypal类的ExcutePayment方法完成付款。
Paypal.php类
<?php
class Paypal{
protected $appId;
protected $appSecret;
protected $baseUrl='https://api.sandbox.paypal.com';
/*构造函数,把实例化Paypal类时传进来的appId和appSecret保存起来
*
*
* */
function __construct($appId,$appSecret)
{
$this->appId = $appId;
$this->appSecret = $appSecret;
}
/*curl post
*param
* $extreUrl string 接口地址
* $data array 提交的数据 为空时为get请求
* $header array 请求头信息
* $Oauth bool 是否使用Oauth验证
* return array 响应信息
* */
protected function httpRequest($extreUrl,$data,$header,$Oauth=false)
{
$url=$this->baseUrl.$extreUrl;
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST,FALSE);
if($Oauth){
curl_setopt($curl, CURLOPT_USERPWD,$this->appId.":".$this->appSecret);
}
//获取请求头信息
//curl_setopt($curl, CURLINFO_HEADER_OUT, TRUE);
curl_setopt($curl, CURLOPT_HTTPHEADER,$header);
if($data){
curl_setopt($curl, CURLOPT_POSTFIELDS,$data);
curl_setopt($curl, CURLOPT_POST,1);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($curl);
//获取请求头信息
//var_dump(curl_getinfo($curl)["request_header"]);
curl_close($curl);
return json_decode($output,true);
}
/*请求头部加上Authorization
*param
* $extreUrl string 接口地址
* $data array 请求的参数
* return array 返回信息
* */
protected function httpRequestWithAuth($extreUrl,$data=[])
{
$header[]='Content-Type:application/json';
$header[]='Authorization:Bearer '.$this->getAccessToken();
return $this->httpRequest($extreUrl,$data,$header);
}
/*转化数组为键值对形式
*param
* $array array 需要转化的数组
* return string 转化后的字符串:参数1=值1&参数2=值2
* */
protected function array2keyvalue($array)
{
$res=[];
foreach($array as $k=>$v){
$res[]=$k.'='.$v;
}
return implode('&',$res);
}
/*获取accesstoken
* 判断存储的accesstoken是否过期,过期则调用接口获取新的accesstoken否则直接返回。
* 可以把文件读取改成缓存读取 数据库读取。
* return string accesstoken值
* */
protected function getAccessToken()
{
//从paypal_access_token.json文件中读取accesstoken和过期时间
$data = json_decode(file_get_contents("paypal_access_token.json"),true);
if ($data['expire_time'] < time()) {
$extreUrl='/v1/oauth2/token';
$postdata['grant_type']='client_credentials';
$postdata=$this->array2keyvalue($postdata);
$header[]="Content-type: application/x-www-form-urlencoded";
$res=$this->httpRequest($extreUrl,$postdata,$header,true);
$access_token = $res['access_token'];
if ($access_token) {
$data['expire_time'] = time()+$res['expires_in'];
$data['access_token'] = $access_token;
$fp = fopen("paypal_access_token.json", "w");
fwrite($fp, json_encode($data));
fclose($fp);
}
} else {
$access_token = $data['access_token'];
}
return $access_token;
}
/*创建payment
*
*/
public function CreatePayment($array)
{
$extreUrl='/v1/payments/payment';
$data['intent']='sale';
$data['payer']=['payment_method'=>'paypal'];
$transactions['amount']=$array['amount'];
$transactions['description']=$array['description'];
$transactions['custom']=$array['orderid'];
$transactions['invoice_number']=$array['orderid'];
$transactions['item_list']['items']=$array['items'];
$transactions['payment_options']['allowed_payment_method']='INSTANT_FUNDING_SOURCE';
$transactions['item_list']['shipping_address']=$array['shipping_address'];
$data['transactions'][]=$transactions;
$data['note_to_payer']=$array['note_to_payer'];
$data['redirect_urls']=$array['redirect_urls'];
return $this->httpRequestWithAuth($extreUrl,json_encode($data));
}
/* 获取 完成payment
*param
* paymentId 创建payment后返回的ID
* PayerID 用户id 用户完成授权后跳转的excute页面中url带的参数
**/
public function ExcutePayment($array)
{
$extreUrl='/v1/payments/payment/'.$array['paymentId'].'/execute';
$data['payer_id']=$array['PayerID'];
return $this->httpRequestWithAuth($extreUrl,json_encode($data));
}
/* 获取payment详情
*param
* paymentId 创建payment后返回的ID
**/
public function PaymentInfo($array)
{
$extreUrl='/v1/payments/payment/'.$array['paymentId'];
return $this->httpRequestWithAuth($extreUrl);
}
}
index.php代码
<?php
include('Paypal.php');
$appId='你的Client ID';
$appSecret='你的Secret';
$paypal=new Paypal($appId,$appSecret);
$a=!is_null($_GET['a'])?$_GET['a']:'create_payment';
$a($paypal);
function create_payment($paypal){
$array['description']='订单说明';
//orderid调用paypal类CreatePayment时传入‘invoice_number’中 ‘custom’字段可根据需要填充更多信息
$array['orderid']=time();
//订单商品详情1
$product1['name']='hat';
$product1['sku']=1;
$product1['description']='Brown hat';
$product1['price']="3";
$product1['currency']='USD';
$product1['tax']='0.01';
$product1['quantity']=5;
//订单商品详情2
$product2['name']='handbag';
$product2['sku']='xxxx';
$product2['description']='Black handbag.';
$product2['price']="15";
$product2['currency']='USD';
$product2['tax']='0.02';
$product2['quantity']=1;
$array['items'][]=$product1;
$array['items'][]=$product2;
/*
订单费用$array['amount']
其中total 要和subtotal的计费计算统一不然会报错
subtotal+tax+shipping+handing_fee+shipping_discount+insurance =total
*/
$array['amount']['total']='30.11';
$array['amount']['currency']='USD';
//订单收费详情
$array['amount']['details']['subtotal']="30.00";
$array['amount']['details']['tax']="0.07";
$array['amount']['details']['shipping']="0.03";
$array['amount']['details']['handling_fee']="1.00";
$array['amount']['details']['shipping_discount']="-1.00";
$array['amount']['details']['insurance']="0.01";
//订单收货地址详情
$array['shipping_address']['recipient_name']="Brian Robinson";
$array['shipping_address']['line1']="4th Floor";
$array['shipping_address']['line2']="Unit #34";
$array['shipping_address']['city']="San Jose";
$array['shipping_address']['country_code']="US";
$array['shipping_address']['postal_code']="95131";
$array['shipping_address']['phone']="011862212345678";
$array['shipping_address']['state']="CA";
//提示信息
$array['note_to_payer']="Contact us for any questions on your order.";
//执行付款回调地址
$array['redirect_urls']['return_url']="http://localhost?a=excute";
//取消付款回调地址
$array['redirect_urls']['cancel_url']="http://localhost?a=cancel";
$res=$paypal->CreatePayment($array);
/*这里需要判断是否成功
*
* */
if(!$res['debug_id'] && strtolower($res['state'])!='failed'){
foreach($res['links'] as $v){
if(strtolower($v['rel'])=='approval_url'){
header('Location: '.$v['href']);
exit;
}
}
}else{
echo $res['message'];
}
}
function excute($paypel){
$data['PayerID']=$_GET['PayerID'];
$data['paymentId']=$_GET['paymentId'];
$res=$paypel->ExcutePayment($data);
if(strtolower($res['state'])=='approved'){
//完成付款
}
}
function cancel(){
echo '用户取消';
}
function check($paypel){
$data['paymentId']=$_GET['paymentId'];
$res=$paypel->PaymentInfo($data);
var_dump($res);
}
create_payment 返回值数据结构
array(8) {
["id"]=>
string(30) "PAYID-LR7Y6YA3UM97660A4514331H"
["intent"]=>
string(4) "sale"
["state"]=>
string(7) "created"
["payer"]=>
array(1) {
["payment_method"]=>
string(6) "paypal"
}
["transactions"]=>
array(1) {
[0]=>
array(7) {
["amount"]=>
array(3) {
["total"]=>
string(5) "30.11"
["currency"]=>
string(3) "USD"
["details"]=>
array(6) {
["subtotal"]=>
string(5) "30.00"
["tax"]=>
string(4) "0.07"
["shipping"]=>
string(4) "0.03"
["insurance"]=>
string(4) "0.01"
["handling_fee"]=>
string(4) "1.00"
["shipping_discount"]=>
string(5) "-1.00"
}
}
["description"]=>
string(12) "订单说明"
["custom"]=>
string(10) "1551863640"
["invoice_number"]=>
string(10) "1551863640"
["payment_options"]=>
array(3) {
["allowed_payment_method"]=>
string(22) "INSTANT_FUNDING_SOURCE"
["recurring_flag"]=>
bool(false)
["skip_fmf"]=>
bool(false)
}
["item_list"]=>
array(2) {
["items"]=>
array(2) {
[0]=>
array(7) {
["name"]=>
string(3) "hat"
["sku"]=>
string(1) "1"
["description"]=>
string(9) "Brown hat"
["price"]=>
string(4) "3.00"
["currency"]=>
string(3) "USD"
["tax"]=>
string(4) "0.01"
["quantity"]=>
int(5)
}
[1]=>
array(7) {
["name"]=>
string(7) "handbag"
["sku"]=>
string(4) "xxxx"
["description"]=>
string(14) "Black handbag."
["price"]=>
string(5) "15.00"
["currency"]=>
string(3) "USD"
["tax"]=>
string(4) "0.02"
["quantity"]=>
int(1)
}
}
["shipping_address"]=>
array(8) {
["recipient_name"]=>
string(14) "Brian Robinson"
["line1"]=>
string(9) "4th Floor"
["line2"]=>
string(8) "Unit #34"
["city"]=>
string(8) "San Jose"
["state"]=>
string(2) "CA"
["postal_code"]=>
string(5) "95131"
["country_code"]=>
string(2) "US"
["phone"]=>
string(15) "011862212345678"
}
}
["related_resources"]=>
array(0) {
}
}
}
["note_to_payer"]=>
string(43) "Contact us for any questions on your order."
["create_time"]=>
string(20) "2019-03-06T09:14:07Z"
["links"]=>
array(3) {
[0]=>
array(3) {
["href"]=>
string(81) "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-LR7Y6YA3UM97660A4514331H"
["rel"]=>
string(4) "self"
["method"]=>
string(3) "GET"
}
[1]=>
array(3) {
["href"]=>
string(94) "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-2VG2711092225420A"
["rel"]=>
string(12) "approval_url"
["method"]=>
string(8) "REDIRECT"
}
[2]=>
array(3) {
["href"]=>
string(89) "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-LR7Y6YA3UM97660A4514331H/execute"
["rel"]=>
string(7) "execute"
["method"]=>
string(4) "POST"
}
}
}
报错
所使用的环境是win10+apache2.4+php7.1,如果是使用php7.1以下版本,需要把构造数组$res=[];
改成$res=array();
。
有的人curl_exec
执行报错,并且echo curl_no($curl);
为35,是因为curl扩展使用的openssl版本过低,不支持TLS 1.2。
解决方式:如果是linux编译php时使用新版的openssl,如果是windows暂时只能用新版的php。