半年前跟老师做项目的时候遇到需要接入支付宝进行支付。这一年以来接触前端觉得涉猎的东西太多了,现在好像什么都不会。。。 ~还是把🌰炒一下,实际接入场景可能根据业务都有所区别,这里通过以前做的项目作为示例,业务的内容不需要纠结,只需要从用法上理解即可。
示例
(前端是有PC端的web以及手机RN(android端),后端是koa2+leancloud)
接入支付宝支付的先置条件
个人无法申请,目前只有企业可以进行申请
1.在支付宝注册企业账号
2.需要在蚂蚁金服开放平台申请应用
完善资料——包括进行授权回调的地址和应用的网关以及接口加签方式,生成好的证书需要好好保管,这不仅是进行后续开发需要使用的,而且更关系到该应用的数据安全。
后进行签约并提交审核
PS: 签约的时候对不同途径有不同的要求,包括手机app支付需要上传关于app的文档(内含必须要有app界面截图!不然会被打回)
接入支付宝
业务思路
无论是支付宝又或者是第三方支付都是以这个为核心
接入方式
可以通过第三方进行快捷接入,也可以使用原生接口/SDK进行接入
在网页H5中使用的是beecloud 依然是只有企业用户可以使用,不对个人进行开放.第三方支付的好处就是统一入口,支持大量的支付途径如
但是依然是每个途径需要自己去开通相关的应用。
beecloud h5 秒支付
beecloud 秒支付的运作方式是将数据渲染在他提供的模板页面上,模板页面校验数据后跳转到支付宝支付页面,最后通过回调通知后台。官方文档
通过使用beecloud的秒支付button可以直接适配pc端和手机端的h5支付。
(在先置条件中必须进行PC端支付的签约才能继续使用)
在开通快捷支付后会得到以下内容
const appid='';
const secret='';
const masterSecret = '';
再在beecloud中填入回调地址后,支付成功会通知该地址,即有请求附带数据发送到该url中。
从用户访问页面开始看代码,由于数据都是储存到leancloud的云数据库中,所以会看到AV(😵不是你们想的那个!)类的相关内容,只需要知道该类是作为数据储存获取所用即可,以下是访问充值页面的路由。
var fun_payBalance = async(ctx, next) => {
var user = await AV.User.current();
var requestData = ctx.request.query;
var amount = requestData.amount;
if (user == null) {
ctx.response.redirect('/personal/recharge');
} else {
if (!amount) {
ctx.response.redirect('/personal/recharge');
}
}
var result = Pay.rechargeBalance(amount, '余额充值');
//这个是进行生成订单包括储存到数据库以及生成signStr进行校验
if (result) {
var signStr = result.signStr;
var outTradeNo = result.outTradeNo;
//将数据传到模板页面
ctx.render('paytest.html', {
title: '余额充值',
OutTradeNo: outTradeNo,
Sign: signStr,
amount: amount
});
} else {
console.log('error');
ctx.response.redirect('/personal/recharge');
}
}
Pay.rechargeBalance的内容
rechargeBalance:(amount,title)=>{
var user = AV.User.current();
if(!user){
return ;
}
var pay = new pays();
var outTradeNo=uuid.v4();
outTradeNo = outTradeNo.replace(/-/g, '');
amount = parseFloat(amount);
var data = appid + title + amount + outTradeNo + secret;
pay.set('orderNumber',outTradeNo);
pay.set('lines',amount);
pay.set('info',title);
pay.set('user',user);
//将订单存入数据库
pay.save();
return {
signStr:sign.createHash('md5').update(data, 'utf8').digest("hex"),
'outTradeNo':outTradeNo,
};
},
支付页面模板内容,核心的是Bc.click method
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<title>{{ title }}</title>
<link rel='stylesheet' href='stylesheets/style.css' />
</head>
<body>
<div class="description">
支付单号:{{ OutTradeNo}}
支付内容:{{title}}
支付金额:{{amount}}
{% if card %}
卡类型:{{card.typeInfo.get('cardName')}}
卡编号:{{card.get('cardId')}}
{%endif%}
<p><center><button id="test" class="button">点击支付</button></center></p>
</div>
<script id='spay-script' src='https://jspay.beecloud.cn/1/pay/jsbutton/returnscripts?appId=af1df3a3-ab4b-48d8-b71a-8dbd60a92451'></script>
<script>
document.getElementById("test").onclick = function() {
asyncPay();
};
function bcPay() {
var out_trade_no = "{{ OutTradeNo}}";
var sign = "{{ Sign }}";
console.log(sign);
console.log(out_trade_no);
/**
* click调用错误返回:默认行为console.log(err)
*/
BC.err = function(data) {
//注册错误信息接受
alert(data["ERROR"]);
}
/**
* 3. 调用BC.click 接口传递参数
*/
BC.click({
"title": "{{ title }}",
"amount": "{{amount}}",
"out_trade_no": out_trade_no, //唯一订单号
"sign" : sign,
/**
* optional 为自定义参数对象,目前只支持基本类型的key =》 value, 不支持嵌套对象;
* 回调时如果有optional则会传递给webhook地址,webhook的使用请查阅文档
*/
"optional": {"test": "willreturn"}
});
}
function asyncPay() {
if (typeof BC == "undefined"){
if( document.addEventListener ){
document.addEventListener('beecloud:onready', bcPay, false);
}else if (document.attachEvent){
document.attachEvent('beecloud:onready', bcPay);
}
}else{
bcPay();
}
}
</script>
</body>
</html>
支付成功的回调,回调路由
'use strict';
const AV = require('leanengine');
const Pay = require('../pay-service')
var beecloudWebhook = async(ctx,next)=>{
var data = ctx.request.body;
Pay.webhook(data);
}
module.exports={
'POST /webhook/back':beecloudWebhook,
}
回调的业务函数
webhook:async(data)=>{
var signKey = data.signature;
var signData = appid+data.transaction_id+data.transaction_type + data.channel_type + data.transaction_fee + masterSecret;
var signStr = sign.createHash('md5').update(signData, 'utf8').digest("hex")
//校验signStr 如果不符合则说明回调来源不合法
if(signStr!=signKey){console.log("key failure");}
//查询订单,在请求支付的时候会产生一个订单号
var query = new AV.Query('pays');
query.equalTo('orderNumber',data.transaction_id);
//查询第一条符合记录
var pay = await query.first();
//如果该订单已经完成了,说明回调重复了 退出
if(pay&&pay.get('status')===1){return {
"result":-1,
"message":"had been finished"
};}
else{
//如果订单不存在 退出
if(typeof pay ==="undefined"){
return{
"result":-1,
"message":"pay do not exit"};
}
//check the transaction_fee(这里可以进一步根据数据库中的订单信息检查金额是否正确) 这个历史版本,懒了就没做了orz
// about the pay result
if(data.transaction_type == "PAY"){
var user = await AV.User.current();
//如果支付成功了
var status = data.trade_success;
if(status){
//update the pay status,更新订单信息
pay.set('status',1);
var payId = pay.id;
await pay.save();
var pay_record =null;
let card_Id = pay.get('cardId');
//后面是业务内容了 不需要再看了
if(card_Id){
pay_record = new rechargeCard();
pay_record.set('cardId',pay.cardId);
let card = new AV.Query('Cards');
var fee = data.transaction_fee;
//测试模式订单金额扩大1000
if(envMode=="test"){
fee*=1000;
}
card.get(card_Id).then(function(findedCard){
findedCard.set(findedcard.get('cardBalance')+fee);
findedCard.save();
console.log(findedCard);
})
}else{
pay_record = new rechargeBalance();
var fee = data.transaction_fee;
// if the test mode the fee * 1000
if(envMode=="test"){
fee*=100;
}
console.log("before"+user.get('balance'));
user.set('balance',user.get('balance')+fee);
user.save();
console.log("after"+user.get('balance'));
}
if(pay_record){
pay_record.set('user',user);
pay_record.set('payLines',data.transaction_fee);
pay_record.set('payId',payId);
pay_record.save();
}
return {
"result":1,
"message":"success"
};
}else{
console.log("status failure");
}
}
}
},
至此第三方支付的🌰已经结束,可能夹杂了一些业务逻辑在里面,但是总体思路还是跟上面的图是一致的。
react-native(android) 接入原生支付
这部分接入是接入是参照一个小姐姐写的教程
这部分的核心内容是,集成原生的android模块
以及使用Ali Sdk nodejs第三方包
�
这部分以上说说的比较详细了
其中遇到的问题只有signStr 不正确的时候可能会出现
ALI 40247 在server端的 signStr 有问题
暂时无法获取订单信息 —— 没有传递金额
程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是success这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);
程序执行完成后,该页面不能执行页面跳转。如果执行页面跳转,支付宝会收不到success字符,会被支付宝服务器判定为该页面程序运行出现异常,而重发处理结果通知;
需要注意在处理alipay webhook的时候需要注意,返回信息的问题,在校验信息时候需要返回success,不然会一直发送信息。
koa2 中 使用上面提到的Ali Sdk nodejs第三方包,可以将这个ali对象存储在ctx.state中。
暂时写到这里,实际应用中涉及支付业务的部分必须要进行每天对账才能保证安全。