文章目录
一、支付宝小程序源码运行
1.开发者工具下载
2.运行小程序
npm run dev:my
其实mpvue兼容的所有平台的项目的运行命令都在package.json的script字段中:
"scripts": {
"dev:wx": "node build/dev-server.js wx",
"start:wx": "npm run dev:wx",
"build:wx": "node build/build.js wx",
"dev:swan": "node build/dev-server.js swan",
"start:swan": "npm run dev:swan",
"build:swan": "node build/build.js swan",
"dev:tt": "node build/dev-server.js tt",
"start:tt": "npm run dev:tt",
"build:tt": "node build/build.js tt",
"dev:my": "node build/dev-server.js my",
"start:my": "npm run dev:my",
"build:my": "node build/build.js my",
"dev": "node build/dev-server.js wx",
"start": "npm run dev",
"build": "node build/build.js wx",
"lint": "eslint --ext .js,.vue src"
},
即默认微信小程序,可指定平台:微信小程序(wx)、百度小程序(swan)、头条小程序(tt)、支付宝小程序(my)
二、支付宝小程序用户授权
打开src\pages\index\index.vue,小程序的加载是从mounted中调用init方法开始的,而在init方法中调用了getSetting方法来判断是否已经具备获取用户信息权限:
...
init() {
showLoading({ title: '正在加载' })
this.getSetting() // 判断是否已经具备获取用户信息权限
}
},
mounted() {
this.init()
}
}
getSetting方法中调用了getSetting接口函数(在src\api\wechat.js):
getSetting() {
this.prepare = false
this.loading = true
const vue = this
// 判断当前小程序是否具备userInfo权限
getSetting(
'userInfo',
(res) => {
console.log('验证成功...', res)
vue.authorized = true
vue.prepare = true
vue.getUserInfo()
},
(res) => {
console.log('验证失败...', res)
vue.authorized = false
vue.prepare = true
hideLoading()
}
)
},
为了做兼容,getSetting接口函数修改为如下:
export function getSetting(authorize, onSuccess, onFail) {
const wx = (res) => {
if (res.authSetting[`scope.${authorize}`]) {
onSuccess(res)
} else {
onFail(res)
}
}
const my = (res) => {
if (res.authSetting[authorize]) {
onSuccess(res)
} else {
onFail(res)
}
}
mpvue.getSetting({
success: (res) => {
adapter({ wx, my }, res)
},
fail: () => {
setError('获取权限失败')
}
})
}
adapter 方法源码:
function adapter(fn, params) {
return fn[mpvuePlatform] && fn[mpvuePlatform](params)
}
主要是因为微信和支付宝小程序接口的调用方式不同,比如有些地方的参数需要以对象的形式传入
除了这个接口其他接口调用都需要做兼容处理,由于篇幅原因这里就不再展示全部代码了
支付宝目前无法拿到用户授权的回调,所以在完成授权之后需要手动刷新页面,否则授权登录页面不会消失
通过官方文档:获取会员基础信息 可知:
- 开发者使用 button 组件 唤起授权框,请添加属性 open-type=“getAuthorize” 用于支持用户授权。这条和微信是类似的,因此做了如下兼容:
<button
class="auth-btn"
@getuserinfo="getUserInfo"
open-type="getUserInfo"
plain="true"
v-if="platform === 'wx'"
>
授权登录
</button>
<button
class="auth-btn"
plain="true"
open-type="getAuthorize"
scope='userInfo'
v-else
>
授权登录
</button>
- 开发者调用 my.getAuthCode 和 alipay.system.oauth.token 接口获取支付宝会员标识(user_id)——能力详情
- 与微信不同的一点是,支付宝允许一旦判断用户授权不存在直接弹出弹窗,因此getSetting方法改造为如下(用户未授权时:微信设置授权状态为false,而支付宝直接拉起授权面板,而不经过按钮点击的一步):
getSetting() {
this.prepare = false
this.loading = true
const vue = this
// 判断当前小程序是否具备userInfo权限
getSetting(
'userInfo',
(res) => {
console.log('验证成功...', res)
vue.authorized = true
vue.prepare = true
vue.getUserInfo()
},
(res) => {
console.log('验证失败...', res)
if (mpvuePlatform === 'wx'){
vue.authorized = false
}
vue.prepare = true
vue.getUserInfo()
hideLoading()
}
)
},
src\api\wechat.js的getUserInfo如下:
export function getUserInfo(onSuccess, onFail) {
const wx = () => {
mpvue.getUserInfo({
success(res) {
const { userInfo } = res
console.log('getUserInfo', userInfo)
onSuccess(userInfo)
},
fail() {
onFail ? onFail() : setError('获取用户信息失败')
}
})
}
const my = () => {
mpvue.getAuthUserInfo({
success(res) {
console.log(res)
res.avatarUrl = res.avatar || res.avatarUrl
delete res.avatar
console.log('getOpenUserInfo', res)
onSuccess(res)
},
fail() {
onFail ? onFail() : setError('获取用户信息失败')
}
})
}
adapter({ wx, my })
}
- 获取用户授权信息需要到支付宝小程序的后台开发管理中手动添加权限:
三、支付宝小程序获取OpenId
1.源码改造
src\pages\index\index.vue的getUserInfo方法调用src\api\wechat.js的getUserInfo方法,并将回调函数传入,成功回调函数中调用了src\api\wechat.js的getUserOpenId方法(这里在import时做了一下转换:getUserOpenId as getOpenId)
getUserInfo() {
const vue = this
const onOpenIdComplete = (vue, openId, userInfo) => {
vue.openId = openId
// 获取首页数据
vue.getHomeData(openId, hideLoading)
// 上报用户信息,注册账号
register(openId, userInfo)
// 判断用户今天是否签到过
vue.getSignState(openId)
}
console.log('getUserInfo...')
getUserInfo(
(userInfo) => {
vue.userInfo = userInfo
setStorageSync('userInfo', userInfo)
const openId = getStorageSync('openId')
console.log('openId', openId)
if (!openId || openId.length === 0) {
getOpenId((openId) => {
onOpenIdComplete(vue, openId, userInfo)
})
} else {
onOpenIdComplete(vue, openId, userInfo)
}
},
(err) => {
console.log('getUserInfo failed', err)
}
)
},
src\api\wechat.js调用wx.getUserInfo或my.getAuthUserInfo
export function getUserInfo(onSuccess, onFail) {
const wx = () => {
mpvue.getUserInfo({
success(res) {
const { userInfo } = res
console.log('getUserInfo', userInfo)
onSuccess(userInfo)
},
fail() {
onFail ? onFail() : setError('获取用户信息失败')
}
})
}
const my = () => {
mpvue.getAuthUserInfo({
success(res) {
console.log(res)
res.avatarUrl = res.avatar || res.avatarUrl
delete res.avatar
console.log('getOpenUserInfo', res)
onSuccess(res)
},
fail() {
onFail ? onFail() : setError('获取用户信息失败')
}
})
}
adapter({ wx, my })
}
- 这里支付宝的接口名称和微信不同,是
getAuthUserInfo
而不是getUserInfo
在src\api\wechat.js的getUserOpenId方法的支付宝处理模块中调用了src\api\index.js的接口请求方法getAlipayOpenId
export function getUserOpenId(cb) {
const wx = () => {
mpvue.login({
success: function(res) {
console.log(res)
if (res.code) {
const appid = 'wx0fad7b50f723dc46'
getOpenId(appid, res.code).then(response => {
if (handleError(response)) {
const openId = response.data.data.openid
const sessionKey = response.data.data.session_key
setStorageSync('openId', openId)
setStorageSync('session_key', sessionKey)
cb && cb(openId)
}
})
} else {
console.log('获取用户登录态失败!' + res.errMsg)
setError('获取用户登录态失败!')
}
},
fail() {
setError('获取openId失败!')
}
})
}
const my = () => {
mpvue.getAuthCode({
scopes: 'auth_user', // 主动授权(弹框):auth_user,静默授权(不弹框):auth_base
success: async (res) => {
console.log('getOpenId', res)
if (res.authCode) {
const code = res.authCode
const appId = '2019060665444521'
const response = await getAlipayOpenId(appId, code)
if (handleError(response)) {
const openId = response.data.data.openid
const sessionKey = response.data.data.session_key
setStorageSync('openId', openId)
setStorageSync('session_key', sessionKey)
cb && cb(openId)
}
} else {
setError('获取openId失败!')
}
},
fail: () => {
setError('获取openId失败!')
}
})
}
adapter({ wx, my })
}
src\api\index.js的接口请求方法getAlipayOpenId,这里的接口是自己服务器上的而不是官方服务器上的,接下来来看服务器端是如何处理的
export function getAlipayOpenId(appId, code) {
return get(`${API_PREFIX}/openId/get/alipay`, { appId, code })
}
2.服务器接口
下载好服务器端的源码后需要准备:
- 支付宝密钥
- 服务器端的数据库配置
- https证书
准备完成之后将https证书引入到app.js中,并在app.js中将“获取支付宝openId”的一步中拿私钥的地方替换为自己的,然后通过node app.js
运行服务器端项目
(1)生成公钥和私钥
开发 openId 获取接口需要生成公钥和私钥,查看:技术文档
(2)填入公钥信息
进入小程序开发者后台,在设置->开发设置->开发信息下填入公钥
(3)服务端 SDK
支付宝小程序获取 openId 需要通过服务端 SDK 开发进行支持,查看:开发文档
(4)Node.js 版本开发
首先需要安装依赖:
npm install alipay-sdk
下面提供核心代码:
const AlipaySdk = require('alipay-sdk').default
app.get('/openId/get/alipay', (req, res) => {
const appId = req.query.appId
const code = req.query.code
if (!appId || !code) {
onFail(res, '获取openId失败')
} else {
const alipaySdk = new AlipaySdk({
appId,
privateKey: fs.readFileSync(appIdMap[appId], 'ascii')
})
alipaySdk.exec('alipay.system.oauth.token', {
grantType: 'authorization_code',
code,
refreshToken: 'token'
}).then(result => {
console.log('alipay', result)
if (result) {
const { alipayUserId, userId, accessToken } = result
onSuccess(res, '获取openId成功', {
openid: `${userId}|${alipayUserId}`,
session_key: accessToken
})
} else {
onFail(res, '获取openId失败')
}
}).catch(err => {
onFail(res, '获取openId失败', err)
})
}
})
这里有两点需要注意:
- appIdMap[appId] 为私钥地址
- 将 userId|alipayUserId 定义为 openId,因为支付宝中没有 openId 的概念
四、支付宝小程序组件定制开发
1.ImageView改造
<template>
<div class="image-view" @click="onClick">
<img
:class="round ? ' round image' : 'image'"
:style="{ height }"
:src="src"
:mode="mode"
:lazy-load="lazyLoad"
@error="onError"
@load="onLoad"
v-show="!isLoading"
>
<img
:class="round ? ' round image' : 'image'"
:style="{ height }"
src="/static/images/loading.jpeg"
:mode="mode"
@error="onPreloadError"
@load="onPreload"
v-show="isLoading || error"
v-if="mpvuePlatform === 'wx'"
>
</div>
</template>
2.Button改造
<template>
<van-button
:size="size"
:type="type"
:round="round"
:custom-class="customClass"
@click="onClick"
v-if="platform === 'wx'"
>
{{text}}
</van-button>
<button
:size="size"
:type="type"
:class="customClass"
:style="{borderRadius: round ? '50px' : '0'}"
@click="onClick"
v-else-if="platform === 'my'"
>
{{text}}
</button>
</template>
<script>
export default {
props: {
customClass: String,
text: String,
color: String,
size: String,
round: {
type: Boolean,
default: false
}
},
data() {
return {
platform: mpvuePlatform
}
},
methods: {
onClick() {
this.$emit('click')
}
}
}
</script>
<style lang="scss" scoped>
</style>
3.Icon改造
<template>
<van-icon
:class="customClass"
:style="customStyle"
:name="name"
:color="color"
:size="size"
v-if="platform === 'wx'"
></van-icon>
<icon
:class="customClass"
:style="customStyle"
:type="name"
:color="color"
:size="size"
v-else-if="platform === 'my'"
/>
</template>
<script>
export default {
props: {
customStyle: Object,
customClass: String,
name: String,
color: String,
size: String
},
data() {
return {
platform: mpvuePlatform
}
}
}
</script>
<style lang="scss" scoped>
</style>
需要改造的一大部分原因是支付宝小程序无法使用UI组件库:vant-weapp,建议使用支付宝自带组件库:基础组件、扩展组件。