支付逻辑简介
跟微信和支付宝支付不一样,微信和支付宝都是先走统一下单接口,然后返回创建的下单会话id等然后传给前端,贝宝支付是我们先通过API发起个支付请求,然后贝宝给我们个支付链接,前端打开我们的支付链接:例如
https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-7K8740615W719670D
,然后该链接会有我们支付订单的部分信息,然后利用沙盒的账号支付确认要支付后,会返回到下单时设置的回调支付那里,如下图:
const accept_url = 'http://fumei.com:8082/service.php?action=paypal_callback&com_id=1210';//返回地址
$redirectUrls = new RedirectUrls();
$redirectUrls->setReturnUrl(self::accept_url.'&orderId='.$order->orderId.'&success=true')
->setCancelUrl(self::accept_url.'&orderId='.$order->orderId.'&success=false');
这里回调会带着订单号和发起是否成功或者失败,如果成功,其实到这里我们并没有完成支付,只有在回调地址里才能完成
payment
支付,不像微信和支付宝,前端调起真实的支付,支付完毕后就要跳转前端准备的支付成功或失败的页面。
PayPal账号设置
- 创建账号:
创建沙盒账号https://www.paypal.com/c2/webapps/mpp/account-selection
- 登录沙盒后台
https://developer.paypal.com/developer/applications/
- 创建项目APP 查看 APPID和Secret
- 设置沙盒支付测试账号,充值,创建
- 设置回调地址
- 代码实现
- SDK下载
- 引入composer 管理包或者直接下载
- 引入文件
use PayPal\Api\Payer;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\Details;
use PayPal\Api\Amount;
use PayPal\Api\Transaction;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Payment;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Exception\PayPalConnectionException;
use PayPal\Rest\ApiContext;
use PayPal\Api\PaymentExecution;
- 初始化
protected $PayPal;
public function __construct()
{
$this->PayPal = new ApiContext(
new OAuthTokenCredential(
self::clientId,
self::clientSecret
)
);
$this->PayPal->setConfig(
array(
'mode' => 'sandbox',//线上的则配置为live
'log.LogEnabled' => true,
'log.FileName' => '../PayPal.log',
'log.LogLevel' => 'DEBUG', // PLEASE USE `INFO` LEVEL FOR LOGGING IN LIVE ENVIRONMENTS
'cache.enabled' => true
)
);
}
- 创建支付链接(即下单接口)
/**
* @param
* $product 商品
* $price 价钱
* $shipping 运费
* $description 描述内容
*/
public function pay()
{
global $db,$request,$comId;
$fenbiao = getFenbiao($comId,20);
$userId = (int)$request['user_id'];
$oid = $request['order_id'];
$order = $db->get_row("select * from order$fenbiao where id = $oid");
$product_json = json_decode($order->product_json);
$subject = '';
$daizhifu = $order->price;
$item = [];
foreach ($product_json as $pdt) {
$product_item = array(
'title' => $pdt->title,
'sn' => $pdt->sn,
'num' => (int)$pdt->num,
'price' => (int) $pdt->price_sale
);
$subject.=','.$pdt->title.'*'.$pdt->num;
}
$body = substr($subject,1);
$subject = sys_substr($body,30,true);
$subject = str_replace('_','',$subject);
$num = (int)$order->pdtNums;
$price = (int)$order->price;
try {
$payer = new Payer();
$payer->setPaymentMethod('paypal');
$item = new Item();
$item->setName($product_item['title']) // 子订单的名称
->setDescription($product_item['sn']) // 子订单描述
->setCurrency(self::Currency) // 币种
->setQuantity($product_item['num']) // 数量
->setPrice($product_item['price']); // 价格
$itemList = new ItemList();
$itemList->setItems([$item]); // 设置子订单列表
// 这里是设置运费等
$details = new Details();
$details->setShipping(0)
->setSubtotal($price);
// 设置总计费用
$amount = new Amount();
$amount->setCurrency(self::Currency)
->setTotal($price)
->setDetails($details);
// 创建交易
$transaction = new Transaction();
$transaction->setAmount($amount)
->setItemList($itemList)
->setDescription($subject)
->setInvoiceNumber($order->orderId);
// 这里设置支付成功和失败后的跳转链接
$redirectUrls = new RedirectUrls();
$redirectUrls->setReturnUrl(self::accept_url.'&orderId='.$order->orderId.'&success=true')
->setCancelUrl(self::accept_url.'&orderId='.$order->orderId.'&success=false');
$payment = new Payment();
$payment->setIntent('sale')
->setPayer($payer)
->setRedirectUrls($redirectUrls)
->setTransactions([$transaction]);
$payment->create($this->PayPal);
// 得到支付链接
$link = $payment->getApprovalLink();
$return['code'] = 1;
$return['message'] = '';
$return['data'] = array();
$return['data']['link'] = $link;
return json_encode($return,JSON_UNESCAPED_UNICODE);
} catch (HttpException $e) {
$data = (['msg' => $e->getMessage(), 'code' => $e->getStatusCode(), 'data' => ['order' => ['no' => '1112323231']]]);
return null;
}
}
- 前端支付密码输入完毕后,发起支付
/**
* 回调
*/
public function callback()
{
global $db,$request,$comId;
$fenbiao = getFenbiao($comId,20);
$success = trim($_GET['success']);
if ($success == 'false' && !isset($_GET['paymentId']) && !isset($_GET['PayerID'])) {
echo '取消付款';die;
}
$paymentId = trim($_GET['paymentId']);
$PayerID = trim($_GET['PayerID']);
$orderId = trim($_GET['orderId']);
if (!isset($success, $paymentId, $PayerID)) {
echo '支付失败';die;
}
if ((bool)$_GET['success'] === 'false') {
echo '支付失败,支付ID【' . $paymentId . '】,支付人ID【' . $PayerID . '】';die;
}
$payment = Payment::get($paymentId, $this->PayPal);
$execute = new PaymentExecution();
$execute->setPayerId($PayerID);
try {
$payment->execute($execute, $this->PayPal);
//todo 记录
$db->query("update order10 set paypal_no = '$paymentId', paypal_payerId ='$PayerID' where orderId = '$orderId'");
//todo 直接跳转前端页面
$url = '';
header("localtion:$url");
} catch (Exception $e) {
echo ',支付失败,支付ID【' . $paymentId . '】,支付人ID【' . $PayerID . '】';die;
}
echo '支付成功,支付ID【' . $paymentId . '】,支付人ID【' . $PayerID . '】';die;
}
- 支付后台回调,更改订单状态等
public function notify(){
//获取回调结果
$json_data = $this->get_JsonData();
if(!empty($json_data)){
Log::debug("paypal notify info:\r\n".json_encode($json_data));
}else{
Log::debug("paypal notify fail:参加为空");
}
//自己打印$json_data的值看有那些是你业务上用到的
//比如我用到
$data['invoice'] = $json_data['resource']['invoice_number'];
$data['txn_id'] = $json_data['resource']['id'];
$data['total'] = $json_data['resource']['amount']['total'];
$data['status'] = isset($json_data['status'])?$json_data['status']:'';
$data['state'] = $json_data['resource']['state'];
try {
//处理相关业务
} catch (\Exception $e) {
//记录错误日志
Log::error("paypal notify fail:".$e->getMessage());
return "fail";
}
return "success";
}
public function get_JsonData(){
$json = file_get_contents('php://input');
if ($json) {
$json = str_replace("'", '', $json);
$json = json_decode($json,true);
}
return $json;
}
- 退款
/**
*
* 根据异步回调的txn_id进行退款操作
* @param Order $order
* @return mixed
*/
public function payPalRefundForTxnId()
{
try {
$txn_id = ‘这个值在回调的时候获取到’;
$apiContext = new ApiContext(
new OAuthTokenCredential(
‘client_id’,
‘client_secret’
)); // 这里是我们第一步拿到的数据
// $apiContext->setConfig(['mode' => 'live']); // live下设置
$amt = new Amount();
$amt->setCurrency('USD')
->setTotal(100); // 退款的费用
$refund = new Refund();
$refund->setAmount($amt);
$sale = new Sale();
$sale->setId($txn_id);
$refundedSale = $sale->refund($refund, $apiContext);
} catch (\Exception $e) {
// PayPal无效退款
return json_decode(json_encode(['message' => $e->getMessage(), 'code' => $e->getCode(), 'state' => $e->getMessage()])); // to object
}
// 退款完成
return $refundedSale;
}
- 代码总:
namespace Zhishang;
use PayPal\Api\Payer;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\Details;
use PayPal\Api\Amount;
use PayPal\Api\Transaction;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Payment;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Exception\PayPalConnectionException;
use PayPal\Rest\ApiContext;
use PayPal\Api\PaymentExecution;
class Paypal{
const clientId = 'AV-SNw2j-1wNqAb1ygg0I4iKT8E8zHT1QCh8eLEbeFZQN2slHhq4dX5pKn4mfz7CHGDhCaVX1N';//ID
const clientSecret = 'EHjumNJEwf5lkurMyNqo1DJ1v64jsZ9mpFYlKIkVQBh6Kksygxx_u77F8G0rSQHW9Awezdqd4RCd';//秘钥
const accept_url = 'http://fumei.com:8082/service.php?action=paypal_callback&com_id=1210';//返回地址
const Currency = 'USD';//币种
protected $PayPal;
public function __construct()
{
$this->PayPal = new ApiContext(
new OAuthTokenCredential(
self::clientId,
self::clientSecret
)
);
//如果是沙盒测试环境不设置,请注释掉
// $this->paypal->setConfig(
// array(
// 'mode' => 'sandbox',
// )
// );
// $this->PayPal->setConfig([
// 'mode' => 'sandbox',
// 'log.LogEnabled' => true,
// 'log.FileName' => 'logs/PayPal.log',
// 'log.LogLevel' => 'DEBUG', // PLEASE USE `INFO` LEVEL FOR LOGGING IN LIVE ENVIRONMENTS
// 'cache.enabled' => true,
// ]);
$this->PayPal->setConfig(
array(
'mode' => 'sandbox',
'log.LogEnabled' => true,
'log.FileName' => '../PayPal.log',
'log.LogLevel' => 'DEBUG', // PLEASE USE `INFO` LEVEL FOR LOGGING IN LIVE ENVIRONMENTS
'cache.enabled' => true
)
);
}
/**
* @param
* $product 商品
* $price 价钱
* $shipping 运费
* $description 描述内容
*/
public function pay()
{
global $db,$request,$comId;
$fenbiao = getFenbiao($comId,20);
$userId = (int)$request['user_id'];
$oid = $request['order_id'];
$order = $db->get_row("select * from order$fenbiao where id = $oid");
$product_json = json_decode($order->product_json);
$subject = '';
$daizhifu = $order->price;
$item = [];
foreach ($product_json as $pdt) {
$product_item = array(
'title' => $pdt->title,
'sn' => $pdt->sn,
'num' => (int)$pdt->num,
'price' => (int) $pdt->price_sale
);
$subject.=','.$pdt->title.'*'.$pdt->num;
}
$body = substr($subject,1);
$subject = sys_substr($body,30,true);
$subject = str_replace('_','',$subject);
$num = (int)$order->pdtNums;
$price = (int)$order->price;
try {
$payer = new Payer();
$payer->setPaymentMethod('paypal');
$item = new Item();
$item->setName($product_item['title']) // 子订单的名称
->setDescription($product_item['sn']) // 子订单描述
->setCurrency(self::Currency) // 币种
->setQuantity($product_item['num']) // 数量
->setPrice($product_item['price']); // 价格
$itemList = new ItemList();
$itemList->setItems([$item]); // 设置子订单列表
// 这里是设置运费等
$details = new Details();
$details->setShipping(0)
->setSubtotal($price);
// 设置总计费用
$amount = new Amount();
$amount->setCurrency(self::Currency)
->setTotal($price)
->setDetails($details);
// 创建交易
$transaction = new Transaction();
$transaction->setAmount($amount)
->setItemList($itemList)
->setDescription($subject)
->setInvoiceNumber($order->orderId);
// 这里设置支付成功和失败后的跳转链接
$redirectUrls = new RedirectUrls();
$redirectUrls->setReturnUrl(self::accept_url.'&orderId='.$order->orderId.'&success=true')
->setCancelUrl(self::accept_url.'&orderId='.$order->orderId.'&success=false');
$payment = new Payment();
$payment->setIntent('sale')
->setPayer($payer)
->setRedirectUrls($redirectUrls)
->setTransactions([$transaction]);
$payment->create($this->PayPal);
// 得到支付链接
$link = $payment->getApprovalLink();
$return['code'] = 1;
$return['message'] = '';
$return['data'] = array();
$return['data']['link'] = $link;
return json_encode($return,JSON_UNESCAPED_UNICODE);
} catch (HttpException $e) {
$data = (['msg' => $e->getMessage(), 'code' => $e->getStatusCode(), 'data' => ['order' => ['no' => '1112323231']]]);
return null;
}
}
/**
* 回调
*/
public function callback()
{
global $db,$request,$comId;
$fenbiao = getFenbiao($comId,20);
$success = trim($_GET['success']);
if ($success == 'false' && !isset($_GET['paymentId']) && !isset($_GET['PayerID'])) {
echo '取消付款';die;
}
$paymentId = trim($_GET['paymentId']);
$PayerID = trim($_GET['PayerID']);
$orderId = trim($_GET['orderId']);
if (!isset($success, $paymentId, $PayerID)) {
echo '支付失败';die;
}
if ((bool)$_GET['success'] === 'false') {
echo '支付失败,支付ID【' . $paymentId . '】,支付人ID【' . $PayerID . '】';die;
}
$payment = Payment::get($paymentId, $this->PayPal);
$execute = new PaymentExecution();
$execute->setPayerId($PayerID);
try {
$payment->execute($execute, $this->PayPal);
//todo 记录
$db->query("update order10 set paypal_no = '$paymentId', paypal_payerId ='$PayerID' where orderId = '$orderId'");
} catch (Exception $e) {
echo ',支付失败,支付ID【' . $paymentId . '】,支付人ID【' . $PayerID . '】';die;
}
echo '支付成功,支付ID【' . $paymentId . '】,支付人ID【' . $PayerID . '】';die;
}
public function notify(){
//获取回调结果
$json_data = $this->get_JsonData();
if(!empty($json_data)){
Log::debug("paypal notify info:\r\n".json_encode($json_data));
}else{
Log::debug("paypal notify fail:参加为空");
}
//自己打印$json_data的值看有那些是你业务上用到的
//比如我用到
$data['invoice'] = $json_data['resource']['invoice_number'];
$data['txn_id'] = $json_data['resource']['id'];
$data['total'] = $json_data['resource']['amount']['total'];
$data['status'] = isset($json_data['status'])?$json_data['status']:'';
$data['state'] = $json_data['resource']['state'];
try {
//处理相关业务
} catch (\Exception $e) {
//记录错误日志
Log::error("paypal notify fail:".$e->getMessage());
return "fail";
}
return "success";
}
public function get_JsonData(){
$json = file_get_contents('php://input');
if ($json) {
$json = str_replace("'", '', $json);
$json = json_decode($json,true);
}
return $json;
}
}
注意事项:
- 正式站:当你切换到正式环境去测试支付的时候
不要拿0.1美元去做测试
,因为你的支付完毕之后,他在商户管理那边不会显示,可能是因为手续费等不知名问题,请尝试1美元。