前言:最近接到一个需求,做一个收银台,实现效果:生成一个商家支付码,用户无论是用微信还是支付宝扫码,都可以完成支付。遂采用Uniapp:跨平台开发框架,支持一次开发,多端部署。H5:通过网页实现支付功能,兼容性强,无需依赖原生环境。
实现步骤:1. 前端新建一个uniapp项目,开发完成后,打包发布成H5,会生成一个链接;2. 用链接生成一个二维码即可。
文章目录
一、项目目录介绍
1.1 新建一个uniapp项目,运行到浏览器进行查看,以下是项目各目录介绍:
二、代码步骤详细讲解
2.1 支付页面样式
(1)数字键盘组件:咱们平常买东西扫码支付时,进入输入金额的支付页面,会看到下面有数字键盘,直接点击数字即可输入金额,而不是使用手机自带的键盘弹起,这里的键盘专门封装一个组件,直接引入到主页面,提升代码的可维护性。
- keyboard.vue
<template>
<view class="keyboard-box">
<view class="left">
<view class="item" @click="typing(1)">1</view>
<view class="item" @click="typing(2)">2</view>
<view class="item" @click="typing(3)">3</view>
<view class="item" @click="typing(4)">4</view>
<view class="item" @click="typing(5)">5</view>
<view class="item" @click="typing(6)">6</view>
<view class="item" @click="typing(7)">7</view>
<view class="item" @click="typing(8)">8</view>
<view class="item" @click="typing(9)">9</view>
<view class="item zero" @click="typing(0)">0</view>
<view class="item point" @click="typing('.')">.</view>
</view>
<view class="right">
<view class="del" @click="typing('')">
<image src="../static/img/payment/del_icon.png" mode="widthFix"></image>
</view>
<view class="confirm" :style="{backgroundColor: userAgentCurrent()==='wechat' ? '#09BA08' : userAgentCurrent()==='alipay' ? '#00AAEE' : '#266F9C', opacity: opacity}" @click="confirm">
<image :src="userAgentCurrent()==='wechat' ? wechatSrc : userAgentCurrent()==='alipay' ? alipaySrc : yunpaySrc" mode="widthFix"></image>
<text>支付</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'keyboard',
props: {
value: '',
inter: {
default: 5
},
decimal: {
default: 2
},
opacity: {
default: 1
}
},
data() {
return {
/*value 在组件中的值*/
val: '',
aIllegal: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0..', '.'],
wechatSrc: require('../static/img/payment/wx_icon.png'),
alipaySrc: require('../static/img/payment/Alipay_icon.png'),
yunpaySrc: require('../static/img/payment/Yun_icon.png')
}
},
created() {
},
methods: {
confirm(){
this.$emit('confirm')
},
/*输入*/
typing(value) {
/*如果是点击删除*/
if (value === '') {
this.del()
}
/*保存旧的值*/
let oldValue = this.val
/*获取新的值*/
this.val = this.val + value
/*检验新值, 如果没有通过检测, 恢复值*/
if (!this.passCheck(this.val)) {
this.val = oldValue
return
}
/*为了让外界同步输入, 需要发送事件*/
this.notify()
},
/*判读是否需要加0*/
toCompletion() {
let list = this.value.split('.')
if (typeof list[1] === 'undefined') {
if (this.val !== '') {
this.val = this.val + '.'
this.completion(this.decimal)
}
} else {
if (list[1].length < this.decimal) {
this.completion(this.decimal - list[1].length)
}
}
},
completion(len) {
let v = ''
for (let i = 0; i < len; i++) {
v = v + '0'
}
this.val = this.val + v
},
notify() {
this.$u.vuex('$money',this.val)
},
del() {
/*删除值并不会触发值的校验, 所以需要手动再触发一次*/
this.val = this.val.slice(0, -1)
this.notify()
},
passCheck(val) {
/*验证规则*/
let aRules = [this.illegalInput, this.illegalValue, this.accuracy]
return aRules.every(item => {
return item(val)
})
},
illegalInput(val) {
if (this.aIllegal.indexOf(val) > -1) {
return false
}
return true
},
/*非法值*/
illegalValue(val) {
if (parseFloat(val) != val) {
return false
}
return true
},
/*验证精度*/
accuracy(val) {
let v = val.split('.')
if (v[0].length > this.inter) {
return false
}
if (v[1] && v[1].length > this.decimal) {
return false
}
return true
}
}
}
</script>
<style scoped lang="scss">
.keyboard-box {
display: flex;
justify-content: space-between;
position: fixed;
bottom: 0;
width: 100%;
background-color: #eee;
.left {
display: flex;
flex-wrap:wrap;
width: 75%;
.item {
width: 33.33%;
height: 120rpx;
line-height: 120rpx;
text-align: center;
font-size: 52rpx;
background-color: #fff;
border-radius: 10rpx;
border: 6rpx solid #F1F1F1;
box-sizing: border-box;
&:active{
background-color: #f2f2f2;
}
}
.zero {
width: 66.66%;
text-align: center;
}
}
.right {
width: 25%;
.del {
display: flex;
align-items: center;
justify-content: center;
height: 120rpx;
border-radius: 10rpx;
border: 6rpx solid #F1F1F1;
box-sizing: border-box;
background-color: #fff;
image{
width: 52rpx;
height: 36rpx;
}
}
.confirm {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 360rpx;
// background-color: #09BA08;
color: #fff;
font-size: 40rpx;
border-radius: 10rpx;
border: 6rpx solid #F1F1F1;
box-sizing: border-box;
&:active {
opacity: .3;
}
image{
width: 96rpx;
height: 78rpx;
}
text{
padding-top: 12rpx;
}
}
}
}
</style>
(2)主页面:把数字键盘引入到主页面,以及主页面其他的样式设计,这个页面可根据自己的需求重新设计样式。
- payment/index.vue
<template>
<view class="content">
<view class="card">
<view class="title_bg">
<image src="../../static/img/payment/home_icon.png" mode="widthFix"></image>
<view class="title">
<text>{{ $merchantName }}</text>
<text>{{ $merchantName }}</text>
</view>
</view>
<view class="title2">金额</view>
<view class="amount_input">
<view class="cursor" v-if="!$money"></view>
<view class="input_num" v-if="$money">{{ $money }}</view>
<view class="input" v-else>请输入金额</view>
<view class="dot">¥</view>
</view>
<view class="remark" @click="getRemark">添加付款备注</view>
</view>
<text class="remark_txt" v-if="remarkTxt">{{remarkTxt}}</text>
<view class="tips">
请核对以上金额、收款商户等信息与交易一致;经您确认支付的款 项,支付机构无法追回亦无赔偿义务。
</view>
<keyboard @confirm="submit" ref="keyboard" :opacity="opacity"></keyboard>
<!-- 备注弹框 -->
<uni-popup ref="popup" type="center" :animation="false">
<view class="remark_pop">
<view class="title">请填写付款说明</view>
<view class="textarea_con">
<textarea class="textarea" maxlength="30" v-model="remark" placeholder="请输入付款说明" placeholder-style="color: #a0a0a0;font-size: 14px;"></textarea>
</view>
<view class="btn">
<button class="cancel_btn" @click="cancel">取消</button>
<button class="confirm_btn" @click="confirm">确定</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import keyboard from '@/components/keyboard.vue'
export default {
data() {
return {
remark: '',
remarkTxt: '',
opacity: 1,
}
},
components: {
keyboard
},
methods: {
confirm(){
this.$refs.popup.close()
this.remarkTxt = this.remark
},
cancel(){
this.$refs.popup.close()
},
getRemark(){
this.remark = ''
this.$refs.popup.open()
},
}
}
</script>
<style lang="scss">
page {
background-color: #F5F6F8;
}
.content {
padding-top: 24rpx;
.card{
width: 94%;
height: 422rpx;
background: #FFFFFF;
border-radius: 8rpx 8rpx 8rpx 8rpx;
display: flex;
flex-direction: column;
.title_bg{
width: 100%;
height: 150rpx;
background-image: url('@/static/img/payment/title_bg.png');
background-repeat: no-repeat;
background-size: 100% 100%;
display: flex;
align-items: center;
padding-left: 28rpx;
image{
width: 68rpx;
height: 68rpx;
}
.title{
display: flex;
flex-direction: column;
justify-content: space-evenly;
padding-left: 24rpx;
text:first-child{
font-weight: 800;
font-size: 32rpx;
color: #232323;
}
text:last-child{
font-weight: 400;
font-size: 22rpx;
color: #666666;
padding-top: 12rpx;
}
}
}
.title2{
font-weight: 500;
font-size: 32rpx;
color: #232323;
padding: 24rpx 0 20rpx 28rpx;
}
.amount_input{
height: 112rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 42rpx 0 28rpx;
.cursor {
width: 4rpx;
height: 50rpx;
background-color: #5E79CB;
margin-right: 10rpx;
animation: flicker 1.2s infinite;
@keyframes flicker {
0%,
100% {
opacity: 0;
}
50% {
opacity: 1;
}
}
}
.input{
flex: 1;
font-weight: 500;
font-size: 40rpx;
color: #999999;
}
.input_num{
flex: 1;
font-weight: 500;
font-size: 40rpx;
color: #232323;
}
.dot{
width: 82rpx;
height: 42rpx;
text-align: left;
font-weight: 400;
font-size: 32rpx;
color: #999999;
}
}
.remark{
font-weight: 500;
font-size: 26rpx;
color: #5E79CB;
text-align: right;
padding-right: 42rpx;
}
}
.remark_txt{
width: 92%;
font-weight: 5400;
font-size: 22rpx;
color: #a4a4a4;
line-height: 38rpx;
text-align: left;
margin-top: 20rpx;
}
.tips{
width: 92%;
font-weight: 500;
font-size: 24rpx;
color: #999999;
line-height: 38rpx;
text-align: left;
margin-top: 20rpx;
}
.remark_pop{
width: 620rpx;
height: 460rpx;
background-color: #fff;
border-radius: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
.title{
line-height: 100rpx;
font-size: 28rpx;
font-weight: 700;
}
.textarea_con{
width: 90%;
margin: 0 auto;
height: 200rpx;
border: 2rpx solid #c4c4c4;
border-radius: 10rpx;
padding: 32rpx;
.textarea{
width: 100%;
height: 100%;
}
}
.btn{
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 100rpx;
margin-top: 32rpx;
button{
width: 50%;
height: 100rpx;
line-height: 100rpx;
text-align: center;
font-size: 28rpx;
font-weight: 500;
background-color: #fff;
border-radius: none;
}
.cancel_btn{
color: #373737;
}
.confirm_btn{
color: #5E79CB;
}
}
}
}
</style>
2.2 获取授权code
2.2.1 判断支付环境
:首次进入页面,在入口页面可判断当前环境,如果是微信内部打开,则调用微信授权登录,如果是支付宝内部打开,则调用支付宝授权。
- 新建 userAgent.js
export const userAgentCurrent = function (){
// 获取用户代理字符串
const userAgent = navigator.userAgent.toLowerCase();
// 判断是否在微信客户端
if (userAgent.indexOf('micromessenger') !== -1) {
console.log('当前环境在微信客户端');
return 'wechat'
}
// 判断是否在支付宝客户端
else if (userAgent.indexOf('alipay') !== -1) {
console.log('当前环境在支付宝客户端');
return 'alipay'
}
// 判断是否在云闪付客户端
else if (userAgent.indexOf('mybank') !== -1) {
console.log('当前环境在云闪付客户端');
return 'yunpay'
}
// 其他情况
else {
console.log('当前环境在其他客户端');
return 'wechat'
}
}
- 引入到入口文件 App.vue
<script>
import { wxAuthorize } from '@/utils/wxauth.js'
import { aliAuthorize } from '@/utils/AlipayAuth.js'
export default {
onLaunch: function() {
console.log('App Launch')
switch (this.userAgentCurrent()){
case "wechat":
wxAuthorize() // 微信网页授权
break
case "alipay":
aliAuthorize() // 支付宝授权
break
case "yunpay":
console.log('当前是浏览器/云闪付')
break
}
},
}
</script>
2.2.2 微信授权
注:我这里微信授权的时候有两种情况,其中一个厂家不需要我们获取微信的code,用厂家自己获取到的code进行授权即可,另外一个厂家需要自己去微信官方获取code进行授权,小伙伴们可根据自己的情况改写:微信H5授权
- wxauth.js
// 微信授权-openId
import { baseUrl } from "../common/config";
// 微信公众号授权
export const wxAuthorize = function () {
let link = window.location.href;
let params = getUrlParams(link); // 地址解析
console.log('params', params);
if (params.openid && params.appid && params.merchName) { // 富有获取openid
uni.$u.vuex('$openId', params.openid)
uni.$u.vuex('$wxAppId', params.appid)
uni.$u.vuex('$merchantName', params.merchName)
let sn = getUrlSn(link)
uni.$u.vuex('$termSn', sn)
}else if (params.code) { // hk-微信授权获取jscode
let sn = getUrlSn(link)
uni.$u.vuex('$termSn', sn)
if (sn) {
// 调用后台接口,授权
uni.request({
url: `${baseUrl}/order/initiateTrans/getOpenid`, // 后端获取openid
method: 'POST',
data: {
transType: 1, //交易类型 1微信 2支付宝
termSn: sn, //终端号
code: params.code //支付宝授权码
},
success: (response) => {
const res = response.data
if(res.code === 200){
if(res.data.openId){
// 海科
uni.$u.vuex('$openId', res.data.openId)
uni.$u.vuex('$merchantName', res.data.merchName)
}else if(res.data.redirectUri){
// 富有
window.location.href = res.data.redirectUri
}
}else if(res.msg){
uni.showToast({
title: res.msg,
icon: 'none'
})
}
},
fail: (err) => {
console.log(err, 'err');
uni.showToast({
title: err.errMsg,
icon: 'none'
})
}
});
}
} else {
let appid = 'xxx自己的微信公众平台的appid';
let uri = encodeURIComponent(link);
let authURL = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${uri}&response_type=code&scope=snsapi_base&state=123#wechat_redirect`;
window.location.href = authURL;
}
}
// 解析url
function getUrlParams(link) {
let decodeLink = decodeURIComponent(link)
const params = {};
const regex = /[?&]+([^=&]+)=([^&]*)/g;
let match;
while ((match = regex.exec(decodeLink)) !== null) {
params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
}
for (let key in params) {
if (params.hasOwnProperty(key)) {
// 检查值是否以#/结尾
if (params[key].endsWith('#/')) {
// 如果是,去掉#/
params[key] = params[key].slice(0, -2);
}
}
}
return params;
}
// 获取sn
function getUrlSn(url){
// 使用URL对象解析URL
var urlObj = new URL(url);
// 获取路径名部分
var pathname = urlObj.pathname;
// 使用字符串方法提取所需部分
var sn = pathname.split('/')[pathname.split('/').length - 1];
return sn;
}
2.2.3 支付宝授权
注:aliPayCode.js 这个文件是个附件,会放到后面。【其实根据官方文档写不需要用到这个js,但是我用官方写法没有生效,遂用这个】
- AlipayAuth.js
// 支付宝授权-openId
import store from "../store";
import { baseUrl } from "../common/config";
import { getAuthCode } from './aliPayCode.js'
// 支付宝授权
export const aliAuthorize = function () {
let link = window.location.href;
let sn = getUrlSn(link)
uni.$u.vuex('$termSn', sn)
getAuthCode ({
appId : '2021xxxxxxxx9508', // 自己的支付宝开放平台appid
scopes : ['auth_base'],
},function(res){
let authCode = res.authCode
if(store.state.$termSn && authCode){
getOpenidByAli(store.state.$termSn, authCode)
}
});
}
function getOpenidByAli(sn, authCode){
// 调用后台接口,授权
uni.request({
url: `${baseUrl}/order/initiateTrans/getOpenid`, // 后端获取openid
method: 'POST',
data: {
transType: 2, //交易类型
termSn: sn, //终端号
code: authCode //支付宝授权码
},
success: (response) => {
const res = response.data
console.log(res, 'res');
if(res.code === 200){
// 存储起来openid
uni.$u.vuex('$merchantName', res.data.merchName)
uni.$u.vuex('$openId', res.data.openId)
}else if(res.msg){
uni.showToast({
title: res.msg,
icon: 'none'
})
}
},
fail: (err) => {
console.log(err, 'err');
uni.showToast({
title: err.msg,
icon: 'none'
})
}
});
}
// 获取sn
function getUrlSn(url){
// 使用URL对象解析URL
var urlObj = new URL(url);
// 获取路径名部分
var pathname = urlObj.pathname;
// 使用字符串方法提取所需部分
var sn = pathname.split('/')[pathname.split('/').length - 1];
return sn;
}
2.3 微信支付
前提:注意!需要提前开通微信的 JSAPI支付哦 !开通步骤参考官方:微信支付
/* 微信支付 */
wxPay(){
// 获取微信支付参数-预下单
uni.request({
url: `${baseUrl}/order/initiateTrans/preCreateOrder`, // 后端获取openid
method: 'POST',
data: {
termSn: this.$termSn, //终端号
transAmount: parseFloat(this.$money) * 100, //交易金额(分)
transType: 1, //交易类型 1微信 2支付宝
remark: this.remark, //备注
appid: this.$wxAppId,
openId: this.$openId //openid
},
success: (response) => {
uni.hideLoading()
const res = response.data
console.log(res, 'res');
this.opacity = 1
if(res.code === 200){
this.wxPayDataTemp = JSON.stringify(res.data)
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', this.onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', this.onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', this.onBridgeReady);
}
}else{
this.onBridgeReady();
}
}else if(res.msg){
uni.showToast({
title: res.msg,
icon: 'none'
})
}
},
fail: (err) => {
uni.hideLoading()
console.log(err, 'err');
uni.showToast({
title: err.msg,
icon: 'none'
})
}
});
},
// 微信jsapi支付
onBridgeReady(){
let data = JSON.parse(this.wxPayDataTemp)
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": data.sdkAppid, //公众号ID,由商户传入
"timeStamp": data.sdkTimestamp, //时间戳,自1970年以来的秒数
"nonceStr": data.sdkNoncestr, //随机串
"package": data.sdkPackage,
"signType": data.sdkSigntype, //微信签名方式:
"paySign": data.sdkPaysign //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ){
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
uni.showToast({
title: '支付成功',
icon: 'none'
})
this.$u.vuex('$money', null)
}else if(res.err_msg == 'get_brand_wcpay_request:cancel'){
uni.showToast({
title: '支付取消',
icon: 'none'
})
}else{
uni.showToast({
title: '支付失败',
icon: 'none'
})
}
}
);
},
2.4 支付宝支付
前提:注意!需要提前申请支付账号,获取到appid,才能调用支付,详细步骤查看官方接入准备:支付宝支付 H5开放文档
// 支付宝预下单
aliPay(){
// 调用后台接口,授权
uni.request({
url: `${baseUrl}/order/initiateTrans/preCreateOrder`, // 后端获取openid
method: 'POST',
data: {
termSn: this.$termSn, //终端号
transAmount: parseFloat(this.$money) * 100, //交易金额(分)
transType: 2, //交易类型 1微信 2支付宝
remark: this.remark, //备注
openId: this.$openId //openid
},
success: (response) => {
uni.hideLoading()
const res = response.data
console.log(res, 'res');
if(res.code === 200){
this.tradeNO = res.data.reservedTransactionId
this.tradePay() // 调支付宝支付-传入订单号
}else if(res.msg){
this.opacity = 1
uni.showToast({
title: res.msg,
icon: 'none'
})
}
},
fail: (err) => {
uni.hideLoading()
this.opacity = 1
console.log(err, 'err');
uni.showToast({
title: err.msg,
icon: 'none'
})
}
});
},
// 支付宝jsapi支付
tradePay() {
this.opacity = 1
if (window.AlipayJSBridge) {
AlipayJSBridge.call("tradePay", {
tradeNO: this.tradeNO
}, function (data) {
if ("9000" == data.resultCode) {
uni.showToast({
title: '支付成功',
icon: 'none'
})
this.$u.vuex('$money', null)
}else if ("6001" == data.resultCode) {
uni.showToast({
title: '支付取消',
icon: 'none'
})
}else{
uni.showToast({
title: '支付失败',
icon: 'none'
})
}
});
}else {
uni.showToast({
title: '请到支付宝打开',
icon: 'none'
})
}
},
三、全部代码
3.1 附上完整代码
- pages/payment/index.vue
<template>
<view class="content">
<view class="card">
<view class="title_bg">
<image src="../../static/img/payment/home_icon.png" mode="widthFix"></image>
<view class="title">
<text>{{ $merchantName }}</text>
<text>{{ $merchantName }}</text>
</view>
</view>
<view class="title2">金额</view>
<view class="amount_input">
<view class="cursor" v-if="!$money"></view>
<view class="input_num" v-if="$money">{{ $money }}</view>
<view class="input" v-else>请输入金额</view>
<view class="dot">¥</view>
</view>
<view class="remark" @click="getRemark">添加付款备注</view>
</view>
<text class="remark_txt" v-if="remarkTxt">{{remarkTxt}}</text>
<view class="tips">
请核对以上金额、收款商户等信息与交易一致;经您确认支付的款 项,支付机构无法追回亦无赔偿义务。
</view>
<keyboard @confirm="submit" ref="keyboard" :opacity="opacity"></keyboard>
<!-- 备注弹框 -->
<uni-popup ref="popup" type="center" :animation="false">
<view class="remark_pop">
<view class="title">请填写付款说明</view>
<view class="textarea_con">
<textarea class="textarea" maxlength="30" v-model="remark" placeholder="请输入付款说明" placeholder-style="color: #a0a0a0;font-size: 14px;"></textarea>
</view>
<view class="btn">
<button class="cancel_btn" @click="cancel">取消</button>
<button class="confirm_btn" @click="confirm">确定</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import { baseUrl } from '../../common/config'
import keyboard from '@/components/keyboard.vue'
import { getAuthCode } from '../../utils/aliPayCode.js'
export default {
data() {
return {
remark: '',
remarkTxt: '',
opacity: 1,
tradeNO: null,
wxPayDataTemp: null, // 微信支付数据暂存
}
},
components: {
keyboard
},
methods: {
confirm(){
this.$refs.popup.close()
this.remarkTxt = this.remark
},
cancel(){
this.$refs.popup.close()
},
getRemark(){
this.remark = ''
this.$refs.popup.open()
},
submit() {
if (!this.$money) {
uni.showToast({
title: '请先输入金额',
icon: 'error'
})
return false
}
this.opacity = 0.3
uni.showLoading({
mask: true
})
switch (this.userAgentCurrent()){
case "wechat":
this.wxPay()
break
case "alipay":
this.aliPay()
break
case "yunpay":
this.yunPay()
break
}
},
/*
微信支付
*/
wxPay(){
// 获取微信支付参数-预下单
uni.request({
url: `${baseUrl}/order/initiateTrans/preCreateOrder`, // 后端获取openid
method: 'POST',
data: {
termSn: this.$termSn, //终端号
transAmount: parseFloat(this.$money) * 100, //交易金额(分)
transType: 1, //交易类型 1微信 2支付宝
remark: this.remark, //备注
appid: this.$wxAppId,
openId: this.$openId //openid
},
success: (response) => {
uni.hideLoading()
const res = response.data
console.log(res, 'res');
this.opacity = 1
if(res.code === 200){
this.wxPayDataTemp = JSON.stringify(res.data)
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', this.onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', this.onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', this.onBridgeReady);
}
}else{
this.onBridgeReady();
}
}else if(res.msg){
uni.showToast({
title: res.msg,
icon: 'none'
})
}
},
fail: (err) => {
uni.hideLoading()
console.log(err, 'err');
uni.showToast({
title: err.msg,
icon: 'none'
})
}
});
},
// 微信jsapi支付
onBridgeReady(){
let data = JSON.parse(this.wxPayDataTemp)
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": data.sdkAppid, //公众号ID,由商户传入
"timeStamp": data.sdkTimestamp, //时间戳,自1970年以来的秒数
"nonceStr": data.sdkNoncestr, //随机串
"package": data.sdkPackage,
"signType": data.sdkSigntype, //微信签名方式:
"paySign": data.sdkPaysign //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ){
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
uni.showToast({
title: '支付成功',
icon: 'none'
})
this.$u.vuex('$money', null)
}else if(res.err_msg == 'get_brand_wcpay_request:cancel'){
uni.showToast({
title: '支付取消',
icon: 'none'
})
}else{
uni.showToast({
title: '支付失败',
icon: 'none'
})
}
}
);
},
/*
支付宝支付
*/
// 支付宝预下单
aliPay(){
// 调用后台接口,授权
uni.request({
url: `${baseUrl}/order/initiateTrans/preCreateOrder`, // 后端获取openid
method: 'POST',
data: {
termSn: this.$termSn, //终端号
transAmount: parseFloat(this.$money) * 100, //交易金额(分)
transType: 2, //交易类型 1微信 2支付宝
remark: this.remark, //备注
openId: this.$openId //openid
},
success: (response) => {
uni.hideLoading()
const res = response.data
console.log(res, 'res');
if(res.code === 200){
this.tradeNO = res.data.reservedTransactionId
this.tradePay() // 调支付宝支付-传入订单号
}else if(res.msg){
this.opacity = 1
uni.showToast({
title: res.msg,
icon: 'none'
})
}
},
fail: (err) => {
uni.hideLoading()
this.opacity = 1
console.log(err, 'err');
uni.showToast({
title: err.msg,
icon: 'none'
})
}
});
},
// 支付宝jsapi支付
tradePay() {
this.opacity = 1
if (window.AlipayJSBridge) {
AlipayJSBridge.call("tradePay", {
tradeNO: this.tradeNO
}, function (data) {
if ("9000" == data.resultCode) {
uni.showToast({
title: '支付成功',
icon: 'none'
})
this.$u.vuex('$money', null)
}else if ("6001" == data.resultCode) {
uni.showToast({
title: '支付取消',
icon: 'none'
})
}else{
uni.showToast({
title: '支付失败',
icon: 'none'
})
}
});
}else {
uni.showToast({
title: '请到支付宝打开',
icon: 'none'
})
}
},
// 云闪付支付
yunPay(){
setTimeout(()=>{
uni.hideLoading()
uni.showToast({
title: '云闪付支付',
icon: 'success',
duration: 3000
})
this.opacity = 1
}, 2000)
},
}
}
</script>
<style lang="scss">
page {
background-color: #F5F6F8;
}
.content {
padding-top: 24rpx;
.card{
width: 94%;
height: 422rpx;
background: #FFFFFF;
border-radius: 8rpx 8rpx 8rpx 8rpx;
display: flex;
flex-direction: column;
.title_bg{
width: 100%;
height: 150rpx;
background-image: url('@/static/img/payment/title_bg.png');
background-repeat: no-repeat;
background-size: 100% 100%;
display: flex;
align-items: center;
padding-left: 28rpx;
image{
width: 68rpx;
height: 68rpx;
}
.title{
display: flex;
flex-direction: column;
justify-content: space-evenly;
padding-left: 24rpx;
text:first-child{
font-weight: 800;
font-size: 32rpx;
color: #232323;
}
text:last-child{
font-weight: 400;
font-size: 22rpx;
color: #666666;
padding-top: 12rpx;
}
}
}
.title2{
font-weight: 500;
font-size: 32rpx;
color: #232323;
padding: 24rpx 0 20rpx 28rpx;
}
.amount_input{
height: 112rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 42rpx 0 28rpx;
.cursor {
width: 4rpx;
height: 50rpx;
background-color: #5E79CB;
margin-right: 10rpx;
animation: flicker 1.2s infinite;
@keyframes flicker {
0%,
100% {
opacity: 0;
}
50% {
opacity: 1;
}
}
}
.input{
flex: 1;
font-weight: 500;
font-size: 40rpx;
color: #999999;
}
.input_num{
flex: 1;
font-weight: 500;
font-size: 40rpx;
color: #232323;
}
.dot{
width: 82rpx;
height: 42rpx;
text-align: left;
font-weight: 400;
font-size: 32rpx;
color: #999999;
}
}
.remark{
font-weight: 500;
font-size: 26rpx;
color: #5E79CB;
text-align: right;
padding-right: 42rpx;
}
}
.remark_txt{
width: 92%;
font-weight: 5400;
font-size: 22rpx;
color: #a4a4a4;
line-height: 38rpx;
text-align: left;
margin-top: 20rpx;
}
.tips{
width: 92%;
font-weight: 500;
font-size: 24rpx;
color: #999999;
line-height: 38rpx;
text-align: left;
margin-top: 20rpx;
}
.remark_pop{
width: 620rpx;
height: 460rpx;
background-color: #fff;
border-radius: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
.title{
line-height: 100rpx;
font-size: 28rpx;
font-weight: 700;
}
.textarea_con{
width: 90%;
margin: 0 auto;
height: 200rpx;
border: 2rpx solid #c4c4c4;
border-radius: 10rpx;
padding: 32rpx;
.textarea{
width: 100%;
height: 100%;
}
}
.btn{
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 100rpx;
margin-top: 32rpx;
button{
width: 50%;
height: 100rpx;
line-height: 100rpx;
text-align: center;
font-size: 28rpx;
font-weight: 500;
background-color: #fff;
border-radius: none;
}
.cancel_btn{
color: #373737;
}
.confirm_btn{
color: #5E79CB;
}
}
}
}
</style>
- main.js
import Vue from "vue"
import App from "./App"
import store from '@/store';
import uView from "uview-ui";
let vuexStore = require("@/store/$u.mixin.js");
import './utils/filters.js'
Vue.config.productionTip = false
App.mpType = "app"
// 区分微信/支付宝环境
import { userAgentCurrent } from '@/utils/userAgent.js'
Vue.prototype.userAgentCurrent = userAgentCurrent
Vue.mixin(vuexStore);
Vue.use(uView);
const app = new Vue({
store,
...App,
})
app.$mount()
- filters.js
import Vue from 'vue'
// 金额保留两位小数
Vue.filter('$money', function(val) {
if (typeof val == 'string') {
val = val.toString().replace(/\$|\,/g, '');
}
if (isNaN(val)) {
val = "0";
}
let sign = (val == (val = Math.abs(val)));
val = Math.floor(val * 100 + 0.50000000001);
let cents = val % 100;
val = Math.floor(val / 100).toString();
if (cents < 10) {
cents = "0" + cents
}
for (var i = 0; i < Math.floor((val.length - (1 + i)) / 3); i++) {
val = val.substring(0, val.length - (4 * i + 3)) + ',' + val.substring(val.length - (4 * i + 3));
}
return (((sign) ? '' : '-') + val + '.' + cents);
})
3.2 上线发布
(1)上线发布时,找到 manifest.json -> Web配置,填写页面标题、路由模式 hash 或 history 皆可,hash 打包出来的链接后面会拼一个 # ,运行的路径可以随便定义一个,避免与其他域名重复。比如定义pay,域名是https://www.abc.com,则打包出来的链接就是 https://www.abc.com/pay/
(2)打包发行:依次点击发行 -> 网站PC Web…然后发行即可。打包之后可以把打包的 /unpackage/dist 文件夹放到域名的服务器上,即可访问改H5页面。
四、页面效果展示
4.1 微信内部打开:
4.1 支付宝内部打开:
五、附件
注:附上支付宝H5授权所需要的 js包和项目内引用到的图片,附件好像在文章顶部展示,大家自行查看哈!
祝大家开心每一天~