Files
pay/plugins/paypal/paypal_plugin.php
T
2025-11-28 10:08:12 +08:00

220 lines
7.0 KiB
PHP

<?php
class paypal_plugin
{
static public $info = [
'name' => 'paypal', //支付插件英文名称,需和目录名称一致,不能有重复
'showname' => 'PayPal', //支付插件显示名称
'author' => 'PayPal', //支付插件作者
'link' => 'https://www.paypal.com/', //支付插件作者链接
'types' => ['paypal'], //支付插件支持的支付方式,可选的有alipay,qqpay,wxpay,bank
'inputs' => [ //支付插件要求传入的参数以及参数显示名称,可选的有appid,appkey,appsecret,appurl,appmchid
'appid' => [
'name' => 'ClientId',
'type' => 'input',
'note' => '',
],
'appkey' => [
'name' => 'ClientSecret',
'type' => 'input',
'note' => '',
],
'appswitch' => [
'name' => '模式选择',
'type' => 'select',
'options' => [0=>'线上模式',1=>'沙盒模式'],
],
'currency_code' => [
'name' => '结算货币',
'type' => 'select',
'options' => [
'USD' => '美元 (USD)',
'AUD' => '澳元 (AUD)',
'BRL' => '巴西雷亚尔 (BRL)',
'CAD' => '加拿大元 (CAD)',
'CNY' => '人民币 (CNY)',
'CZK' => '克朗 (CZK)',
'DKK' => '丹麦克朗(DKK)',
'EUR' => '欧元 (EUR)',
'HKD' => '港币 (HKD)',
'HUF' => '匈牙利福林 (HUF)',
'INR' => '印度卢比 (INR)',
'ILS' => '以色列新谢克尔 (ILS)',
'JPY' => '日元 (JPY)',
'MYR' => '马来西亚林吉特 (MYR)',
'MXN' => '墨西哥比索 (MXN)',
'TWD' => '新台币 (TWD)',
'NZD' => '新西兰元 (NZD)',
'NOK' => '挪威克朗 (NOK)',
'PHP' => '菲律宾比索 (PHP)',
'PLN' => '波兰兹罗提 (PLN)',
'GBP' => '英镑 (GBP)',
'RUB' => '俄罗斯卢布 (RUB)',
'SGD' => '新加坡元 (SGD)',
'SEK' => '瑞典克朗 (SEK)',
'CHF' => '瑞士法郎 (CHF)',
'THB' => '泰铢 (THB)',
],
],
'currency_rate' => [
'name' => '货币汇率',
'type' => 'input',
'note' => '例如1元人民币兑换0.137美元(USD),则此处填0.137',
],
],
'select' => null,
'note' => '', //支付密钥填写说明
'bindwxmp' => false, //是否支持绑定微信公众号
'bindwxa' => false, //是否支持绑定微信小程序
];
static public function submit(){
global $siteurl, $channel, $order, $ordername, $sitename, $conf, $DB;
require_once(PAY_ROOT."inc/PayPalClient.php");
if(!$channel['currency_rate']) $channel['currency_rate'] = 1;
$money = round($order['realmoney'] * $channel['currency_rate'], 2);
$parameter = [
'intent' => 'CAPTURE',
'purchase_units' => [
[
'amount' => [
'currency_code' => $channel['currency_code'],
'value' => $money,
],
'description' => $order['name'],
'custom_id' => TRADE_NO,
'invoice_id' => TRADE_NO,
],
],
'application_context'=> [
'cancel_url' => $siteurl.'pay/cancel/'.TRADE_NO.'/',
'return_url' => $siteurl.'pay/return/'.TRADE_NO.'/',
],
];
try {
$approvalUrl = \lib\Payment::lockPayData(TRADE_NO, function() use($channel, $parameter) {
$client = new PayPalClient($channel['appid'], $channel['appkey'], $channel['appswitch']);
$result = $client->createOrder($parameter);
$approvalUrl = null;
foreach($result['links'] as $link){
if($link['rel'] == 'approve'){
$approvalUrl = $link['href'];
}
}
if(empty($approvalUrl)){
throw new Exception('获取支付链接失败');
}
return $approvalUrl;
});
return ['type'=>'jump','url'=>$approvalUrl];
}
catch (Exception $ex) {
sysmsg('PayPal下单失败:'.$ex->getMessage());
}
}
//同步回调
static public function return(){
global $channel, $order;
require_once(PAY_ROOT."inc/PayPalClient.php");
if (isset($_GET['token']) && isset($_GET['PayerID'])) {
$token = $_GET['token'];
try {
$client = new PayPalClient($channel['appid'], $channel['appkey'], $channel['appswitch']);
$result = $client->captureOrder($token);
} catch (Exception $ex) {
return ['type'=>'error','msg'=>'支付订单失败 '.$ex->getMessage()];
}
$captures = $result['purchase_units'][0]['payments']['captures'][0];
$amount = $captures['seller_receivable_breakdown']['gross_amount']['value'];
$trade_no = $captures['id'];
$out_trade_no = $captures['invoice_id'];
$buyer = $result['payer']['email_address'];
if($out_trade_no == TRADE_NO){
processReturn($order, $trade_no, $buyer);
}else{
return ['type'=>'error','msg'=>'订单信息校验失败'];
}
} else {
return ['type'=>'error','msg'=>'PayPal返回参数错误'];
}
}
static public function cancel(){
return ['type'=>'page','page'=>'error'];
}
static public function webhook(){
global $channel, $order;
$json = file_get_contents('php://input');
$arr = json_decode($json, true);
if(!$arr || empty($arr['event_type'])){
exit('事件类型为空');
}
if(!in_array($arr['event_type'], ['PAYMENT.CAPTURE.COMPLETED'])){
exit('其他事件('.$arr['event_type'].':'.$arr['summary'].')');
}
if(empty($channel['appsecret'])){
exit('未配置webhookid');
}
$crc32 = crc32($json);
if (empty($_SERVER['HTTP_PAYPAL_TRANSMISSION_ID']) || empty($_SERVER['HTTP_PAYPAL_TRANSMISSION_TIME']) || empty($crc32)) {
exit('签名数据为空');
}
$sign_string = $_SERVER['HTTP_PAYPAL_TRANSMISSION_ID'].'|'.$_SERVER['HTTP_PAYPAL_TRANSMISSION_TIME'].'|'.$channel['appsecret'].'|'.$crc32;
// 通过PAYPAL-CERT-URL头信息去拿公钥
$public_key = openssl_pkey_get_public(get_curl($_SERVER['HTTP_PAYPAL_CERT_URL']));
$details = openssl_pkey_get_details($public_key);
$verify = openssl_verify($sign_string, base64_decode($_SERVER['HTTP_PAYPAL_TRANSMISSION_SIG']), $details['key'], 'SHA256');
if($verify != 1)
{
exit('签名验证失败');
}
$resource = $arr['resource'];
$amount = $resource['amount']['value'];
$trade_no = $resource['id'];
$out_trade_no = $resource['invoice_id'];
}
//退款
static public function refund($order){
global $channel;
if(empty($order))exit();
require_once(PAY_ROOT."inc/PayPalClient.php");
if(!$channel['currency_rate']) $channel['currency_rate'] = 1;
$money = round($order['refundmoney'] * $channel['currency_rate'], 2);
$parameter = [
'amount' => [
'currency_code' => $channel['currency_code'],
'value' => $money,
],
];
try{
$client = new PayPalClient($channel['appid'], $channel['appkey'], $channel['appswitch']);
$res = $client->refundPayment($order['api_trade_no'], $parameter);
$result = ['code'=>0, 'trade_no'=>$res['id'], 'refund_fee'=>$res['amount']['value']];
}catch(Exception $e){
$result = ['code'=>-1, 'msg'=>$e->getMessage()];
}
return $result;
}
}