uniapp开发微信小程序微信支付实战
前端框架:uniapp
后端框架:laravel
后端依赖:overtrue/wechat
照例我们还是安装一下easywechat依赖
composer require overtrue/wechat:~4.0 -vvv
配置一下我们的微信支付的sdk参数
//微信支付配置
$wechat_pay_config = [
// 必要配置
'app_id' => 'wx***********',
'mch_id' => '1589*********',
'key' => 'emiaodui*************', // API 密钥
// 如需使用敏感接口(如退款、发送红包等)需要配置 API 证书路径(登录商户平台下载 API 证书)
'cert_path' => "/www/wwwroot/maijuanba/wechat_key/apiclient_cert.pem", // XXX: 绝对路径!!!!
'key_path' => "/www/wwwroot/maijuanba/wechat_key/apiclient_key.pem", // XXX: 绝对路径!!!!
'notify_url' => env('APP_URL') . "/api/order/notify", // 支付回调通知路径 你也可以在下单时单独设置来想覆盖它
];
define('wechat_pay_config', $wechat_pay_config);
注意我们的cert证书需要绝对路径
-----------------------------------------
配置完成了再理一下我们的支付思路
>用户选中商品
>提交订单给后端
>后端创建订单(存储需要购买的商品,数量,总金额是多少)
>提交给微信支付创建一个支付订单
>返回数据给前端js
>前端js吊起微信支付
>微信通知后端支付结果
>前端跳转到支付结果处理界面
大概思路和步骤就是这样了,那么就着手开始搞吧一步一步来看代码实现
---------------------------------------------
前端选中商品,调用后端创建订单接口
create_order() { this.request.post('/order/create_order', { id: this.goods.id, number: this.number }).then(res => { if (res.code == 20000) { //调起支付 this.request.pay(res.data.pay, res.data.order) } else { uni.showToast({ title: res.msg, icon: 'none' }) return } }) },
传递参数是商品的id和需要购买的数量,如果创建订单成功会吊起request.pay()方法,这个方法我们后面再看,先一步一步的来
----------------------------------------------
看看我们后端的create_order方法
public function create_order(Request $request)
{
try {
DB::beginTransaction();
$id = (int)$request->input('id');
$number = (int)$request->input('number');
if ($number < 1 || $number > 999)
return self::errorMsg('购买数量范围1-999');
$goods = GoodsModel::query()->where('id', $id)->first();
if (!isset($goods) || $goods->status !== 0)
return self::errorMsg('没有找到该商品');
if ($number > $goods->limit) {
return self::errorMsg('该商品单次最多购买' . $goods->limit . '件!');
}
$order = OrderModel::query()->where('goods_id', $id)
->where('uid', $request->get('uid'))
->where('status', 0)
->first();
if (isset($order))
return self::errorMsg('你已经创建了该商品的订单,请完成支付或取消支付后重新购买');
$count = CardModel::query()->where('goods_id', $id)->where('pay', 0)->count(); //现有库存
$lock = OrderModel::query()->where('goods_id', $id)->where('status', 0)->sum('number'); //锁定库存
if ($lock >= $count)
return self::errorMsg('当前库存不够');
$count = $count - $lock;
if ($number > $count)
return self::errorMsg("当前库存不够,最多购买 $count 件");
$order = new OrderModel();
$order->uid = $request->get('uid');
$order->goods_id = $id;
$order->number = $number;
$order->price = $number * $goods->money;
$order->money = $goods->money;
$order->old_money = $goods->old_money;
$order->old_price = $number * $goods->old_money;
$order->des_money = $order->old_price - $order->price;
$order->order_id = date('YmdHis', time()) . str_pad($request->get('uid'), 9, '0', STR_PAD_LEFT);;
$order->create_time = time();
if ($order->save()) {
$data['order'] = $order;
$data['pay'] = $this->pay_order($goods->name . "*" . $order->number, $order, $request);
DB::commit();
return self::returnMsg($data);
} else {
DB::rollBack();
return self::errorMsg();
}
} catch (\Exception $exception) {
DB::rollBack();
return self::errorMsg($exception->getMessage());
}
}
前面有一大堆的的业务逻辑,什么库存啊,限量购买啊,存在相同的订单啊,前面都通过了才可以调用我们的pay_order()方法并存储我们的订单信息入数据库,进入重头戏
$data['order'] = $order;
$data['pay'] = $this->pay_order($goods->name . "*" . $order->number, $order, $request); //我们要发起支付了
----------------------------------------------
看看pay_order方法
public function pay_order($sub, $order, $request)
{
$app = Factory::payment(wechat_pay_config);
$jssdk = $app->jssdk;
$res = $app->order->unify([
'body' => $sub,
'out_trade_no' => $order->order_id,
'total_fee' => $order->price * 100,
'trade_type' => 'JSAPI', // 请对应换成你的支付方式对应的值类型
'openid' => $request->get('member')['openid']
]);
if (!isset($res) || $res['result_code'] !== "SUCCESS")
throw new \Exception($res['err_code_des']);
$js = $jssdk->bridgeConfig($res['prepay_id'], false);
$res['js'] = $js;
return $res;
}
Factory类位置
use EasyWeChat\Factory;
需要注意的的是,微信的金额单位为角,我们的单位为元,有小数点用decimal类型存储,所以需要*100
这一步就是通知微信支付服务器,我们需要创建一个支付订单,让微信服务器创建一个并告诉我们调用的令牌,我们拿到令牌后再返回给我们的前端
-------------------------------------------------
现在一切顺利,我们的用户购买的商品库存够,也没发生什么其他意外,已经可以开始支付了,那么就回到我们第一步的提到的request.pay方法
pay(data, order) { console.log(data); uni.requestPayment({ provider: 'wxpay', appId: data.js.appId, timeStamp: data.js.timeStamp, nonceStr: data.js.nonceStr, package: data.js.package, signType: 'MD5', paySign: data.js.paySign, success: function(res) { uni.showToast({ title: '支付成功', icon: "success" }) setTimeout(() => { uni.navigateTo({ url: "/pages/tabbar/card/pay?id=" + order.id }) }, 2000) console.log('success:' + JSON.stringify(res)); }, fail: function(err) { console.log('fail:' + JSON.stringify(err)); } }); },
uniapp为我们提供了
uni.requestPayment()
我们在后端拿到的
this.request.pay(res.data.pay, res.data.order)
数据传递给该方法,后端返回给了我们令牌,正确传参就可以了,这样就发起支付了
注意success回调指的不是支付成功,而是正确吊起微信支付,就触发success,所以是否支付成功我们还是要跳转到支付结果页面去。
那么到底有没有支付成功呢?微信服务器会直接告诉我们的后端
--------------------------------------
看看我们后端的微信回调方法
public function notify(Request $request)
{
$app = Factory::payment(wechat_pay_config);
$response = $app->handlePaidNotify(function ($message, $fail) {
// 你的逻辑
DB::beginTransaction();
try {
$order = OrderModel::with(['goods'])->where('order_id', $message['out_trade_no'])->first();
if (!isset($order)) {
$fail('订单不存在');
return false;
}
if ($order->status == 1)
return true; //订单已支付
if ($order->status == 0) {
//订单未支付 写确认逻辑
$order->status = 1;
$order->pay_time = time();
$order->sdk_order_id = $message['transaction_id'];
$order->goods->number += $order->number;
$card = CardModel::query()->where('goods_id', $order->goods_id)
->where('pay', 0)
->orderByDesc('prior')
->orderBy('id')
->limit($order->number)
->get();
foreach ($card as $k => $v) {
$v->pay = 2;
$v->pay_time = $order->pay_time;
$v->order_id = $order->id;
$v->uid = $order->uid;
$v->money = $order->money;
$v->save();
}
$order->save();
$order->goods->save();
$member = MembersModel::query()->where('id', $order->uid)->first();
$member->money += $order->price;
$member->old_money += $order->old_price;
$member->save();
DB::commit();
return true;
}
$fail('啥子错误!');
return false;
} catch (\Exception $exception) {
$fail($exception->getMessage());
}
});
return $response;
}
前面两行主要就是鉴定微信支付传递给我们的数据是否合法,easychat已经为我们做了这些鉴定了,所以直接写我们的逻辑就行了。
如果订单不存在,或者订单已经支付成功了,那么我们就不应该继续给用户增加金额了对吧,只有正确的支付才能让我们的订单完成,然后把商品给用户。
当订单完成了,我们前端的支付结果页面就可以通过order订单的id去查数据库该订单是否支付成功了。
------------------------------------------
还有一个地方,订单创建成功了,微信也吊起支付了,但是发现钱包里面钱不够该怎么办(我当然不可能帮用户把微信钱包充满!!!!)
但是等一会用户的老公给她转了200块钱还是想购买我们的商品,当然要有一个继续支付的按钮让用户继续完成刚刚创建的订单。
前端代码,继续支付按钮的逻辑
continue_order(id) { this.request.post('/order/continue_order', { id: id, }).then(res => { if (res.code == 20000) { //吊起支付 this.request.pay(res.data.pay, res.data.order) } else { uni.showToast({ title: res.msg, icon: 'none' }) return } }) },
后端代码,继续支付的令牌获取
public function continue_order(Request $request)
{
try {
$id = (int)$request->input('id');
$order = OrderModel::with(['goods:id,name'])->where('id', $id)->first();
if (!isset($order) || $order->uid !== $request->get('uid'))
return self::errorMsg('没有找到此订单');
if ($order->status !== 0)
return self::errorMsg('该订单已支付或者已过期');
if ($order->status == 0 and $order->create_time + 300 < time())
return self::errorMsg('该订单已过期');
$data['order'] = $order;
$data['pay'] = $this->pay_order($order->goods->name . "*" . $order->number, $order, $request);
return self::returnMsg($data);
} catch (\Exception $exception) {
return self::errorMsg($exception->getMessage());
}
}
这样一顿操作后,用户就可以继续支付我们刚刚的订单了
------------------------------------------------
还有我们的order表的数据结构
# Host: blog.95shouyou.com (Version: 5.7.32-log) # Date: 2020-12-31 14:47:53 # Generator: MySQL-Front 5.3 (Build 4.234) /*!40101 SET NAMES utf8 */; # # Structure for table "order" # CREATE TABLE `order` ( `id` int(11) NOT NULL AUTO_INCREMENT, `uid` int(11) NOT NULL DEFAULT '0' COMMENT '用户id', `goods_id` int(11) NOT NULL DEFAULT '0' COMMENT '商品id', `number` int(11) NOT NULL DEFAULT '0' COMMENT '购买数量', `price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '购买总价', `money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '购买单价', `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建日期', `status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '0:未支付 1:支付 2:过期', `pay_time` int(11) NOT NULL DEFAULT '0' COMMENT '支付日期', `order_id` varchar(255) NOT NULL DEFAULT '0' COMMENT '自有订单id', `sdk_order_id` varchar(255) DEFAULT NULL COMMENT '第三方订单id', `old_money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠前单价', `old_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠前总价', `des_money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '为用户节省的钱', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
