赞
踩
在写这篇记录的时候,关于ios的支付已经对接的差不多了,下一步就是测试好了直接发版,总共花了好几周的时间,从0到1对于首次做ios支付来说,确实很很多坑。
其实业务层面很简单,甚至比安卓支付还简单,因为支付的整体流程uniapp那边已经提供好了,甚至可以直接套模板。主要坑在于不了解ios内购这套东西,及其细节处理。
该APP使用的是uv-ui组件库,uv-ui 破釜沉舟之兼容vue3+2、nvue、app、h5、小程序等多端基于uni-app和uView2.x的生态框架,支持单独导入,开箱即用,利剑出击。
最重要的就是准备证书、描述文件等环节。现在uniapp开发ios没有这两样东西,不能真机运行。还需要区分测试证书和正式证书,本地真机运行只能使用测试证书,正式证书只能打包上传后台到TestFlight中下载测试或提App Store审核(即时到了这步,内购也只能使用沙箱环境支付,非正式支付。只有App Store审核通过后才能走真实支付,这里建议开发灰度测试功能,后续会详细讲解)。
注册账号及创建APP等准备工作都是产品去做的,所以对此流程可能会有遗漏,所以只记录大概我所了解。
充值页面混入pay.ios.js:
import { Iap, IapTransactionState } from "@/common/js/iap.js" export default { data() { return { title: "iap", loadingIOS: false, disabled: true, productId: "", productList: [], isError: false } }, methods: { async payInitIOS() { uni.showLoading({ mask: true, title: '苹果验证中,请稍等' }); this.isError = false; // 创建实例 this._iap = new Iap({ products: [this.productId] // 苹果开发者中心创建 }) try { // 初始化,获取iap支付通道 await this._iap.init(); // 从苹果服务器获取产品列表 this.productList = await this._iap.getProduct(); this.productList[0].checked = true; this.productId = this.productList[0].productid; // 填充产品列表,启用界面 this.disabled = false; } catch (e) { this.isError = true; uni.showModal({ title: "init", content: e.message, showCancel: false }); } finally { if (this._iap._ready && !this.isError) { this.restore(); } else { uni.hideLoading(); } } }, async restore() { // 检查上次用户已支付且未关闭的订单,可能出现原因:首次绑卡,网络中断等异常 // 在此处检查用户是否登陆 // uni.showLoading({ // mask: true, // title: '苹果验证中,请稍等' // }); try { // 从苹果服务器检查未关闭的订单,可选根据 username 过滤,和调用支付时透传的值一致 const transactions = await this._iap.restoreCompletedTransactions({ username: '' }); if (!transactions.length) { return; } // 开发者业务逻辑,从服务器获取当前用户未完成的订单列表,和本地的比较 // 此处省略 for (let i = 0; i < transactions.length; i++) { const transaction = transactions[i]; switch (transaction.transactionState) { case IapTransactionState.purchased: this.isError = true; // 用户已付款,在此处请求开发者服务器,在服务器端请求苹果服务器验证票据 uni.showLoading({ mask: true, title: '您有一笔订单正在处理中...' }) const order_sn = transaction.payment.username || uni.getStorageSync('IOSPAYORDERID'); if(!order_sn) { this.isError = false; return await this._iap.finishTransaction(transaction); } let result = await this.validatePaymentResult({ product_id: transaction.payment.productid, order_sn: order_sn, receipt: transaction.transactionReceipt, // 不可作为订单唯一标识 transactionIdentifier: transaction.transactionIdentifier }, 0); // 验证通过,交易结束,关闭订单 if (result) { await this._iap.finishTransaction(transaction); } break; case IapTransactionState.failed: this.isError = false; // 关闭未支付的订单 await this._iap.finishTransaction(transaction); break; default: break; } } } catch (e) { // 为了兼容高版本机型在取消订单时候出现的错误,重启后不存在 if(e.code == -100 && e.errMsg.indexOf("本地没有响应要移除的事务")>-1){ this.isError = false; return; } this.isError = true; uni.showModal({ title: `restore${e.errCode}`, content: e.message, showCancel: false }); } finally { if (!this.isError) { this.paymentIOS(); } else { uni.hideLoading(); } } }, async paymentIOS() { if (this.loadingIOS == true) { return; } this.loadingIOS = true; uni.showLoading({ mask: true, title: '支付处理中...' }); try { // 从开发者服务器创建订单 const orderId = await this.createOrder({ productId: this.productId }); // orderId存在本地,防止丢失 uni.setStorageSync('IOSPAYORDERID', orderId); // 请求苹果支付 const transaction = await this._iap.requestPayment({ productid: this.productId, username: orderId, manualFinishTransaction: true, quantity: 1 }); // 在此处请求开发者服务器,在服务器端请求苹果服务器验证票据 await this.validatePaymentResult({ product_id: this.productId, order_sn: transaction.payment.username || orderId, receipt: transaction.transactionReceipt, // 不可作为订单唯一标识 transactionIdentifier: transaction.transactionIdentifier }); // 验证成功后关闭订单 await this._iap.finishTransaction(transaction); // 支付成功 this.paySccuess(); } catch (e) { uni.$uv.toast('支付取消或失败'); } finally { this.loadingIOS = false; uni.hideLoading(); } }, createOrder({ productId }) { return new Promise((resolve, reject) => { this.getOrderInfo({ product_id: productId }).then(res => { resolve(res.order_no); }) }) }, /** * 充值,e.code = 201 或 then返回均代表 处理成功 * @param {Object} data 订单数据 */ validatePaymentResult(data, type = 1) { return new Promise((resolve, reject) => { const fn = (loading = 1) => { this.validatePayment(data, loading).then(res => { // 处理成功 uni.hideLoading(); if (type == 0) { this.successTip(); } resolve(true); }).catch(e => { if (e.code == 201) { //处理成功-订单已更新 this.successTip(); uni.hideLoading(); resolve(true); } else { setTimeout(() => { fn(0); }, 3000) } }) } fn(type == 0 ? 0 : 1); }); }, applePriceChange(e) { this.productId = e.detail.value; }, successTip() { uni.showModal({ title: '温馨提示', content: '您的待处理订单已经处理成功,充值金额已到您的账户余额中,请注查收!', showCancel: false, confirmText: '我知道了' }); } } }
首次开发ios及其内购买项目,遇到坑是正常的,感谢这次机会,至少让我得到了成长,接下来就讲讲整个ios开发遇到了哪些坑:
如果输入沙箱账号和密码支付后未完成后续验证,杀掉APP进程,重启APP进行补单。这时候肯定会检测到未支付的订单,就需要手动关闭订单this._iap.finishTransaction
。但是某些苹果机型一直反馈错误信息:undefined.Payment_appleiap:本地没有响应要移除的事务,https://ask.dcloud.net.cn/article/282
原因分析:在6s机型没有这问题,在7等机型会有这个问题,导致支付流程不能往下执行
解决方案:捕捉到此错误,然后就当正确的逻辑处理,在上述完整示例代码中也有体现
catch (e) {
// 为了兼容高版本机型在取消订单时候出现的错误,重启后不存在
if(e.code == -100 && e.errMsg.indexOf("本地没有响应要移除的事务")>-1){
this.isError = false;
return;
}
<!--后面的逻辑在省略-->
本地只能沙箱账号进行支付测试,怎么办?
解决方案:根据uni官方的回复,灰度测试,设置几个固定账号进行上线后测试,其他账号暂不支持支付。官方回答:https://ask.dcloud.net.cn/question/179074?notification_id-1321394__rf-false__item_id-254173#!answer_254173
输入密码支付过程中,杀掉进程,会造成丢单情况
原因分析:由于网络或者用户主动关闭APP等情况,支付流程断掉,如果根据username进行订单关联,可能有些机型在补单的时候丢失该值,最终导致丢单,这在ios是正常情况
解决方案:
打包上传到iTunes Store,versionCode每次上传都得高于上一次,versionName可以不变
上传到iTunes Store的工具推荐(必须mac):通过 Transporter App 上传 App 的二进制文件
同一套代码,在安卓机没问题。但是在ios发现发烧很严重,打开APP就开始发烧。
原因分析:1. 开始以为是本地基座的问题,其实仔细想想不会是这个问题,uniapp不会这么拉胯;2. 经过代码排查,发现是因为image标签使用了@load,我们APP中恰好有很多图片展示,这应该是ios这边的机制比较耗CPU,导致发热严重。
解决方案:去掉image上的@load,取消图片加载效果,只做图片失败效果
原因分析:代码中使用了uni.preLogin相关,但是ios并未涉及相关模块,所以在ios端屏蔽掉就OK了。
解决方案:参考文档:http://www.codingwhy.com/view/12174.html
Payment_appleiap:Failed to return order information
监听网络状态
uni.onNetworkStatusChange(function (res) {
console.log(res.isConnected);
console.log(res.networkType);
});
这是怎么回事呢?我们公司有个需求就是测试苹果退款功能,这个基本上都是后端实现。本地测试次数太多,然后苹果那边就把IP给禁了,在再怎么调取支付等功能都失败。
解决办法:换IP
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。