- getUserProfile的bindtap问题?
does not have a method "getUserProfile" to handle event "tap". uniapp [uview]:<button class="login-title" bindtap="getUserProfile">获取昵称和头像</button> 无法出发弹窗,显示 does not have a method "getUserProfile" to handle event "tap". 函数妈名字确认是有的 代码: ``` <template> <view class="wrap cell-page"> <view class="user-area" v-show="userInfo.uid != 0"> <u-row justify="space-between" customStyle="margin-bottom: 10px"> <u-col span="7"> <view class="u-page__login"> <view class="login-title">欢迎回来 {{userInfo.phoneNumber}} </view> </view> </u-col> <u-col span="3"> <view class="u-demo-block"> <text class="u-demo-block__title">{{ userInfo.nickname }}</text> <view class="u-demo-block__content"> <view class="u-page__image-item"> <u--image shape="circle" :src="userInfo.avatarUrl" width="80px" height="80px"></u--image> </view> </view> </view> </u-col> </u-row> </view> <view class="login-area" v-show="userInfo.uid == 0"> <!-- #ifdef MP-WEIXIN --> <u-row justify="space-between" customStyle="margin-bottom: 10px"> <u-col span="7"> <view class="u-page__login"> <view> <button class="login-title" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">微信一键登录</button> <view class="login-text">登陆后可享受更多功能</view> </view> </view> </u-col> <u-col span="3"> <view class="u-demo-block"> <text class="u-demo-block__title">昵称</text> <view class="u-demo-block__content"> <view class="u-page__image-item"> <u--image shape="circle" :src="userInfo.avatarUrl" width="80px" height="80px"></u--image> </view> </view> </view> </u-col> </u-row> <!-- #endif --> <!-- #ifndef MP-WEIXIN --> <u-row justify="space-between" customStyle="margin-bottom: 10px"> <u-col span="7"> <view class="u-page__login" @click="openPopup()"> <view> <button class="login-title">登录/注册 2</button> <view class="login-text">登陆后可享受更多功能</view> </view> </view> </u-col> <u-col span="3"></u-col> </u-row> <!-- #endif --> </view> <button class="login-title" bindtap="getUserProfile">获取用户头像和昵称</button> <u-popup :safeAreaInsetBottom="true" :safeAreaInsetTop="true" :mode="popupData.mode" :show="popupShow" :round="popupData.round" :overlay="popupData.overlay" :borderRadius="popupData.borderRadius" :closeable="popupData.closeable" :closeOnClickOverly="popupData.closeOnClickOverly" @close="close" @open="open"> <view class="u-popup-slot" :style="{ width: ['bottom', 'top'].includes(popupData.mode) ? '750rpx' : '90%', marginTop: ['left', 'right'].includes(popupData.mode) ? '480rpx' : '0', }"> <u-row> <u-col span="3"> <view class="u-page__popup_item">手机号</view> </u-col> <u-col span="5"> <view class="u-page__popup_item"> <u--input v-model="userInfo.phoneNumber" type="number" border="none" placeholder="请填写手机号"> </u--input> </view> </u-col> <u-col span="3"></u-col> </u-row> <!-- <u-row> <u-col span="3"> <view class="u-page__popup_item">验证码</view> </u-col> <u-col span="5"> <view class="u-page__popup_item"> <u--input v-model="login_code" border="none" placeholder="请填写验证码"></u--input> </view> </u-col> <u-col span="3"> <view class="u-page__popup_item"> <u-code ref="uCode1" @change="codeChange1" keep-running change-text="倒计时XS" @start="disabled2 = true" @end="disabled2 = false"></u-code> <u-button type="primary" @tap="getCode1" :text="tips1" size="small" :disabled="disabled2"> </u-button> </view> </u-col> </u-row> --> <u-row justify="space-between" customStyle="margin-bottom: 10px"> <view class="u-page__popup_item"> <u-button type="primary" customStyle="margin-top: 10px" text="登录" @click="login_user()"> </u-button> </view> </u-row> </view> </u-popup> <!-- 底部导航栏 --> <view class="u-page__item"> <u-gap height="150"></u-gap> <u-tabbar :value="indexNavValue" @change="name => indexNavValue = name" :fixed="true" :placeholder="true" :safeAreaInsetBottom="true"> <u-tabbar-item text="首页" icon="home" :customStyle="myStyle" @click="goPage('/pages/index/index')"> </u-tabbar-item> <u-tabbar-item text="我的" icon="account" @click="goPage('/pages/user/user')"></u-tabbar-item> </u-tabbar> </view> </view> </template> <script> export default { data() { return { baseUrl: '', userInfo: { uid: 0, unionid: '', nickname: '', avatarUrl: 'https://cdn.uviewui.com/uview/album/1.jpg' }, indexNavValue: 1, myStyle: { 'border-right': '1px solid #ceccca' }, popupShow: false, popupData: { borderRadius: '', round: true, overlay: true, mode: 'center', closeable: true, closeOnClickOverly: true }, title: '显示关闭按钮', iconUrl: 'https://cdn.uviewui.com/uview/demo/popup/showCloseBtn.png', login_code: '', disabled2: false, tips1: '' } }, methods: { onShow() { // #ifdef MP-WEIXIN uni.login({ provider: 'weixin' }) // #endif // 自动登录 this.baseUrl = this.$globals.baseUrl let uid = uni.getStorageSync('uid') if (uid != '') { this.userInfo = Object.assign(this.userInfo, { uid: uid, phoneNumber: uni.getStorageSync('phoneNumber'), nickname: uni.getStorageSync('phoneNumber'), unionid: uni.getStorageSync('phoneNumber'), }) console.log(this.userInfo) } else { console.log('用户本地缓存为空'); } }, codeChange1(text) { this.tips1 = text; }, getCode1() { if (this.$refs.uCode1.canGetCode) { // 模拟向后端请求验证码 uni.showLoading({ title: '正在获取验证码' }) setTimeout(() => { uni.hideLoading() // 这里此提示会被this.start()方法中的提示覆盖 uni.$u.toast('验证码已发送') // 通知验证码组件内部开始倒计时 this.$refs.uCode1.start() }, 2000) } else { uni.$u.toast('倒计时结束后再发送'); } }, close() { this.popupShow = false }, openPopup() { console.log('openPopup') uni.$u.sleep().then(() => { this.popupShow = !this.show }) }, //获取手机号 async getPhoneNumber(e) { let detail = e.detail let that = this uni.login({ provider: 'weixin' }).then((res) => { console.log('uni.login then') console.log('data: ', JSON.stringify(res)) if (res[1].errMsg !== 'login:ok') { console.log('登录失败!' + JSON.stringify(res)) return false } //发起网络请求 let code = res[1].code uni.request({ url: this.baseUrl + 'WxUser/getPhoneNumberLogin', //解密手机号码接口 method: 'POST', data: { "code": code, "encryptedData": detail.encryptedData, "iv": detail.iv } }).then(res => { console.log('data: ', JSON.stringify(res)) let data = res[1]['data']['data'] console.log('getPhoneNumberLogin then 这里获取手机号', data.phoneNumber) uni.setStorageSync('phoneNumber', data.phoneNumber) uni.setStorageSync('nickname', data.nickname) uni.setStorageSync('uid', data.uid) uni.setStorageSync('unionid', data.unionid) let userInfo = { phoneNumber: data.phoneNumber, nickname: data.nickname, uid: data.user_id, unionid: data.unionid, } that.userInfo = userInfo uni.$u.toast('一键登录成功!') }).catch(err => { uni.$u.toast('登录失败,请切换其它登录方式,或重试') }) }) }, //获取用户信息 getUserProfile(e) { console.log('用于完善会员资料') uni.getUserProfile({ desc: '用于完善会员资料', success: (res) => { console.log('用户昵称为:', JSON.stringify(res.userInfo)) that.userInfo.nickname = res.userInfo.nickName that.userInfo.avatarUrl = res.userInfo.avatarUrl that.userInfo.gender = res.userInfo.gender } }) }, //登录注册 async login_user() { if (this.userInfo.phoneNumber.length != 11) { uni.$u.toast('手机号码是11位') return } uni.request({ url: this.baseUrl + 'WxUser/sampleLogin', //解密手机号码接口 method: 'POST', data: { "mobile": this.userInfo.phoneNumber, } }).then(res => { uni.$u.toast('登录成功!') console.log('res: ', JSON.stringify(res)) let data = res[1]['data']['data'] console.log('code: ', res[1]['data'].code, res[1]['data'].code == 1) console.log('data: ', JSON.stringify(data)) if (res[1]['data'].code == 1) { console.log('getPhoneNumberLogin then 这里获取手机号', data.mobile) uni.setStorageSync('phoneNumber', data.mobile) uni.setStorageSync('nickname', data.nickname) uni.setStorageSync('uid', data.user_id) uni.setStorageSync('unionid', data.unionid) let userInfo = { phoneNumber: data.mobile, nickname: data.nickname, uid: data.uid, unionid: data.unionid, } this.userInfo = userInfo uni.$u.toast('登录成功!') this.close() } else { uni.$u.toast('登录失败,请切换其它登录方式,或重试') } }).catch(err => { uni.showToast({ title: '登录失败:' + JSON.stringify(err) }) }) }, goPage(path) { uni.navigateTo({ url: path }) } } } </script>
2021-12-02 - 适合云开发的微信支付v2及v3版Nodejs SDK
接续微信支付APIv3的Nodejs版SDK,在2020这个时间节点,之所以再造一遍微信支付v2(相对于APIv3来说)的轮子,实属是无奈之举,线下交易场景常见的付款码支付及退款功能,官方当下还没有开放出来v3版的,只能借助v2接口来处理;[代码]wechatpay-axios-plugin[代码] 从一开始目标就是为云原生而设计,遂再造一遍轮子,也抽出一些共生方法,为v3而用。 设计思路 此类库核心部件是利用了Axios的transform功能,数据在内部流转过程中,会经过 [代码]transformRequest[代码] 及 [代码]transformResponse[代码] 处理,通过构造两个自定义transformer,完整实现v2版的技术规格要求,从而完成 HTTP 请求/响应 处理。 Transformer.request 方法返回值是个数组,含两个方法 [代码][signer, toXml][代码],字面意思即,对输入数据签名,然后转换成xml; Transformer.response 方法返回值是个数组,含两个方法 [代码][toObject, verifier][代码],字面意思即,返回值做数据转换为对象,然后校验签名; 证书设置 凡是涉及资金变动的接口,均需要商户证书,此实现同时支持 [代码]pem[代码] 及 [代码]p12[代码] 格式的证书,使用方法见随包README: [代码]const {Wechatpay, Formatter: fmt} = require('wechatpay-axios-plugin') const client = Wechatpay.xmlBased({ secret: 'your_merchant_secret_key_string', merchant: { cert: '-----BEGIN CERTIFICATE-----' + '...' + '-----END CERTIFICATE-----', key: '-----BEGIN PRIVATE KEY-----' + '...' + '-----END PRIVATE KEY-----', // or // passphrase: 'your_merchant_id', // pfx: fs.readFileSync('/your/merchant/cert/apiclient_cert.p12'), }, }) [代码] 实力化一个 [代码]client[代码] 的最小参数为 [代码]secret[代码],即所谓的 密钥,字符串形式,32字节长度。 自定义打印日志 按需,如果需要检测类库的数据情况,在实例化完成后,可以加入如下类似两段代码,即可以打印出日志;当然也可以按需把日志输出至文件等,抛砖引玉而已。 [代码]//在格式转换完后,打印日志 client.defaults.transformRequest.push(data => (console.log(data), data)) //在请求返回,先行打印日志 client.defaults.transformResponse.unshift(data => (console.log(data), data)) [代码] 使用示例 实例化对象 [代码]secret[代码] 所对应的商户类型,可以是服务商、普通商户、特约商户,入参按照官方文档,手捋填入即可,以下几个方法,均测试过,正常运转。 申请退款 [代码]client.post('/secapi/pay/refund', { appid: 'wx8888888888888888', mch_id: '1900000109', out_trade_no: '1217752501201407033233368018', out_refund_no: '1217752501201407033233368018', total_fee: 100, refund_fee: 100, refund_fee_type: 'CNY', nonce_str: fmt.nonce(), }).then(res => console.info(res.data)).catch(({response}) => console.error(response)) [代码] [代码]//log输入 { return_code: 'SUCCESS', return_msg: 'OK', appid: 'wx8888888888888888', mch_id: '1365319302', nonce_str: 'X8bpYtUJbPHK0Fyd', sign: '12BDC0390958455875108947AD51D897', result_code: 'SUCCESS', transaction_id: '4200000684202009114087736848', out_trade_no: '1217752501201407033233368018', out_refund_no: '1217752501201407033233368018', refund_id: '50300005642020091102621479983', refund_channel: '', refund_fee: '100', coupon_refund_fee: '0', total_fee: '100', cash_fee: '100', coupon_refund_count: '0', cash_refund_fee: '100' } [代码] 付款码支付 [代码]client.post('/pay/micropay', { appid: 'wx8888888888888888', mch_id: '1900000109', nonce_str: fmt.nonce(), sign_type: 'HMAC-SHA256', body: 'image形象店-深圳腾大-QQ公仔', out_trade_no: '1217752501201407033233368018', total_fee: 888, fee_type: 'CNY', spbill_create_ip: '8.8.8.8', auth_code: '120061098828009406', }).then(res => console.info(res.data)).catch(({response}) => console.error(response)) [代码] 现金红包 [代码]client.post('/mmpaymkttransfers/sendredpack', { nonce_str: fmt.nonce(), mch_billno: '10000098201411111234567890', mch_id: '10000098', wxappid: 'wx8888888888888888', send_name: '鹅企支付', re_openid: 'oxTWIuGaIt6gTKsQRLau2M0yL16E', total_amount: 1000, total_num: 1, wishing: 'HAPPY BIRTHDAY', client_ip: '192.168.0.1', act_name: '回馈活动', remark: '会员回馈活动', scene_id: 'PRODUCT_4', }).then(res => console.info(res.data)).catch(({response}) => console.error(response)) [代码] 企业付款 [代码]client.post('/mmpaymkttransfers/promotion/transfers', { appid: 'wx8888888888888888', mch_id: '1900000109', partner_trade_no: '10000098201411111234567890', openid: 'oxTWIuGaIt6gTKsQRLau2M0yL16E', check_name: 'FORCE_CHECK', re_user_name: '王小王', amount: 10099, desc: '理赔', spbill_create_ip: '192.168.0.1', nonce_str: fmt.nonce(), }).then(res => console.info(res.data)).catch(({response}) => console.error(response)) [代码] 和v3一起用 [代码]const wxpay = new Wechatpay({ mchid: 'your_merchant_id', serial: 'serial_number_of_your_merchant_public_cert', privateKey: '-----BEGIN PRIVATE KEY-----' + '...' + '-----END PRIVATE KEY-----', certs: { 'serial_number': '-----BEGIN CERTIFICATE-----' + '...' + '-----END CERTIFICATE-----', } }) [代码] Native下单 [代码]wxpay.v3.pay.transactions.native .post({/*文档参数放这里就好*/}) .then(({data: {code_url}}) => console.info(code_url)) .catch(({response: {status, statusText, data}}) => console.error(status, statusText, data)) [代码] 查询订单 [代码]wxpay.v3.pay.transactions.id['{transaction_id}'] .withEntities({transaction_id: '1217752501201407033233368018'}) .get({params: {mchid: '1230000109'}}) .then(({data}) => console.info(data)) .catch(({response: {status, statusText, data}}) => console.error(status, statusText, data)) [代码] 关单 [代码]wxpay.v3.pay.transactions.outTradeNo['1217752501201407033233368018'] .post({mchid: '1230000109'}) .then(({status, statusText}) => console.info(status, statusText)) .catch(({response: {status, statusText, data}}) => console.error(status, statusText, data)) [代码] 创建商家券 [代码]wxpay.v3.marketing.busifavor.stocks .post({/*商家券创建条件*/}) .then(({data}) => console.info(data)) .catch(({response: {status, statusText, data}}) => console.error(status, statusText, data)) [代码] 查询用户单张券详情 [代码];(async () => { try { const {data: detail} = await wxpay.v3.marketing.busifavor.users.$openid$.coupons['{coupon_code}'].appids['wx233544546545989'] .withEntities({openid: '2323dfsdf342342', coupon_code: '123446565767'}) .get() console.info(detail) } catch({response: {status, statusText, data}}) { console.error(status, statusText, data) } } [代码] 上传图片 [代码]const FormData = require('form-data') const {createReadStream} = require('fs') const imageMeta = { filename: 'hellowechatpay.png', // easy calculated by the command `sha256sum hellowechatpay.png` on OSX // or by require('wechatpay-axios-plugin').Hash.sha256(filebuffer) sha256: '1a47b1eb40f501457eaeafb1b1417edaddfbe7a4a8f9decec2d330d1b4477fbe', } const imageData = new FormData() imageData.append('meta', JSON.stringify(imageMeta), {contentType: 'application/json'}) imageData.append('file', createReadStream('./hellowechatpay.png')) Wechatpay.client.post('/v3/marketing/favor/media/image-upload', imageData, { meta: imageMeta, headers: imageData.getHeaders() }).then(res => { console.info(res.data.media_url) }).catch(error => { console.error(error) }) [代码] 下载账单并格式化 [代码]const assert = require('assert') const {Hash: {sha1}} = require('wechatpay-axios-plugin') Wechatpay.client.get('/v3/bill/tradebill', { params: { bill_date: '2020-06-01', bill_type: 'ALL', } }).then(({data: {download_url, hash_value}}) => Wechatpay.client.get(download_url, { signed: hash_value, responseType: 'arraybuffer', })).then(res => { assert(sha1(res.data) === res.config.signed, 'verify the SHA1 digest failed.') console.info(fmt.castCsvBill(res.data)) }).catch(error => { console.error(error) }) [代码] 委托营销 [代码](async () => { try { const res = await Wechatpay.client.post(`/v3/marketing/partnerships/build`, { partner: { type, appid }, authorized_data: { business_type, stock_id } }, { headers: { [`Idempotency-Key`]: 12345 } }) console.info(res.data) } catch (error) { console.error(error) } })() [代码] 查询投诉信息并解密 [代码];(async () => { try { const res = await Wechatpay.client.get('/v3/merchant-service/complaints', {params: { limit: 50, offset: 0, begin_date: (new Date(+new Date - 29*86400*1000)).toJSON().slice(0, 10), end_date: (new Date).toJSON().slice(0, 10), }}) // decrypt the `Sensitive Information` res.data.data.map(row => (row.payer_phone = rsa.decrypt(row.payer_phone, merchantPrivateKey), row)) console.info(res.data) } catch({response: {status, statusText, data, headers}, request, config}) { console.error(status, statusText, data) } })() [代码] TODO v2版的[代码]AES-256-ECB/PKCS7Padding[代码]未做封装,这个不难,npm上也有许多优秀的类库可用,暂且先这样。 写到最后 MIT开放源码@npm, github ,可用于企业商业用途。
2020-10-16