目录
前言
准备:
1:应用的证书(证书申请和配置可以参考我另一篇分享“支付宝 ‘应用证书配置’ ”)
2:配置证书后注意公共参数中 私钥值 是csr生成后的私钥
3:需要接口能力已开通
一、服务层
<?php
class AlipayMethod
{
// 私钥值
const RSAPRIVATEKEY = "配置证书后私钥是在CSR生成中的私钥";
// 网关地址
const SERVERURL = "https://openapi.alipay.com/gateway.do";
// 开放平台上创建的应用的ID
const APPID = "app_id";
// 字符串编码,推荐:utf-8
const CHARSET = "UTF-8";
// 签名算法类型,推荐:RSA2
const SIGNTYPE = "RSA2";
// 报文格式,推荐:json
const FORMAT = "json";
// 敏感信息对称加密算法密钥
const ENCRYPYKEY = "";
// 调用的接口版本,固定为:1.0
const VERSION = "1.0";
// 接口方法
const METHOD_TRANSFER = "alipay.fund.trans.uni.transfer"; // 转账到支付宝账号
const METHOD_OAUTH_TOKEN = "alipay.system.oauth.token"; // 换取授权访问令牌
const TOKE_NGRANT_TYPE = "authorization_code"; // 访问令牌授权方式
// 证书
const ALIPAY_ROOT_CERT = "你的路径"; // 支付宝根证书路径
const ALIPAY_CERT_PUBLIC_KEY = "你的路径"; // 应用公钥证书路径
/**
* (单笔转账接口)转账到支付宝账号
* @param sOid 订单号
* @param ALIPAY_USER_ID 支付宝用户UID
* @param nMoney 转款金额
* @return array
*/
public function onPaymentByAlipay($alipayUserId, $nMoney)
{
// 转账金额:保留两位小数
$transAmount = number_format($nMoney, 2);
// $transAmount = number_format("1", 2);
// 请求参数
$biz_content = [
// 商家侧唯一订单号,由商家自定义
"out_biz_no" => "测试订单号".time(),
// 订单总金额,单位为元,不支持千位分隔符,精确到小数点后两位
"trans_amount" => $transAmount,
// 固定为 TRANS_ACCOUNT_NO_PWD
"product_code" => "TRANS_ACCOUNT_NO_PWD",
// 固定为 DIRECT_TRANSFER
"biz_scene" => "DIRECT_TRANSFER",
// 转账业务的标题:收款方会收到
"order_title" => "测试支付",
// 收款方信息
"payee_info" => [
// 参与方的标识 ID:ALIPAY_USER_ID(UID) ALIPAY_LOGON_ID(支付宝登录号)
"identity" => $alipayUserId,
"identity_type" => "ALIPAY_USER_ID",
// "name" => "真实姓名", // identity_type = ALIPAY_LOGON_ID name必填
]
];
// 请求参数
$params = [
"app_id" => self::APPID,
"method" => self::METHOD_TRANSFER,
"format" => self::FORMAT,
"charset" => self::CHARSET,
"sign_type" => self::SIGNTYPE,
"timestamp" => date('Y-m-d H:i:s'),
"version" => self::VERSION,
"biz_content" => json_encode($biz_content, 256),
"alipay_root_cert_sn" => $this->getRootCertSN(dirname(__FILE__) . self::ALIPAY_ROOT_CERT), // 支付宝根证书路径
"app_cert_sn" => $this->getCertSN(dirname(__FILE__) . self::ALIPAY_CERT_PUBLIC_KEY), // 应用公钥证书路径
];
// 签名
$params["sign"] = $this->generateSign($params, $params['sign_type']);
// 发起请求
$result = $this->curlPost('https://openapi.alipay.com/gateway.do', $params);
// 结果数组化
$result = json_decode($result, true);
return $result;
}
/**
* 换取授权访问令牌
* @param authCode 授权code
* @return array
*/
public function getAccessToken($authCode)
{
//公共参数
$params = array(
"app_id" => self::APPID,
"method" => self::METHOD_OAUTH_TOKEN,
"format" => self::FORMAT,
"charset" => self::CHARSET,
"sign_type" => self::SIGNTYPE,
"timestamp" => date("Y-m-d H:i:s"),
"version" => self::VERSION,
"grant_type" => self::TOKE_NGRANT_TYPE,
"code" => $authCode,
"alipay_root_cert_sn" => $this->getRootCertSN(dirname(__FILE__) . self::ALIPAY_ROOT_CERT), // 支付宝根证书路径
"app_cert_sn" => $this->getCertSN(dirname(__FILE__) . self::ALIPAY_CERT_PUBLIC_KEY), // 应用公钥证书路径
);
// 签名
$params["sign"] = $this->generateSign($params, $params['sign_type']);
// 发起请求
$result = $this->curlPost("https://openapi.alipay.com/gateway.do?charset=" . self::CHARSET, $params);
// 结果数组化
$result = json_decode($result, true);
return $result;
}
/**-------------------------------我是分割线---------------------------------- */
/**
* 加签
* @param $params
* @param string $signType
* @return mixed
*/
public function generateSign($params, $signType)
{
return $this->sign($this->getSignContent($params), $signType);
}
protected function sign($data, $signType)
{
$priKey = self::RSAPRIVATEKEY;
$res = "-----BEGIN RSA PRIVATE KEY-----\n" .
wordwrap($priKey, 64, "\n", true) .
"\n-----END RSA PRIVATE KEY-----";
($res) or die('您使用的私钥格式错误,请检查RSA私钥配置');
if ($signType == "RSA2") {
openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
// OPENSSL_ALGO_SHA256是php5.4.8以上版本才支持
// openssl_sign($data, $sign, $res, version_compare(PHP_VERSION, '5.4.0', '<') ? SHA256 : OPENSSL_ALGO_SHA256);
} else {
openssl_sign($data, $sign, $res);
}
$sign = base64_encode($sign);
return $sign;
}
/**
* 校验$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;
}
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, self::CHARSET);
if ($i == 0) {
$stringToBeSigned .= "$k" . "=" . "$v";
} else {
$stringToBeSigned .= "&" . "$k" . "=" . "$v";
}
$i++;
}
}
unset($k, $v);
return $stringToBeSigned;
}
/**
* 转换字符集编码
* @param $data
* @param $targetCharset
* @return string
*/
function characet($data, $targetCharset)
{
if (!empty($data)) {
$fileType = self::CHARSET;
if (strcasecmp($fileType, $targetCharset) != 0) {
$data = mb_convert_encoding($data, $targetCharset, $fileType);
}
}
return $data;
}
/**
* 发送curl请求
* @param $url
* @param null $postFields
* @return bool|string
* @throws Exception
*/
protected function curlPost($url, $postFields = null)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
$postBodyString = "";
$encodeArray = array();
$postMultipart = false;
if (is_array($postFields) && 0 < count($postFields)) {
foreach ($postFields as $k => $v) {
if ("@" != substr($v, 0, 1)) //判断是不是文件上传
{
$postBodyString .= "$k=" . urlencode($this->characet($v, self::CHARSET)) . "&";
$encodeArray[$k] = $this->characet($v, self::CHARSET);
} else //文件上传用multipart/form-data,否则用www-form-urlencoded
{
$postMultipart = true;
$encodeArray[$k] = new \CURLFile(substr($v, 1));
}
}
unset($k, $v);
curl_setopt($ch, CURLOPT_POST, true);
if ($postMultipart) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $encodeArray);
} else {
curl_setopt($ch, CURLOPT_POSTFIELDS, substr($postBodyString, 0, -1));
}
}
if (!$postMultipart) {
$headers = array('content-type: application/x-www-form-urlencoded;charset=' . self::CHARSET);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
$reponse = curl_exec($ch);
if (curl_errno($ch)) {
throw new \Exception(curl_error($ch), 0);
} else {
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (200 !== $httpStatusCode) {
throw new \Exception($reponse, $httpStatusCode);
}
}
curl_close($ch);
return $reponse;
}
/**
* 从证书中提取序列号
* @param $cert
* @return string
*/
public function getCertSN($certPath)
{
$cert = file_get_contents($certPath);
$ssl = openssl_x509_parse($cert);
$SN = md5($this->array2string(array_reverse($ssl['issuer'])) . $ssl['serialNumber']);
return $SN;
}
/**
* 提取根证书序列号
* @param $cert 根证书
* @return string|null
*/
public function getRootCertSN($certPath)
{
$cert = file_get_contents($certPath);
$this->alipayRootCertContent = $cert;
$array = explode("-----END CERTIFICATE-----", $cert);
$SN = null;
for ($i = 0; $i < count($array) - 1; $i++) {
$ssl[$i] = openssl_x509_parse($array[$i] . "-----END CERTIFICATE-----");
if (strpos($ssl[$i]['serialNumber'], '0x') === 0) {
$ssl[$i]['serialNumber'] = $this->hex2dec($ssl[$i]['serialNumberHex']);
}
if ($ssl[$i]['signatureTypeLN'] == "sha1WithRSAEncryption" || $ssl[$i]['signatureTypeLN'] == "sha256WithRSAEncryption") {
if ($SN == null) {
$SN = md5($this->array2string(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
} else {
$SN = $SN . "_" . md5($this->array2string(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
}
}
}
return $SN;
}
/**
* 0x转高精度数字
* @param $hex
* @return int|string
*/
function hex2dec($hex)
{
$dec = 0;
$len = strlen($hex);
for ($i = 1; $i <= $len; $i++) {
$dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
}
return $dec;
}
protected function array2string($array)
{
$string = [];
if ($array && is_array($array)) {
foreach ($array as $key => $value) {
$string[] = $key . '=' . $value;
}
}
return implode(',', $string);
}
}
二、控制层
1.控制层
/**
* 换取授权访问令牌
* @param authCode 授权code
* @return array
*/
public function getAccessToken($authCode,$nMoney)
{
$cAlipayMethod = new AlipayMethod();
// 换取授权访问令牌(获取返回中code对应的user_id)
$result = $cAlipayMethod->getAccessToken($authCode);
if (!isset($result["alipay_system_oauth_token_response"])) {
// 换取授权访问令牌失败
return json(["code" => 0, "data" => $result]);
} else {
// 获取用户唯一标识
$alipayUserId = $result["alipay_system_oauth_token_response"]["user_id"];
// 请求转账
$result = $cAlipayMethod->onPaymentByAlipay($alipayUserId, $nMoney);
if ($result["alipay_fund_trans_uni_transfer_response"]["code"] == "10000") {
dump("转账成功")
// 业务逻辑
} else {
dump("转账失败")
// 业务逻辑
}
}
}
2.接口返回参数实例
alipay.system.oauth.token(换取授权访问令牌):
成功:
{
"alipay_system_oauth_token_response": {
"access_token": "....",
"alipay_user_id": "....",
"auth_start": "2022-11-19 11:05:18",
"expires_in": ....,
"re_expires_in": ....,
"refresh_token": "....",
"user_id": "...." // 用户标识:是我们需要的
},
"alipay_cert_sn": "....",
"sign": "...."
}
错误:
{
"error_response": {
"code": "40002",
"msg": "Invalid Arguments",
"sub_code": "isv.code-invalid",
"sub_msg": "授权码code无效"
},
"alipay_cert_sn": "....",
"sign": "...."
}
alipay.fund.trans.uni.transfer(单笔转账接口)转账到支付宝账号:
成功:
{
"alipay_fund_trans_uni_transfer_response": {
"code": "10000",
"msg": "Success",
"order_id": "....",
"out_biz_no": "....",
"pay_fund_order_id": "....",
"status": "SUCCESS",
"trans_date": "2022-11-18 13:45:02"
},
"alipay_cert_sn": "....",
"sign": "...."
}
异常:
{
"alipay_fund_trans_uni_transfer_response": {
"code": "40004",
"msg": "Business Failed",
"sub_code": "INVALID_PARAMETER",
"sub_msg": "参数有误参数payee_info.identity格式非法"
},
"alipay_cert_sn": "....",
"sign": "...."
}