第一步 支付申请

登录支付宝,进入我的商家服务,签约App支付服务,签约完成可获得支付宝分配的一些id,seller_id,partnerid

第二步 设置公钥

官网文档

第三步 开放平台创建支付应用

创建支付应用获取appid,在应用详情设置接口加签方式,设置应用公钥,公钥通过支付宝提供的生成key 工具,生成公钥私钥,RSA2加签的选择生成2048位的密钥,公钥上传到支付宝,同时在本地会生成两个.txt的密钥文件(应用公钥2048.txt,应用私钥2048.txt),上传到网站,也就是下面示例代码的$_privateKeyPath,$_publicKeyPath的路径文件.

第四步 接入

官网文档

接入可以直接使用官网sdk,也可以自己接入

自己接入主要分几个步骤

1. 生成支付参数(服务端生成,客户端不安全)
2. 客户端调用sdk
3. 服务端异步接收支付通知

分享个生成支付参数,代码类

<?php 

class Aliapp.
{

		private $_appid = '';
		private $_method = 'alipay.trade.app.pay';
		private $_charset = 'utf-8';
		private $_seller_id = '';
		private $_privateKeyPath = '';//私钥路径
		private $_publicKeyPath = '';//公钥路径

		public function pay(array $params)
		{
				$pay_params = $this->createParams($params);
				return $pay_params;
		}

		protected function createParams($params)
		{
				$request = array();
				$request['app_id'] = $this->_appid;
				$request['method'] = $this->_method;
				$request['charset'] = $this->_charset;
				$request['sign_type'] = 'RSA2';
				$request['timestamp'] = date('Y-m-d H:i:s');
				$request['version'] = '1.0';
				$request['notify_url'] = 'http://example.com/notify.html';

				$request['biz_content'] = json_encode(array (
						'subject' => $params['subject'],
						'out_trade_no' => $params['out_trade_no'],
						'total_amount' => $params['total_fee'],
						'seller_id' => $this->_seller_id,
						'product_code' => 'QUICK_MSECURITY_PAY'
						));
				$sign_str = $this->fetchSignStr($request);

				$sign = $this->generateSign($sign_str);

				return $this->urlencodeRequestStr($request,$sign);
		}


		/**
		 * 对所有参数进行url编码
		 * @param  [type] $request [description]
		 * @param  [type] $sign    [description]
		 * @return [type]          [description]
		 */
		protected function urlencodeRequestStr($request,$sign)
		{
				ksort($request);
				$result = '';
				foreach ($request as $k=>$v) {
						if ($v === '') {
								continue;
						}
						$result .= $k.'='.urlencode($v).'&';
				}
				$result = rtrim($result,'&');
				return $result.'&sign='.urlencode($sign);
		}


		/**
		 * 拼接签名字符串
		 * @param  [type] $request [description]
		 * @return [type]          [description]
		 */
		protected function fetchSignStr($request)
		{
				ksort($request);
				$str = '';
				foreach ($request as $k=>$v) {
						if ($v === '') {
								continue;
						}
						$v = mb_convert_encoding($v,$this->_charset);//转换编码
						$str .= $k.'='.$v.'&';
				}

				return rtrim($str,'&');
		}


		/**
		 * 生成签名函数
		 * @param  string $sign_str  签名拼接好的字符串
		 * @param  string $sign_type 签名方式
		 * @return [type]            [description]
		 */
		protected function generateSign($sign_str,$sign_type = 'RSA2')
		{
				$priKey = file_get_contents($this->_privateKeyPath);
				$res = openssl_get_privatekey($priKey);
				if ($sign_type == 'RSA2') {
						openssl_sign($sign_str, $sign, $res, OPENSSL_ALGO_SHA256);
				} else {
						openssl_sign($data, $sign, $res);
				}

				$sign = base64_encode($sign);
				return $sign;
		}


		/**
		 * 验签函数
		 * @param  string $sign_str  签名拼接好的字符串
		 * @param  string $sign      支付宝返回的签名
		 * @param  string $sign_type 签名类型
		 * @return [type]            [description]
		 */
		public function verifySign($params,$sign_type = 'RSA2')
		{
				$sign_str = $this->getVerifySignStr($params);
				$sign = $params['sign'];
				$alipay_public_key = file_get_contents($this->_publicKeyPath);
				$alipay_public_key=str_replace("-----BEGIN PUBLIC KEY-----","",$alipay_public_key);
				$alipay_public_key=str_replace("-----END PUBLIC KEY-----","",$alipay_public_key);
				$alipay_public_key=str_replace("\n","",$alipay_public_key);

				$alipay_public_key = "-----BEGIN PUBLIC KEY-----\n" .
								wordwrap($alipay_public_key, 64, "\n", true) .
								"\n-----END PUBLIC KEY-----";
				$res = openssl_get_publickey($alipay_public_key);
				if ($sign_type == "RSA2") {
						$result = (bool)openssl_verify($sign_str, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
				} else {
						$result = (bool)openssl_verify($sign_str, base64_decode($sign), $res);
				}

				openssl_free_key($res);//释放资源

				return $result;
		}


		/**
		 * 获取异步通知验签字符串
		 * @param  [type] $params [description]
		 * @return [type]         [description]
		 */
		protected function getVerifySignStr($params)
		{
				$response = array();
				foreach ($params as $k=>$v) {
						if ($k == 'sign_type' || $k == 'sign') {
								continue;
						}

						$response[$k] = urldecode(html_entity_decode($v));
				}

				ksort($response);
				$sign_str = '';
				foreach ($response as $key=>$val) {
						$sign_str .= $key.'='.$val.'&';
				}

				return rtrim($sign_str,'&');
		}


}

收到支付宝异步通知,通过验签函数,以及自己商城业务逻辑处理后续流程

注意

在验签函数,生成签名字符串函数(getVerifySignStr())中有一句

$response[$k] = urldecode(html_entity_decode($v));

不懂是支付宝没有说清楚,还是个人环境问题,在支付宝异步通知的参数中,有个json字符串中的双引号被转化为html字符实体,导致了一直验签失败.

'fund_bill_list' => '[{"amount":"0.01","fundChannel":"ALIPAYACCOUNT"}]',

另外,公钥路径是支付宝公钥,不是开放平台app的公钥

另外还遇到一个坑

windows下使用支付宝提供的RSA签名验签工具生成的公钥与私钥,公钥上传到应用,并且配置到代码中之后,在使用函数生成签名的过程中

$priKey = file_get_contents($this->_privateKeyPath);
$res = openssl_get_privatekey($priKey); //此处res返回false

报错:

openssl_sign(): supplied key param cannot be coerced into a private key 

工具生成的应用私钥.txt文件中的私钥是保存成一行的,所以需要特殊处理下,函数修改方案

protected function generateSign($sign_str,$sign_type = 'RSA2')
{
    $priKey = file_get_contents($this->_privateKeyPath);
    $priKey = chunk_split($priKey, 64, "\n");
    $priKey = "-----BEGIN RSA PRIVATE KEY-----\n$priKey-----END RSA PRIVATE KEY-----\n";
    $res = openssl_get_privatekey($priKey);
    if ($sign_type == 'RSA2') {
        openssl_sign($sign_str, $sign, $res, OPENSSL_ALGO_SHA256);
    } else {
        openssl_sign($sign_str, $sign, $res);
    }
    
    $sign = base64_encode($sign);
    return $sign;
}

如果上面的类不能使用,把这个函数替换下.