准备
需求
需求就是最简单的对接支付宝支付接口
方案心路历程
- 简单用http对接一下,不过想了一下觉得第三方提供的SDK总是不用,所以打算尝试一下
- 支付宝新版SDK,从文档到git再安装到使用,然后放弃,【文档不详、部分接口不完善】没办法
- 支付宝老版SDK,下载引入包装一气呵成
实操
支付宝后台配置
登录支付宝后台—进入后台—点击能力管理—查看已获取能力(手机网站支付)–!没有的话可以点击获取更多能力申请—然后点击管理进入
绑定应用—保存好appid
接着点击密钥管理—进入账户中心—点击开放平台密钥—设置接口加签方式
下载支付宝开发工具
生成密钥保存好应用私钥公钥
—复制应用钥在刚刚的加签管理弹窗粘贴—保存设置—然后就会弹出支付宝公钥
—复制保存
点击开发工具密钥—粘贴应用公钥
—设置本地ip与生产环境ip白名单
到这里支付宝后台的配置就完成了
编码
下载SDK—放入项目中
这里我们使用的是laravel框架为例
直接附上demo code
可以同时参考支付宝接口文档
<?php
namespace App\Services\Support\Ali;
require_once 'aop/AopClient.php'; // 引入SDK类
require_once 'aop/request/AlipayTradeWapPayRequest.php'; // 引入SDK类
use Illuminate\Support\Facades\Log;
class Alipay
{
public function __construct()
{
// 配置SDK
$this->baseUrl = config('ali.alipay.baseUrl');
$this->appid = config('ali.alipay.appid');
$this->privateKey = config('ali.alipay.privateKey');
$this->publicKey = config('ali.alipay.publicKey');
$this->aliPublicKey = config('ali.alipay.aliPublicKey');
$this->sign_type = "RSA2";
$this->charset = "utf-8";
}
public function wapPay($options)
{
$aop = new \AopClient();
$aop->gatewayUrl = $this->baseUrl . 'gateway.do';
$aop->appId = $this->appid;
$aop->rsaPrivateKey = $this->privateKey;
$aop->alipayrsaPublicKey = $this->aliPublicKey;
$aop->apiVersion = '1.0';
$aop->signType = 'RSA2';
$aop->postCharset = 'utf-8';
$aop->format = 'json';
$request = new \AlipayTradeWapPayRequest();
// 配置支付信息
$request->setReturnUrl($options['returnUrl']);
$request->setNotifyUrl($options['notifyUrl']);
$content = [
"subject" => $options['subject'],
"out_trade_no" => $options['outTradeNo'],
"total_amount" => $options['totalAmount'],
"quit_url" => $options['quitUrl'],
"product_code" => "QUICK_WAP_PAY",
];
$bizContent = json_encode($content);
$request->setBizContent($bizContent);
$result = $aop->pageExecute($request);
// 创建支付订单,返回支付页面的Form
// Log::info("Alipay->wapPay():result: $result");
return $result;
}
}
服务入口
<?php
namespace App\Services;
use App\Services\Support\Ali\Alipay;
class Ali
{
public function __construct()
{
}
public function alipay()
{
return new Alipay();
}
}
路由入口
<?php
namespace App\Http\Controllers;
use App\Services\Ali;
use App\Utils\Common\DataHandle;
class TestController extends Controller
{
public function test()
{
$systemSlug = 'test';
return response()->json((new Ali())->alipay()->wapPay([
"subject" => '支付测试',
"totalAmount" => 0.01,
'outTradeNo' => sprintf("%s_%s_%s", $systemSlug, time(), DataHandle::strRandom(17, 'key')), // 自定义
'timestamp' => date('Y-m-d H:i:s'),
'quitUrl' => config('ali.alipay.quitUrl'),
'returnUrl' => config('ali.alipay.returnUrl'),
'notifyUrl' => config('ali.alipay.notifyUrl') . 100,
]));
}
}
SDK返回的form
内容
<form id='alipaysubmit' name='alipaysubmit' action='https://openapi.alipay.com/gateway.do?charset=utf-8' method='POST'><input type='hidden' name='biz_content' value='{\"subject\":\"test\",\"out_trade_no\":\"test_1623329685_lpfbl1dan16dd5xw2\",\"total_amount\":0.01,\"quit_url\":\"xxx\",\"product_code\":\"QUICK_WAP_PAY\"}'/><input type='hidden' name='app_id' value='xxx'/><input type='hidden' name='version' value='1.0'/><input type='hidden' name='format' value='json'/><input type='hidden' name='sign_type' value='RSA2'/><input type='hidden' name='method' value='alipay.trade.wap.pay'/><input type='hidden' name='timestamp' value='2021-06-10 20:54:45'/><input type='hidden' name='alipay_sdk' value='alipay-sdk-PHP-4.11.14.ALL'/><input type='hidden' name='notify_url' value='xxx'/><input type='hidden' name='return_url' value='xxx'/><input type='hidden' name='charset' value='utf-8'/><input type='hidden' name='sign' value='xxx'/><input type='submit' value='ok' style='display:none;''></form><script>document.forms['alipaysubmit'].submit();</script>
前端处理form
,我这里使用的是uniapp,这个东西也踩了我很多坑
this.$http
.post({
path: "test",
dontShowToast: true,
errData: true,
data: data,
}).then((res) => {
if (this.alipay) {
//将接口返回的Form表单显示到页面
document.querySelector("body").innerHTML = res.data;
//调用submit 方法
document.forms[0].submit();
}
console.log(res.message);
this.submitSuccess();
})
效果
异步处理结果
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Api\Controller;
use App\Payorder;
use App\Services\Ali;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class NotifyController extends Controller
{
public function payCall($payType)
{
try {
switch ($payType) {
case 100:
// 支付宝手机网站支付
$data = $_POST;
// 异步回调验签
if (!(new Ali())->alipay()->signCheck($data)) {
Log::error("fails for notify, 验签失败 data: " . json_encode($data));
return $this->rsp(403, []);
}
$orderId = $data['out_trade_no'];
$statusMapState = [
'WAIT_BUYER_PAY' => 0, // 交易创建,等待买家付款。
'TRADE_CLOSED' => -1, // 在指定时间段内未支付时关闭的交易或在交易完成全额退款成功时关闭的交易。
'TRADE_SUCCESS' => 1, // 商户签约的产品支持退款功能的前提下,买家付款成功。
'TRADE_FINISHED' => 2, // 商户签约的产品不支持退款功能的前提下,买家付款成功;或者,商户签约的产品支持退款功 能的前提下,交易已经成功并且已经超过可退款期限。
];
$state = $statusMapState[$data['trade_status']];
break;
default:
return $this->rsp(403, []);
}
} catch (\Exception $e) {
Log::error("fails for notify, $e");
return $this->rsp(403, []);
}
DB::beginTransaction();
// 支付完成操作
$payorder = Payorder::where(['order_id' => $orderId, 'state' => 0])->first();
if (!$payorder) {
DB::rollBack();
return $this->rsp(400, [], '参数错误');
}
$payorder->notifyData = $data;
$payorder->state = $state;
$payorder->save();
DB::commit();
return $this->rsp(0, []);
}
}
这个demo中只是简单的记录支付结果我配置的异步回调url是https://xxx/api/notify/payCall/{paytype}
踩坑
laravel与aop冲突
laravel中引入aop
会导致框架自定义函数与SDK冲突,这里我们需要手动修改SDK源码
将原本的encrypt
和decrypt
函数名修改成自定义的名字,我这里修改为alipayEncrpt
和alipayDecrypt
然后把使用到这两个函数的源码也一并修改
我这里是已经修改好之后的
支付完成同步回调失效
在使用SDK中的alipay.trade.wap.pay
接口时遇到了同步回调失效问题
跟踪问题
在上面的代码中是已经设置了同步回调和异步回调配置的,这里忍不住吐槽一下支付宝文档,找不到在哪里可以配置这两个参数
这是支付宝的请求实例代码
$aop = new AopClient ();
$aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
$aop->appId = 'your app_id';
$aop->rsaPrivateKey = '请填写开发者私钥去头去尾去回车,一行字符串';
$aop->alipayrsaPublicKey='请填写支付宝公钥,一行字符串';
$aop->apiVersion = '1.0';
$aop->signType = 'RSA2';
$aop->postCharset='GBK';
$aop->format='json';
$request = new AlipayTradeWapPayRequest ();
$request->setBizContent("{" .
"\"body\":\"Iphone6 16G\"," .
"\"subject\":\"大乐透\"," .
"\"out_trade_no\":\"70501111111S001111119\"," .
"\"timeout_express\":\"90m\"," .
"\"time_expire\":\"2016-12-31 10:05\"," .
"\"total_amount\":9.00," .
"\"auth_token\":\"appopenBb64d181d0146481ab6a762c00714cC27\"," .
"\"goods_type\":\"0\"," .
"\"quit_url\":\"http://www.taobao.com/product/113714.html\"," .
"\"passback_params\":\"merchantBizType%3d3C%26merchantBizNo%3d2016010101111\"," .
"\"product_code\":\"QUICK_WAP_PAY\"," .
"\"promo_params\":\"{\\\"storeIdType\\\":\\\"1\\\"}\"," .
"\"extend_params\":{" .
"\"sys_service_provider_id\":\"2088511833207846\"," .
"\"hb_fq_num\":\"3\"," .
"\"hb_fq_seller_percent\":\"100\"," .
"\"industry_reflux_info\":\"{\\\\\\\"scene_code\\\\\\\":\\\\\\\"metro_tradeorder\\\\\\\",\\\\\\\"channel\\\\\\\":\\\\\\\"xxxx\\\\\\\",\\\\\\\"scene_data\\\\\\\":{\\\\\\\"asset_name\\\\\\\":\\\\\\\"ALIPAY\\\\\\\"}}\"," .
"\"card_type\":\"S0JP0000\"," .
"\"specified_seller_name\":\"XXX的跨境小铺\"" .
" }," .
"\"merchant_order_no\":\"20161008001\"," .
"\"enable_pay_channels\":\"pcredit,moneyFund,debitCardExpress\"," .
"\"disable_pay_channels\":\"pcredit,moneyFund,debitCardExpress\"," .
"\"store_id\":\"NJ_001\"," .
" \"goods_detail\":[{" .
" \"goods_id\":\"apple-01\"," .
"\"alipay_goods_id\":\"20010001\"," .
"\"goods_name\":\"ipad\"," .
"\"quantity\":1," .
"\"price\":2000," .
"\"goods_category\":\"34543238\"," .
"\"categories_tree\":\"124868003|126232002|126252004\"," .
"\"body\":\"特价手机\"," .
"\"show_url\":\"http://www.alipay.com/xxx.jpg\"" .
" }]," .
"\"specified_channel\":\"pcredit\"," .
"\"business_params\":\"{\\\"data\\\":\\\"123\\\"}\"," .
"\"ext_user_info\":{" .
"\"name\":\"李明\"," .
"\"mobile\":\"16587658765\"," .
"\"cert_type\":\"IDENTITY_CARD\"," .
"\"cert_no\":\"362334768769238881\"," .
"\"min_age\":\"18\"," .
"\"fix_buyer\":\"F\"," .
"\"need_check_info\":\"F\"" .
" }" .
" }");
$result = $aop->pageExecute ( $request);
$responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
$resultCode = $result->$responseNode->code;
if(!empty($resultCode)&&$resultCode == 10000){
echo "成功";
} else {
echo "失败";
}
不过通过查看AlipayTradeWapPayRequest
我找到了这两个函数
但是这反而让我迷失自我了,同步回调url已经设置好了,那么为什么还会失效呢??
百度了一圈,并没有找到问题
emmm…
接着,终于发现问题关键点,我习惯保留第三方对接的上下文,回来查看SDK给我返回的form
我发现SDK给我返回的form
有notify_url
但是并没有return_url`这个input,上图是修复问题后的返回结果
于是我又回去看SDK源码
这里附上完整的AopClient
类中的pageExecute
函数
<?php
require_once 'AopEncrypt.php';
require_once 'EncryptParseItem.php';
require_once 'EncryptResponseData.php';
require_once 'SignData.php';
class AopClient
{
...
public function pageExecute($request, $httpmethod = "POST", $appAuthToken = null)
{
$this->setupCharsets($request);
if (strcasecmp($this->fileCharset, $this->postCharset)) {
// writeLog("本地文件字符集编码与表单提交编码不一致,请务必设置成一样,属性名分别为postCharset!");
throw new Exception("文件编码:[" . $this->fileCharset . "] 与表单提交编码:[" . $this->postCharset . "]两者不一致!");
}
$iv = null;
if (!$this->checkEmpty($request->getApiVersion())) {
$iv = $request->getApiVersion();
} else {
$iv = $this->apiVersion;
}
//组装系统参数
$sysParams["app_id"] = $this->appId;
$sysParams["version"] = $iv;
$sysParams["format"] = $this->format;
$sysParams["sign_type"] = $this->signType;
$sysParams["method"] = $request->getApiMethodName();
$sysParams["timestamp"] = date("Y-m-d H:i:s");
$sysParams["alipay_sdk"] = $this->alipaySdkVersion;
if (!$this->checkEmpty($request->getTerminalType())) {
$sysParams["terminal_type"] = $request->getTerminalType();
}
if (!$this->checkEmpty($request->getTerminalInfo())) {
$sysParams["terminal_info"] = $request->getTerminalInfo();
}
if (!$this->checkEmpty($request->getProdCode())) {
$sysParams["prod_code"] = $request->getProdCode();
}
if (!$this->checkEmpty($request->getNotifyUrl())) {
$sysParams["notify_url"] = $request->getNotifyUrl();
}
$sysParams["charset"] = $this->postCharset;
if (!$this->checkEmpty($appAuthToken)) {
$sysParams["app_auth_token"] = $appAuthToken;
}
//获取业务参数
$apiParams = $request->getApiParas();
if (method_exists($request, "getNeedEncrypt") && $request->getNeedEncrypt()) {
$sysParams["encrypt_type"] = $this->encryptType;
if ($this->checkEmpty($apiParams['biz_content'])) {
throw new Exception(" api request Fail! The reason : encrypt request is not supperted!");
}
if ($this->checkEmpty($this->encryptKey) || $this->checkEmpty($this->encryptType)) {
throw new Exception(" encryptType and encryptKey must not null! ");
}
if ("AES" != $this->encryptType) {
throw new Exception("加密类型只支持AES");
}
// 执行加密
$enCryptContent = alipayEncrypt($apiParams['biz_content'], $this->encryptKey);
$apiParams['biz_content'] = $enCryptContent;
}
//print_r($apiParams);
$totalParams = array_merge($apiParams, $sysParams);
//待签名字符串
$preSignStr = $this->getSignContent($totalParams);
//签名
$totalParams["sign"] = $this->generateSign($totalParams, $this->signType);
if ("GET" == strtoupper($httpmethod)) {
//value做urlencode
$preString = $this->getSignContentUrlencode($totalParams);
//拼接GET请求串
$requestUrl = $this->gatewayUrl . "?" . $preString;
return $requestUrl;
} else {
//拼接表单字符串
return $this->buildRequestForm($totalParams);
}
}
...
}
看完,同志们发现什么了吗???
有设置notify_url
参数,并没有设置return_url
的代码,what??
恕我愚昧,我不知道这算不算bug,不过在我的demo里的确是同步回调失败
最后,修改为以上代码,解决问题