某人

此前素未谋面、此后遥遥无期

0%

IOS支付

IOS支付

使用苹果开发者账号登录 App Store Connect,在应用的功能选项卡页面,添加 App 内购项目。注意:

  • 内购项目的各信息需要填写完整,然后保存,此时内购项目的状态应该是准备提交,当提交应用通过审核后,状态则变为已批准
  • 测试时,建议使用测试证书打一个自定义的 iOS 基座进行测试
  • 在应用 TestFight 的选项卡添加 App Store Connect 用户,测试支付时可以使用此用户帐号进行测试

开通, 并签署协议

  1. 登录App Store Connect签署《付费应用程序协议》, 点击【商务】,签署该页面的所有协议,不能有警告
    image
  2. 添加银行账户,CNAPS12位数字(大额支付行号/银行识别代码)
    image
    image
  3. 添加报税表
    image
    image

==(提示:这一步做完需要等待大概1个小时左右,苹果测试时才会生效)==

添加沙盒用户

  1. 用户访问,添加沙盒用户,邮箱可以不存在。
    image
  2. 手机ios 设备登录沙盒用户,设置->开发者-》沙盒APPLE账户
    image

添加内购项目

  1. 添加内购项目
    image
  2. 选择销售范围
    image
  3. App 内购买项目定价
    image
    image
  4. 添加 App Store 本地化版本
    image
  5. 图像(可选)
    image
  6. 填写审核信息
    image
  7. 点击存储,状态为准备提交

证书配置

如果您的应用只接入内购(IAP),那么请您忽略手动创建 Merchant ID 的步骤。您只需要在 Xcode 中开启 In-App Purchase 能力,并在 App Store Connect 中配置商品和银行信息即可。

证书配置完成记得重新生成

image

配置SDK

image

支付流程

  1. 获取支付通道 (uni.getProvider)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import { applePayCallback } from '@/api'
import { RECHARGE_ENV } from '@/constant'
let IAP = null;


// plus.runtime.openURL("https://apps.apple.com/account/billing");
const CODE_MAP = new Map([
[2, '支付流程结束'],
[-1, '参数错误'],
[-2, '用户取消'],
[-3, '此功能不支持'],
[-4, '文件不存在'],
[-5, 'IO错误'],
[-6, '网络错误'],
[-7, '业务参数配置缺失'],
[-8, '客户端未安装'],
[-9, '快捷方式已存在'],
[-10, '授权失败'],
[-100, '业务内部错误']
]);

/**
* 获取iap支付
*/
async function getIpaChannels() {
if(IAP){
restoreOrder();
return IAP;
}
return new Promise((resolve, reject) => {
uni.getProvider({
service: 'payment',
success: (res) => {
const iapChannel = res.providers.find((channel) => {
return (channel.id === 'appleiap')
})
if(iapChannel){
IAP = iapChannel;
// console.log('IAP', IAP)
resolve(iapChannel)
restoreOrder();
} else {
reject('未找到苹果IAP支付通道')
}
},
fail: (e) => {
reject(e)
}
});
});
}
  1. 通过支付通道获取产品列表 (iapChannel.requestProduct)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 获取产品列表
*/
async function getProducts(productIds = []) {
if(!IAP){
uni.showToast({title: 'IPA通道不存在',icon: 'none'})
return
}
return new Promise((resolve, reject) => {
IAP.requestProduct(productIds,(res) => {
resolve(res)
}, (err) => {
reject(err)
})
});
}
  1. 检查是否存在未关闭的订单 (iapChannel.restoreCompletedTransactions, 可选在合适的时机检查)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 检查上次用户已支付且未关闭的订单,可能出现原因:首次绑卡,网络中断等异常
*/
function restoreOrder () {
if(!IAP){
uni.showToast({title: 'IPA通道不存在',icon: 'none'})
return
}
IAP.restoreCompletedTransactions({
manualFinishTransaction: true, // true时不关闭订单
username: '' // 支付商户的名称 可不填
}, (res) => {
// console.log(res, "未关闭的订单");
if (res?.length > 0) {
IAP.finishTransaction(res[0], (reslut) => {
console.log(reslut, '关闭成功');
})
}
}, (err) => {
console.log(err);
})
}
  1. 请求支付,传递产品信息 (uni.requestPayment)
  2. 客户端接收苹果返回的支付票据发送到服务器,在服务器请求苹果服务器验证支付是否有效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* 苹果支付
*/
function applePay(parameter = {
orderInfo,
rechargeOrder,
successCall,
failCall,
}) {
const { orderInfo, successCall, failCall, rechargeOrder } = parameter
uni.showLoading({title: '支付中...', mask: true})
uni.requestPayment({
provider: 'appleiap',
orderInfo,
success: async (res) => {
// console.log('@applePay', res)
// console.log('parameter', parameter)
uni.hideLoading()
const transactionId = res.transactionIdentifier //交易id
const receiptData = res.transactionReceipt //校验体
try {
const res = await applePayCallback({
userId: rechargeOrder?.userId,
transactionId,
receiptData,
iosOrderId: rechargeOrder?.id,
environment: RECHARGE_ENV.sandbox
})
successCall && successCall(res)
} catch (error) {
// console.log('@applePay@error', error)
failCall && failCall(error)
//TODO handle the exception
}
},
fail: (err) => {
// console.log('@applePay', err)
uni.hideLoading()
let errRes = err
if(CODE_MAP.get(err?.code)){
errRes = {
...err,
message: CODE_MAP.get(err?.code)
}
}
failCall && failCall(errRes)
}
})
}

  1. 服务器验证票据有效后在客户端关闭订单 (iapChannel.finishTransaction)

HBuilder 3.5.1 之前因自动关闭订单导致某些情况下丢单的问题

HBuilder 3.5.1 + 增加了手动关闭订单参数 manualFinishTransaction, 在合适的时机调用
HBuilder 3.5.1+ 开始支持通过 uni.getProvider 获取IAP支付通道的方法

注意事项

  1. 相同订单,重复调用 restoreCompletedTransactionstransactionReceipt 会发生变化,并非唯一值
  2. 调用 finishTransaction 关闭订单可能不会立即生效,取决于苹果的服务器
  3. 沙盒环境:一个测试账号相同产品仅能购买一次,重复测试需要清除购买记录或重新添加沙盒测试账号
  4. 沙盒环境:调用 restoreCompletedTransactions 长时间无反应,检查设备登陆的沙箱账号是否正常
  5. 提交App StoreApp 才能开通应用内支付,使用苹果企业账号发布的 App 不能开通使用
  6. 根据 App Store 审核指南条款 3.1.1 要求,虚拟物品交易必须使用应用内支付,实物交易才可使用第三方支付(支付宝、微信等)
  7. 创建 App内购买项目的各项信息需要填写完整,保存后内购项目的状态是准备提交,当提交应用通过审核后,状态则变为已批准
  8. 正式上线前可在 Test Flight 平台添加 App Store Connect 用户,测试支付时可以使用此用户帐号

订单丢失场景

用户没有绑定 AppStore 支付方式,调用 uni.requestPayment() 准备支付,触发失败 fail 回调,errCode=2,用户未绑定支付方式,app内支付流程结束。 系统弹出框引导用户绑定支付方式,此过程将跳转到系统应用 AppStore 进行绑定支付方式,绑定成功同步支付成功,用户成功付款

测试

  1. 使用开发证书打包,可以借助App Uploader生成证书、和测试、和安装

相关链接

  1. 配置 App 内购买项目概述
  2. 苹果应用内支付
  3. 5+API错误代码