对于对接第三方支付,只想说文档很重要,漏了一步,后期排错真的很费时间。好好读文档,结合自己的项目优化官方给的SDK例子。也可以拿官方的demo在自己服务器先能成功执行完整个流程后,再结合自己项目从sdk抽出自己想要的代码
在开发过程中,对接第三方为保证数据的可靠以及安全,都会对数据进行加签以及验签。
加签会涉及到你在第三方平台设置的公钥(相当你的私钥),当你把数据传给第三方平台的时候,第三方平台会验签,通过后会同步或者异步返回数据给你
验签会涉及到第三方平台公钥(相当于第三方平台加签用的私钥),当你接受到第三方平台传回的数据后进行验签来保证数据可靠,验签成功后响应对应的数据给第三方平台
signtype = 'RSA2';
$this->charset = 'utf8';
$this->method = 'alipay.trade.wap.pay';
}
/**
* 支付宝官方-H5支付(生成支付链接或者表单)
*这里是支付链接
*/
public function doPayInit($payParams)
{
$orderId = $payParams['orderId'];
$totalAmount = $payParams['money'];
//请求参数
$requestConfigs = array(
'out_trade_no' => $orderId,
'product_code' => 'QUICK_WAP_WAY',
'total_amount' => $totalAmount, //单位 元
'subject' => 'test', //订单标题
);
$commonConfigs = array(
//公共参数
'app_id' => $this->appId,
'method' => $this->method, //接口名称
'format' => 'JSON',
'charset' => $this->charset,
'sign_type' => $this->signtype,
'timestamp' => date('Y-m-d H:i:s'),
'version' => '1.0',
'notify_url' => '你的异步回调地址',
'biz_content' => json_encode($requestConfigs, JSON_UNESCAPED_UNICODE),
);
$commonConfigs["sign"] = $this->generateSign($commonConfigs);
//value做urlencode
$preString = $this->getSignContentUrlencode($commonConfigs);
//拼接GET请求串
$requestUrl = $this->gatewayUrl."?".$preString;
$res = [];
$res['pay_info'] = $requestUrl;
return $res;
}
/**
* 支付宝官方-H5支付回调地址
*/
public function doPayCb($request)
{
$retResponse = $_REQUEST;
if (empty($retResponse)) {
throw new Exception('fail');
}
// 其验签步骤为:
// 第一步: 在通知返回参数列表中,除去sign、sign_type两个参数外,凡是通知返回回来的参数皆是待验签的参数。
// 第二步: 将剩下参数进行url_decode, 然后进行字典排序,组成字符串,得到待签名字符串:
// 第三步: 将签名参数(sign)使用base64解码为字节码串。
// 第四步: 使用RSA的验签方法,通过签名字符串、签名参数(经过base64解码)及支付宝公钥验证签名。
// 第五步:在步骤四验证签名正确后,必须再严格按照如下描述校验通知数据的正确性。
$result = $this->rsaCheckV1($retResponse);
if (!in_array( $retResponse['trade_status'] , [ 'TRADE_FINISHED', 'TRADE_SUCCESS' ] )) {
throw new Exception('fail');
}
//验签成功处理你自己的逻辑,更新订单状态等
// 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号;
// 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额);
// 3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email);
// 4、验证app_id是否为该商户本身。
return 'success';//最后一定返回success,否则支付宝会继续回调
}
//此方法对value做urlencode
public function getSignContentUrlencode($params) {
ksort($params);
$stringToBeSigned = "";
$i = 0;
foreach ($params as $k => $v) {
if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
// 转换成目标字符集
$v = $this->characet($v, $this->charset);
if ($i == 0) {
$stringToBeSigned .= "$k" . "=" . urlencode($v);
} else {
$stringToBeSigned .= "&" . "$k" . "=" . urlencode($v);
}
$i++;
}
}
unset ($k, $v);
return $stringToBeSigned;
}
/** rsaCheckV1 & rsaCheckV2
* 验证签名
* 在使用本方法前,必须初始化AopClient且传入公钥参数。
* 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
* */
public function rsaCheckV1($params)
{
$sign = $params['sign'];
$params['sign_type'] = null;
$params['sign'] = null;
return $this->verify($this->getSignContent($params), $sign);
}
public function verify($data, $sign)
{
$pubKey = $this->alipayPublicKey;
$res = "-----BEGIN PUBLIC KEY-----\n" .
wordwrap($pubKey, 64, "\n", true) .
"\n-----END PUBLIC KEY-----";
if(!$res){
throw new Exception(Exception::ETYPE_INVALID_PAY_CALLBACK_ERROR,"支付宝RSA公钥错误。请检查公钥文件格式是否正确");
}
//调用openssl内置方法验签,返回bool值
$result = FALSE;
if ("RSA2" == $this->signtype) {
$result = (openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256) === 1);
} else {
$result = (openssl_verify($data, base64_decode($sign), $res) === 1);
}
return $result;
}
public function getSignContent($params)
{
ksort($params);
$stringToBeSigned = "";
$i = 0;
foreach ($params as $k => $v) {
if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
// 转换成目标字符集
$v = $this->characet($v, $this->charset);
if ($i == 0) {
$stringToBeSigned .= "$k" . "=" . "$v";
} else {
$stringToBeSigned .= "&" . "$k" . "=" . "$v";
}
$i++;
}
}
unset($k, $v);
return $stringToBeSigned;
}
/**
* 校验$value是否非空
* if not set ,return true;
* if is null , return true;
* */
protected function checkEmpty($value)
{
if (!isset($value))
return true;
if ($value === null)
return true;
if (trim($value) === "")
return true;
return false;
}
/**
* 转换字符集编码
* @param $data
* @param $targetCharset
* @return string
*/
public function characet($data, $targetCharset)
{
if (!empty($data)) {
$fileType = $this->charset;
if (strcasecmp($fileType, $targetCharset) != 0) {
$data = mb_convert_encoding($data, $targetCharset, $fileType);
}
}
return $data;
}
public function generateSign($params)
{
return $this->sign($this->getSignContent($params));
}
//加签
protected function sign($data)
{
$priKey = $this->rsaPrivateKey;
$res = "-----BEGIN RSA PRIVATE KEY-----\n" .
wordwrap($priKey, 64, "\n", true) .
"\n-----END RSA PRIVATE KEY-----";
if(!$res){
throw new Exception(Exception::ETYPE_INVALID_PAY_CALLBACK_ERROR,"支付宝RSA私钥错误。请检查私钥文件格式是否正确");
}
if ("RSA2" == $this->signtype) {
openssl_sign($data, $sign, $res, version_compare(PHP_VERSION, '5.4.0', '<') ? SHA256 : OPENSSL_ALGO_SHA256); //OPENSSL_ALGO_SHA256是php5.4.8以上版本才支持
} else {
openssl_sign($data, $sign, $res);
}
$sign = base64_encode($sign);
return $sign;
}
}