當面付顧名思義,面對面付款,幫助商家在線下消費場景下實現快速收款;當面付產品支援條碼支付和掃碼支付兩種付款方式。
我們這裡對接的就是掃碼支付
掃碼支付,指用戶打開支付寶錢包中的「掃一掃」功能,掃描商家展示在某收銀場景下的二維碼並進行支付的模式。該模式適用於線下實體店支付、面對面支付等場景。業務流程如下圖所示:
由於當面付的簽約非常簡單,允許個體工商戶/個人商戶簽約。所以該方式也被大量用於線上的掃碼支付,由於該方式違反了支付寶的相關條款,有一定風險,咱作為技術交流,暫且先拋開這個問題。
作為技術對接,即使你沒有簽約當面付產品,也是可以進行開發的。
支付能力直接涉及到交易與資金,為了方便開放者調試支付能力,開放平台已經準備好沙箱環境,包括沙箱環境帳號和沙箱版支付寶錢包,這樣開發者就可以在沙箱環境調試了。點擊了解如何接入沙箱並接入沙箱環境。
所以我這邊開發使用的是沙箱環境,畢竟裡面好多錢,隨便用。
首先先下載相應的開發語言的sdk 下載:https://docs.open.alipay.com/194/105201/
掃碼支付文檔:https://docs.open.alipay.com/194/106078/
配置密鑰
為了保證交易雙方(商戶和支付寶)的身份和數據安全,開發者在調用接口前,需要配置雙方密鑰,對交易數據進行雙方校驗。
下載支付寶開放平台開發助手進行密鑰生成。
生成密鑰後,開發者需要在開放平台開發者中心進行密鑰配置,配置完成後可以獲取支付寶公鑰
設計接入
由於我這邊的設計不需要用到輪詢(後面會說),所以沒有加上
以下是我業務中的相關代碼
public function pay(){
if (request()->isPost()) {
// (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
// 需保证商户系统端不能重复,建议通过数据库sequence生成,
$uid = Session::get('sq.uid');
$outTradeNo = order\_num() . $uid;
// (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”
$subject = '聚合平台用户积分充值';
// (必填) 订单总金额,单位为元,不能超过1亿元
// 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
$totalAmount = input('post.pay\_money/f');
if($totalAmount < 1){
return \['status' => 1, 'msg' => '最低充值金額1元'\];
}
if($totalAmount > 9999999){
return \['status' => 1, 'msg' => '充值最大金額不能超過9999999元'\];
}
// (不推荐使用) 订单可打折金额,可以配合商家平台配置折扣活动,如果订单部分商品参与打折,可以将部分商品总价填写至此字段,默认全部商品可打折
// 如果该值未传入,但传入了【订单总金额】,【不可打折金额】 则该值默认为【订单总金额】- 【不可打折金额】
//String discountableAmount = "1.00"; //
// (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
// 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
// $undiscountableAmount = "0.01";
// 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
// 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
//$sellerId = "";
// 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
$body = "聚合平台用戶積分儲值" . $totalAmount . '元';
//商户操作员编号,添加此参数可以为商户操作员做销售统计
// $operatorId = "";
// (可选) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
// $storeId = "";
// 支付宝的店铺编号
// $alipayStoreId= "";
// 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),系统商开发使用,详情请咨询支付宝技术支持
// $providerId = ""; //系统商pid,作为系统商返佣数据提取的依据
// $extendParams = new ExtendParams();
// $extendParams->setSysServiceProviderId($providerId);
// $extendParamsArr = $extendParams->getExtendParams();
// 支付超时,线下扫码交易定义为5分钟
$timeExpress = "5m";
// 商品明细列表,需填写购买商品详细信息,
// $goodsDetailList = array();
// // 创建一个商品信息,参数含义分别为商品id(使用国标)、名称、单价(单位为分)、数量,如果需要添加商品类别,详见GoodsDetail
// $goods1 = new GoodsDetail();
// $goods1->setGoodsId("apple-01");
// $goods1->setGoodsName("iphone");
// $goods1->setPrice(3000);
// $goods1->setQuantity(1);
// //得到商品1明细数组
// $goods1Arr = $goods1->getGoodsDetail();
// // 继续创建并添加第一条商品信息,用户购买的产品为“xx牙刷”,单价为5.05元,购买了两件
// $goods2 = new GoodsDetail();
// $goods2->setGoodsId("apple-02");
// $goods2->setGoodsName("ipad");
// $goods2->setPrice(1000);
// $goods2->setQuantity(1);
// //得到商品1明细数组
// $goods2Arr = $goods2->getGoodsDetail();
// $goodsDetailList = array($goods1Arr,$goods2Arr);
//第三方应用授权令牌,商户授权系统商开发模式下使用
$appAuthToken = "";//根据真实值填写
// 创建请求builder,设置请求参数
$qrPayRequestBuilder = new AlipayTradePrecreateContentBuilder();
$qrPayRequestBuilder->setOutTradeNo($outTradeNo);
$qrPayRequestBuilder->setTotalAmount($totalAmount);
$qrPayRequestBuilder->setTimeExpress($timeExpress);
$qrPayRequestBuilder->setSubject($subject);
$qrPayRequestBuilder->setBody($body);
// $qrPayRequestBuilder->setUndiscountableAmount($undiscountableAmount);
// $qrPayRequestBuilder->setExtendParams($extendParamsArr);
// $qrPayRequestBuilder->setGoodsDetailList($goodsDetailList);
// $qrPayRequestBuilder->setStoreId($storeId);
// $qrPayRequestBuilder->setOperatorId($operatorId);
// $qrPayRequestBuilder->setAlipayStoreId($alipayStoreId);
$qrPayRequestBuilder->setAppAuthToken($appAuthToken);
// 调用qrPay方法获取当面付应答
require ROOT\_PATH.'extend/f2fpay/config/config.php';
$qrPay = new AlipayTradeService($config);
$qrPayResult = $qrPay->qrPay($qrPayRequestBuilder);
// 根据状态值进行业务处理
switch ($qrPayResult->getTradeStatus()){
case "SUCCESS":
$response = $qrPayResult->getResponse();
Db::name('order')
->insert(\[
'uid' => $uid,
'pay\_id' => $outTradeNo,
'money' => $totalAmount,
'creat\_time' => time(),
'subject' => $subject
\]);
return \['status' => 0, 'msg' => '支付寶創建訂單二維碼成功!!!"','data' => \[
'qr\_code' => $response->qr\_code,
'outTradeNo' => $outTradeNo
\]\];
// $qrcode = $qrPay->create\_erweima($response->qr\_code);
// echo $qrcode;
// print\_r($response);
break;
case "FAILED":
return \['status' => 1, 'msg' => '支付寶創建訂單二維碼失敗!!!"'\];
// if(!empty($qrPayResult->getResponse())){
// print\_r($qrPayResult->getResponse());
// }
break;
case "UNKNOWN":
return \['status' => 1, 'msg' => '系統異常,狀態未知!!!"'\];
// echo "系統異常,狀態未知!!!"."<br>--------------------------<br>";
// if(!empty($qrPayResult->getResponse())){
// print\_r($qrPayResult->getResponse());
// }
break;
default:
return \['status' => 1, 'msg' => '不支持的返回狀態,創建訂單二維碼返回異常!!!'\];
break;
}
return ;
}
}
以上就是當面付預下單代碼
關於這個SDK,我非常有必要吐槽一下,哪個傢伙寫的demo,還在PHP例子裡引入了個lotusphp框架,一大堆沒有用的東西,完全沒有考慮我們開發者能不能接受得了。
我也是花了一點時間,把SDK給精簡了一下,只拿出我需要的部分,放入了我自己的框架中,加上了namespace,自動載入。
掃碼支付有一個獨有的功能—-異步通知
這個也正是線上支付最為需要的功能
當收銀台調用預下單請求 API 生成二維碼展示給用戶後,用戶通過手機掃描二維碼進行支付,支付寶會將該筆訂單的變更信息,沿著商戶調用預下單請求時所傳入的異步通知地址 notify_url,通過 POST 請求的形式將支付結果作為參數通知到商戶系統。
記住這個異步通知地址需要在應用那設置一下。
// 异步回调
public function notify() {
if (request()->isPost()) {
require ROOT\_PATH.'extend/f2fpay/config/config.php';
$aop = new AopClient;
$aop->alipayrsaPublicKey = $config\['alipay\_public\_key'\];
$flag = $aop->rsaCheckV1($\_POST, null, "RSA2");
if ($flag) {
//異步SIGN驗證成功, 可以進行下一步動作。例如驗證訂單金額 然後完成訂單。之類的。。
//需要驗證的就是 訂單號 與 訂單金額是否一致,驗證成功 就可以對數據庫中的訂單進行操作了。
//TRADE\_SUCCESS 對於當面付來說,已經到帳了。詳情可以看這裡 https://www.cnblogs.com/tdalcn/p/5956690.html
if ($\_POST\['trade\_status'\] === "TRADE\_SUCCESS") {
//訂單處理模板
$res = Db::name('order')
->where('pay\_id', $\_POST\['out\_trade\_no'\])
->where('money', $\_POST\['total\_amount'\])
->where('status', 0)
->find();
if($res){
Db::name('order')
->where('id',$res\['id'\])
->update(\[
'status' => 1,
'buyer\_logon\_id' => $\_POST\['buyer\_logon\_id'\],
'pay\_time' => $\_POST\['gmt\_payment'\],
'pay\_no' => $\_POST\['trade\_no'\]
\]);
Db::name('user')
->where('uid',$res\['uid'\])
->setInc('integral', floatval($\_POST\['total\_amount'\]) \* 1000);
}
}
}
echo 'success'; //接口必須返回success 不然阿里會一直發送校驗驗證。
}
}
輪詢 其中如果需要頁面支付成功後同步跳轉,則需要添加輪詢,由於當面付是沒有同步通知這個功能,所以需要用到輪詢,並且這個方法也是在當面付的文檔中所提及到的。 以下代碼摘自Bty付費版
public function query()
{
if (input('post.no')) {
$out\_trade\_no = input('post.no');
$queryContentBuilder = new AlipayTradeQueryContentBuilder();
$queryContentBuilder->setOutTradeNo($out\_trade\_no);
$queryResponse = new AlipayTradeService($this->alipay\_config);
$queryResult = $queryResponse->queryTradeResult($queryContentBuilder);
$res\['status'\] = $queryResult->getTradeStatus();
$res\['buyer'\] = isset($queryResult->getResponse()->buyer\_logon\_id) ? $queryResult->getResponse()->buyer\_logon\_id : '';
$res\['amount'\] = $queryResult->getResponse()->buyer\_pay\_amount;
if ($res\['status'\] == 'SUCCESS') {
$this->paySuccess('', $out\_trade\_no);
}
exit(json\_encode($res));
} else {
$this->error('非法請求');
}
}
由於我們對接的並不是類似商城一般的系統,所以暫時用不到類似退款這些的複雜操作。
當面付的基礎對接就到此結束。
完結撒花!