前言
项目上需要接触到不同种类的支付平台,有一些其他第三方平台的支付也是为了企业没资质或为了得到更好的费率原因,因此也总结一下个人在开发中的使用记录
目标
通过合并支付的接口,让用户在选择支付上能更简单快捷
前置条件
相关支付平台都有自己的支付申请条件,因此根据需求自行了解注册 环境 PHP
正式内容
支付的需求最主要在于3个接口的实现
interface PaymentClientInterface
{
/**
* 支付请求
* @param $order_out_no
* @param $total_fee
* @param array $param
* @return mixed
*/
public function pay($out_trade_no,$total_fee,$param=[]);
/**
* 查询支付结果
* @return mixed
*/
public function query($out_trade_no,$param=[]);
/**
* 支付回调,由于各支付平台的参数都不同,因此返回原始信息自己处理
* @param $all
* @return mixed
*/
public function handle($all);
}
wechat 微信支付
public function query($out_trade_no, $param = [])
{
$action = "/v3/pay/transactions/out-trade-no/{$out_trade_no}";
$resp = $this->request($action, [], 'GET');
return $resp;
}
private function sign($url, $params, $method)
{
$params['mchid'] = $this->apiConfig->ExtraConfig['mch_id'];
$serial_no = $this->apiConfig->ExtraConfig['serial_no'];
$mch_private_key = file_get_contents("/conf/wechat_private_key.pem");
ksort($params);
$timestamp = time();
$nonce = $this->getNonceStr(32);
$body = ($method == "POST" ? json_encode($params, JSON_UNESCAPED_UNICODE) : "");
$url_parts = parse_url($url);
$canonical_url = $url_parts['path'] . ($method == "POST" ? "" : "?" . http_build_query($params));
$message = $method . "\n" .
$canonical_url . "\n" .
$timestamp . "\n" .
$nonce . "\n" .
$body . "\n";
openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$params['mchid'], $nonce, $timestamp, $serial_no, $sign);
return ['query' => $params, 'token' => $token];
}
/**
* @param $action
* @param $params
* @return mixed
* @throws GuzzleHttp\Exception\GuzzleException
*/
private function request($action, $params, $method = "POST")
{
$url = $this->apiConfig->Host . $action;
$data = $this->sign($url, $params, $method);
$option = [
'verify' => false,
'headers' => [
'User-Agent' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)',
'Accept' => '*/*',
'Content-Type' => 'application/json',
'Authorization' => "WECHATPAY2-SHA256-RSA2048 {$data['token']}",
],
'http_errors' => false,
];
if ($method == "GET") {
$option['query'] = $data['query'];
} else {
$option['body'] = json_encode($data['query'], JSON_UNESCAPED_UNICODE);
}
try {
$request = $this->client->request($method,
$url,
$option
)
->getBody()
->getContents();
return json_decode($request, 1);
} catch (GuzzleHttp\Exception\GuzzleException $e) {
return ['error' => $e->getMessage()];
} catch (\Throwable $e) {
return ['error'=>$e->getMessage()] ;
}
}
/**
*
* 产生随机字符串,不长于32位
* @param int $length
* @return string 产生的随机字符串
*/
public function getNonceStr($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/**
* 支付请求
* @param $order_out_no
* @param $total_fee
* @param array $param
* @return mixed
*/
public function jsapi($out_trade_no, $total_fee, $param = [])
{
$action = "/v3/pay/transactions/jsapi";
$p = [
'appid' => $this->apiConfig->ExtraConfig['appid'],
'description' => '充值',
'out_trade_no' => $out_trade_no,
'amount' => [
'total' => $total_fee * 100,
'currency' => 'CNY',
],
'notify_url' => (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] ? $_SERVER['REQUEST_SCHEME'] : request()->server('REQUEST_SCHEME')) . "://" . MAINSITE . "/payment/wxpay_notify_v3.php",
// 'notify_url' => "http://" . MAINSITE . "/payment/wxpay_notify_v3.php",
'payer' => [
'openid' => $param['openid'],
],
];
$resp = $this->request($action, $p);
if (isset($resp['code'])) return $resp['message'];
$data = [
'appId' => $this->apiConfig->ExtraConfig['appid'],
'timeStamp' => strval(time()),
'nonceStr' => DomainUtil::generateDomainPassword(24, false),
'package' => "prepay_id={$resp['prepay_id']}",
];
$message="{$data['appId']}\n{$data['timeStamp']}\n{$data['nonceStr']}\n{$data['package']}\n";
openssl_sign($message,
$raw_sign,
file_get_contents(WWWBASE . "/conf/global/wechat_private_key.pem"),
'sha256WithRSAEncryption');
$data['paySign'] = base64_encode($raw_sign);
$data['openid']=$param['openid'];
$data['signType']='RSA';
return ['content'=>$data,'Type'=>'JSAPI'];
}
/**
* 支付请求
* @param $order_out_no
* @param $total_fee
* @param array $param
* @return mixed
*/
public function pay($out_trade_no, $total_fee, $param = [])
{
$action = "/v3/pay/transactions/native";
$p = [
'appid' => $this->apiConfig->ExtraConfig['appid'],
'description' => '充值',
'out_trade_no' => $out_trade_no,
'amount' => [
'total' => intval(strval(round($total_fee,2) * 100)),
'currency' => 'CNY',
],
'notify_url' => "your website url",
];
$resp = $this->request($action, $p);
if (isset($resp['error'])&&$resp['error']) return $resp['error'];
if(isset($resp['message'])&&$resp['message']) return $resp['message'];
return [
'content' => $resp['code_url'],
'QrcodeUrl' => $resp['code_url'],
'Type' => 'qrcode_url'];
}
/**
* 支付回调,由于各支付平台的参数都不同,因此返回原始信息自己处理
* @param $all
* @return mixed
*/
public function handle($all)
{
// TODO: Implement handle() method.
}
public function decode($associatedData, $nonceStr, $ciphertext)
{
$ciphertext = base64_decode($ciphertext);
if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
return false;
}
if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') &&
sodium_crypto_aead_aes256gcm_is_available()) {
return sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->apiConfig->ExtraConfig['key']);
}
// ext-libsodium (need install libsodium-php 1.x via pecl)
if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') &&
\Sodium\crypto_aead_aes256gcm_is_available()) {
return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->apiConfig->ExtraConfig['key']);
}
// openssl (PHP >= 7.1 support AEAD)
if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
$ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
$authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);
return \openssl_decrypt($ctext, 'aes-256-gcm', $this->apiConfig->ExtraConfig['key'], \OPENSSL_RAW_DATA, $nonceStr,
$authTag, $associatedData);
}
return false;
}
alipay 支付宝支付
public function pay($out_trade_no, $total_fee, $param = [])
{
$p = [
'service' => $this->is_mobile_request()?'alipay.wap.create.direct.pay.by.user':"create_direct_pay_by_user",
'partner' => $this->apiConfig->ExtraConfig['partner'],
'return_url' => (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] ? $_SERVER['REQUEST_SCHEME'] : request()->server('REQUEST_SCHEME')) . "://" . MAINSITE . "/payment/alipay_return_url.php",
'notify_url' => (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] ? $_SERVER['REQUEST_SCHEME'] : request()->server('REQUEST_SCHEME')) . "://" . MAINSITE . "/payment/alipay_notify_url.php",
'_input_charset'=>'utf-8',
'subject'=>'网上汇入',
'body'=>'网上汇入',
'out_trade_no'=>$out_trade_no,
'total_fee'=>$total_fee,
'payment_type'=>1,
'show_url'=>(isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] ? $_SERVER['REQUEST_SCHEME'] : request()->server('REQUEST_SCHEME'))."://".MAINSITE,
'seller_email'=>$this->apiConfig->ExtraConfig['seller_email'],
];
$action="https://mapi.alipay.com/gateway.do";
$resp=$this->sign($p);
return ['content' => $action."?".http_build_query($resp), 'Type' => 'url'];
}
public function sign($p){
$prestr="";
ksort($p);
foreach ($p as $key=>$value){
$prestr.="{$key}={$value}&";
}
$prestr=trim($prestr,"&");
$p['sign_type']='MD5';
$p['sign']=md5($prestr.$this->apiConfig->ExtraConfig['security_code']);
return $p;
}
function is_mobile_request()
{
$_SERVER['ALL_HTTP'] = isset($_SERVER['ALL_HTTP']) ? $_SERVER['ALL_HTTP'] : '';
$mobile_browser = '0';
if(isset($_SERVER['HTTP_USER_AGENT'])&&preg_match('/(up.browser|up.link|mmp|symbian|smartphone|midp|wap|phone|iphone|ipad|ipod|android|xoom)/i', strtolower($_SERVER['HTTP_USER_AGENT'])))
$mobile_browser++;
if((isset($_SERVER['HTTP_ACCEPT'])) and (strpos(strtolower($_SERVER['HTTP_ACCEPT']),'application/vnd.wap.xhtml+xml') !== false))
$mobile_browser++;
if(isset($_SERVER['HTTP_X_WAP_PROFILE']))
$mobile_browser++;
if(isset($_SERVER['HTTP_PROFILE']))
$mobile_browser++;
if(isset($_SERVER['HTTP_USER_AGENT'])) {
$mobile_ua = strtolower(substr($_SERVER['HTTP_USER_AGENT'], 0, 4));
$mobile_agents = array(
'w3c ', 'acs-', 'alav', 'alca', 'amoi', 'audi', 'avan', 'benq', 'bird', 'blac',
'blaz', 'brew', 'cell', 'cldc', 'cmd-', 'dang', 'doco', 'eric', 'hipt', 'inno',
'ipaq', 'java', 'jigs', 'kddi', 'keji', 'leno', 'lg-c', 'lg-d', 'lg-g', 'lge-',
'maui', 'maxo', 'midp', 'mits', 'mmef', 'mobi', 'mot-', 'moto', 'mwbp', 'nec-',
'newt', 'noki', 'oper', 'palm', 'pana', 'pant', 'phil', 'play', 'port', 'prox',
'qwap', 'sage', 'sams', 'sany', 'sch-', 'sec-', 'send', 'seri', 'sgh-', 'shar',
'sie-', 'siem', 'smal', 'smar', 'sony', 'sph-', 'symb', 't-mo', 'teli', 'tim-',
'tosh', 'tsm-', 'upg1', 'upsi', 'vk-v', 'voda', 'wap-', 'wapa', 'wapi', 'wapp',
'wapr', 'webc', 'winw', 'winw', 'xda', 'xda-'
);
if(in_array($mobile_ua, $mobile_agents))
$mobile_browser++;
}
if(strpos(strtolower($_SERVER['ALL_HTTP']), 'operamini') !== false)
$mobile_browser++;
// Pre-final check to reset everything if the user is on Windows
if( isset($_SERVER['HTTP_USER_AGENT'])&&strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'windows') !== false)
$mobile_browser=0;
// But WP7 is also Windows, with a slightly different characteristic
if(isset($_SERVER['HTTP_USER_AGENT'])&&strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'windows phone') !== false)
$mobile_browser++;
if($mobile_browser>0)
return true;
else
return false;
}
globalalipay 海外支付宝支付
/**
* 支付请求
* @param $order_out_no
* @param $total_fee
* @param array $param
* @return mixed
*/
public function pay($out_trade_no, $total_fee, $param = [])
{
$p = [
// 'product_code' => 'NEW_OVERSEAS_SELLER',
'service' => 'create_forex_trade',
'partner' => $this->apiConfig->ExtraConfig['partner'],
'return_url' => $this->apiConfig->ExtraConfig['return_url'],
'notify_url' => $this->apiConfig->ExtraConfig['notify_url'],
'_input_charset'=>'utf-8',
'subject'=>'網上匯入',
'body'=>'網上匯入',
'out_trade_no'=>$out_trade_no,
'total_fee'=>$total_fee,
'payment_type'=>1,
'show_url'=>(isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] ? $_SERVER['REQUEST_SCHEME'] : request()->server('REQUEST_SCHEME'))."://".MAINSITE,
'seller_email'=>$this->apiConfig->ExtraConfig['seller_email'],
'currency'=>isset($param['currency'])&&$param['currency']?$param['currency']:"HKD",
];
$action="https://mapi.alipay.com/gateway.do";
$resp=$this->sign($p);
return ['content' => $action."?".http_build_query($resp), 'Type' => 'url'];
}
public function sign($p){
$prestr="";
ksort($p);
foreach ($p as $key=>$value){
$prestr.="{$key}={$value}&";
}
$prestr=trim($prestr,"&");
$p['sign_type']='MD5';
$p['sign']=md5($prestr.$this->apiConfig->ExtraConfig['security_code']);
return $p;
}
Tonglian 通联支付
/**
* 网关支付
* @param $out_trade_no
* @param $total_fee
* @param array $param
* @return array
*/
public function gateway($out_trade_no, $total_fee, $param = [])
{
$p = [
'cusid' => $this->apiConfig->ExtraConfig['cusid'],
'appid' => $this->apiConfig->Username,
'charset' => 'UTF-8',
// 'returl' => '',
'returl' =>( isset($_SERVER['REQUEST_SCHEME'])&&$_SERVER['REQUEST_SCHEME']?$_SERVER['REQUEST_SCHEME']: request()->server('REQUEST_SCHEME')) . "://" . MAINSITE . "/payment/allinpay_notify_url.php",
'notifyurl' =>( isset($_SERVER['REQUEST_SCHEME'])&&$_SERVER['REQUEST_SCHEME']?$_SERVER['REQUEST_SCHEME']: request()->server('REQUEST_SCHEME')) . "://" . MAINSITE . "/payment/allinpay_notify_url.php",
'trxamt' => $total_fee * 100,
'orderid' => $out_trade_no,
'randomstr' => time(),
'gateid' => isset($param['bankid'])&&$param['bankid']?$param['bankid']:"",
'paytype' =>isset($param['bankid'])&&$param['bankid']?"B2C": 'B2C,B2B',
];
$p['sign'] = $this->sign($p);
$url = "{$this->apiConfig->Host}/gateway/pay";
$str = "<form id='myform1' method='post' action='{$url}' target='_blank' >";
foreach ($p as $key => $value) {
$str .= "<input type='hidden' name='{$key}' value='{$value}'/>";
}
$str .= '</form>';
return ['content' => $str, 'Type' => 'form'];
}
/**
* 支付请求
* 文档url: https://aipboss.allinpay.com/know/devhelp/main.php?pid=15#mid=88
* @param $order_out_no
* @param $total_fee
* @param array $param
* @return mixed
*/
public function pay($out_trade_no, $total_fee, $param = [])
{
$action = "/unitorder/pay";
$paytype = "W01";
if (isset($param['paytype'])) {
switch ($param['paytype']) {
case "wechat":
$paytype = "W01";
break;
case "alipay":
$paytype = "A01";
break;
case "qq":
$paytype = "Q01";
break;
case "union":
$paytype = "U01";
break;
}
}
$p = [
'trxamt' => $total_fee * 100,
'reqsn' => $out_trade_no,
'paytype' => $paytype,
'randomstr' => time(),
'body' => '充值',
'remark' => $out_trade_no,
'notify_url' =>( isset($_SERVER['REQUEST_SCHEME'])&&$_SERVER['REQUEST_SCHEME']?$_SERVER['REQUEST_SCHEME']: request()->server('REQUEST_SCHEME')) . "://" . MAINSITE . "/payment/allinpay_notify_url.php",
];
$resp = $this->request($action, $p);
if (isset($resp['errmsg'])) {
return $resp['errmsg'];
}
$array = [
'Type' => 'qrcode_url',
'QrcodeUrl' => $resp['payinfo'],
'content' => $resp['payinfo'],
];
return $array;
}
public function gateway_query($out_trade_no, $param = [])
{
$action="/gateway/query";
$p=[
'orderid'=>$out_trade_no,
];
$resp=$this->request($action,$p);
return isset($resp['errmsg'])?$resp['errmsg']: $resp;
}
/**
* 查询支付结果
* @return mixed
*/
public function query($out_trade_no, $param = [])
{
$action="/unitorder/query";
$p=[
'reqsn'=>$out_trade_no,
];
$resp=$this->request($action,$p);
return isset($resp['errmsg'])?$resp['errmsg']: $resp;
}
public function sign($param)
{
$param['key'] = $this->apiConfig->Password;
ksort($param);
$buff = "";
foreach ($param as $k => $v) {
if ($v != "" && !is_array($v)) {
$buff .= $k . "=" . $v . "&";
}
}
return md5(trim($buff, "&"));
}
public function request($action, $param)
{
$param['cusid'] = $this->apiConfig->ExtraConfig['cusid'];
$param['appid'] = $this->apiConfig->Username;
$param['version'] = $this->apiConfig->ExtraConfig['version'];
$sign = $this->sign($param);
$param["sign"] = $sign;
$content = $this->client->request("POST", $this->apiConfig->Host . $action, [
'form_params' => $param
])->getBody()->getContents();
return json_decode($content, 1);
}
/**
* 支付回调,由于各支付平台的参数都不同,因此返回原始信息自己处理
* @param $all
* @return mixed
*/
public function handle($all)
{
$sign = $all['sign'];
unset($all['sign']);
$mySign = $this->sign($all);
if (strtolower($sign) == strtolower($mySign)) {
return [
'out_trade_no' => $all['cusorderid'],
'amount' => $all['trxamt'] / 100,
'attach' => isset($all['trxreserved']) ? $all['trxreserved'] : "",
'created_at' => date("Y-m-d H:i:s", strtotime($all['trxdate'])),
'payed_at' => date("Y-m-d H:i:s", strtotime($all['paytime'])),
];
}
return false;
}
public function refund($out_trade_no, $total_fee, $param = [])
{
$action = "/unitorder/refund";
$p = [
'trxamt' => $total_fee * 100,
'oldreqsn' => $out_trade_no,
'reqsn'=>"{$out_trade_no}_refund",
'remark'=>'退款'
];
return $this->request($action, $p);
}
union 银联支付
public function pay($out_trade_no, $total_fee, $param = [])
{
$http_type = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://';
$returnUrl = $http_type . '/payment/unionpay_result.php?';//返回URL
$notifyUrl = $http_type . '/payment/unionpay_result.php?';//通知URL
$p = [
'version' => '1.0.0',
'charset' => 'UTF-8',
'merId' => $this->apiConfig->ExtraConfig['merId'],
'merAbbr' => 'merAbbr',
'acqCode' => '',
'merCode' => '',
'transType' => '01',
'origQid' => '',
'commodityUrl' => '',
'commodityName' => '',
'commodityUnitPrice' => $total_fee * 100,
'commodityQuantity' => 1,
'commodityDiscount' => 0,
'transferFee' => 0,
'orderNumber' => $out_trade_no,
'orderAmount' => $total_fee * 100,
'orderCurrency' => '156',
'orderTime' => date('YmdHis'),
'customerIp' => '',
'customerName' => '',
'defaultPayType' => '',
'defaultBankNumber' => '',
'transTimeout' => '300000',
'merReserved' => '',
'frontEndUrl' => $returnUrl,
'backEndUrl' => $notifyUrl,
];
$url=$this->apiConfig->Host."/api/Pay.action";
$p=$this->sign($p);
$str = "<form id='myform1' method='post' action='{$url}' target='_blank' >";
foreach ($p as $key => $value) {
$str .= "<input type='hidden' name='{$key}' id='{$key}' value='{$value}'/>";
}
$str .= '</form>';
return ['content' => $str, 'Type' => 'form'];
}
public function sign($p){
$prestr="";
ksort($p);
reset($p);
foreach ($p as $key=>$value){
$prestr.="{$key}={$value}&";
}
$p['signMethod']='MD5';
$p['signature']=md5($prestr.md5($this->apiConfig->ExtraConfig['sucurity_key']));
return $p;
}