HarmonyOS开发:支付集成支付流程
HarmonyOS开发:支付集成支付流程
📌 核心要点:支付是电商最敏感的环节,流程设计要安全可靠,华为支付/微信支付/支付宝集成各有门道,防重复支付和支付结果回调是重中之重。
背景与动机
用户选好了商品、填好了地址、点了"立即支付"——这一刻,钱要从用户口袋到你的账户。这一步出任何问题,要么用户付了钱没收到货,要么你发了货没收着钱。哪个都是事故。
支付为什么难?不是因为对接SDK复杂,而是因为支付是异步的。你发起支付请求,用户跳到支付App完成支付,再跳回你的App——这个过程中什么都可能发生:网络断了、App被杀了、用户关机了、支付结果回调丢了。
你怎么确认用户到底付没付?你不能只看客户端返回的结果——那可以伪造。你必须以服务端的支付回调为准。但如果回调丢了呢?你需要主动查询。如果查询时支付还在处理中呢?你需要轮询。轮询到什么时候?超时了怎么办?
支付集成的核心不是"怎么调SDK",而是怎么保证支付结果的一致性。
核心原理
支付流程的核心是客户端发起→第三方处理→服务端确认的三方协同。客户端只负责发起和展示结果,真正的支付确认必须在服务端完成。
flowchart TD
A[用户点击支付] --> B[客户端创建支付请求]
B --> C[服务端创建预支付订单]
C --> D{支付方式}
D -->|华为支付| E1[调用华为IAP SDK]
D -->|微信支付| E2[调用微信支付SDK]
D -->|支付宝| E3[调用支付宝SDK]
E1 --> F[用户完成支付]
E2 --> F
E3 --> F
F --> G[第三方回调服务端]
F --> H[客户端获取支付结果]
G --> I[服务端验签确认]
I --> J{验签结果}
J -->|成功| K[更新订单状态为已支付]
J -->|失败| L[记录异常日志]
H --> M[客户端轮询订单状态]
M --> N{订单已支付?}
N -->|是| O[展示支付成功]
N -->|否| P[继续轮询/提示用户]
K --> N
classDef client fill:#1565C0,color:#fff,stroke:#0D47A1
classDef server fill:#2E7D32,color:#fff,stroke:#1B5E20
classDef payment fill:#E65100,color:#fff,stroke:#BF360C
classDef result fill:#6A1B9A,color:#fff,stroke:#4A148C
classDef error fill:#C62828,color:#fff,stroke:#B71C1C
class A,B,H,M,N,O,P,client
class C,G,I,J,K,L,server
class D,E1,E2,E3,F,payment
class result
支付安全三原则
- 客户端不信任客户端:客户端的支付结果可以被篡改,必须以服务端回调为准
- 服务端幂等处理:同一个支付可能回调多次,服务端必须幂等(处理一次和多次结果一样)
- 防重复支付:同一个订单不能支付两次,支付前必须检查订单状态
支付方式对比
| 支付方式 | 适用场景 | 集成难度 | 审核要求 |
|---|---|---|---|
| 华为支付 | HarmonyOS应用内购买 | 中 | 需要华为开发者认证 |
| 微信支付 | 国内主流支付方式 | 高 | 需要微信商户号 |
| 支付宝 | 国内主流支付方式 | 高 | 需要支付宝商户号 |
代码实战
基础用法:支付流程管理
先把支付流程的骨架搭好,统一管理不同支付方式。
// PaymentManager.ets — 支付流程管理
// 支付方式枚举
enum PaymentMethod {
HUAWEI_PAY = 'HUAWEI_PAY',
WECHAT_PAY = 'WECHAT_PAY',
ALIPAY = 'ALIPAY',
}
// 支付请求参数
interface PaymentRequest {
orderId: string // 业务订单ID
amount: number // 支付金额(分)
title: string // 商品描述
method: PaymentMethod // 支付方式
extra?: Record<string, string> // 扩展参数
}
// 支付结果
interface PaymentResult {
success: boolean
orderId: string
transactionId?: string // 第三方交易号
errorCode?: string
errorMsg?: string
}
// 支付状态
enum PaymentStatus {
IDLE = 'IDLE', // 空闲
CREATING_ORDER = 'CREATING', // 创建预支付订单中
PAYING = 'PAYING', // 支付中
QUERYING = 'QUERYING', // 查询支付结果中
SUCCESS = 'SUCCESS', // 支付成功
FAILED = 'FAILED', // 支付失败
CANCELLED = 'CANCELLED', // 用户取消
}
class PaymentManager {
private static instance: PaymentManager
private paymentStatus: PaymentStatus = PaymentStatus.IDLE
private currentOrderId: string = ''
private maxRetryCount: number = 5 // 最大轮询次数
private retryInterval: number = 2000 // 轮询间隔(毫秒)
private constructor() {}
static getInstance(): PaymentManager {
if (!PaymentManager.instance) {
PaymentManager.instance = new PaymentManager()
}
return PaymentManager.instance
}
// ========== 发起支付 ==========
async pay(request: PaymentRequest): Promise<PaymentResult> {
// 防重复支付:检查当前是否有正在进行的支付
if (this.paymentStatus === PaymentStatus.PAYING) {
return {
success: false,
orderId: request.orderId,
errorCode: 'PAYMENT_IN_PROGRESS',
errorMsg: '已有支付正在进行中'
}
}
this.currentOrderId = request.orderId
this.paymentStatus = PaymentStatus.CREATING_ORDER
try {
// 1. 服务端创建预支付订单
const prepayInfo = await this.createPrepayOrder(request)
if (!prepayInfo) {
this.paymentStatus = PaymentStatus.FAILED
return {
success: false,
orderId: request.orderId,
errorCode: 'CREATE_ORDER_FAILED',
errorMsg: '创建预支付订单失败'
}
}
// 2. 调用对应支付SDK
this.paymentStatus = PaymentStatus.PAYING
const payResult = await this.invokePaymentSDK(request.method, prepayInfo)
// 3. 查询服务端确认支付结果
this.paymentStatus = PaymentStatus.QUERYING
const confirmedResult = await this.confirmPaymentResult(request.orderId)
this.paymentStatus = confirmedResult.success ? PaymentStatus.SUCCESS : PaymentStatus.FAILED
return confirmedResult
} catch (error) {
this.paymentStatus = PaymentStatus.FAILED
return {
success: false,
orderId: request.orderId,
errorCode: 'PAYMENT_ERROR',
errorMsg: `支付异常: ${JSON.stringify(error)}`
}
}
}
// ========== 创建预支付订单 ==========
private async createPrepayOrder(
request: PaymentRequest
): Promise<Record<string, string> | null> {
try {
// 实际项目:请求服务端接口
// const response = await http.post('/api/payment/prepay', {
// orderId: request.orderId,
// amount: request.amount,
// method: request.method,
// title: request.title
// })
// return response.data
// 模拟返回
return {
prepayId: `prepay_${Date.now()}`,
orderId: request.orderId,
nonceStr: Math.random().toString(36).substring(2),
timestamp: `${Math.floor(Date.now() / 1000)}`
}
} catch (error) {
console.error(`[Payment] 创建预支付订单失败: ${JSON.stringify(error)}`)
return null
}
}
// ========== 调用支付SDK ==========
private async invokePaymentSDK(
method: PaymentMethod,
prepayInfo: Record<string, string>
): Promise<boolean> {
switch (method) {
case PaymentMethod.HUAWEI_PAY:
return await this.invokeHuaweiPay(prepayInfo)
case PaymentMethod.WECHAT_PAY:
return await this.invokeWechatPay(prepayInfo)
case PaymentMethod.ALIPAY:
return await this.invokeAlipay(prepayInfo)
default:
console.error(`[Payment] 不支持的支付方式: ${method}`)
return false
}
}
// ========== 华为支付 ==========
private async invokeHuaweiPay(
prepayInfo: Record<string, string>
): Promise<boolean> {
try {
// 实际项目:调用华为IAP SDK
// import { iap } from '@kit.IAPKit'
// const purchase = await iap.createPurchase(prepayInfo.prepayId)
// return purchase.purchaseState === iap.PurchaseState.PURCHASED
console.info('[Payment] 华为支付调用')
return true // 模拟成功
} catch (error) {
console.error(`[Payment] 华为支付失败: ${JSON.stringify(error)}`)
return false
}
}
// ========== 微信支付 ==========
private async invokeWechatPay(
prepayInfo: Record<string, string>
): Promise<boolean> {
try {
// 实际项目:调用微信支付SDK
// 微信支付需要集成微信Open SDK
// const result = await wxPay.pay({
// appId: prepayInfo.appId,
// partnerId: prepayInfo.partnerId,
// prepayId: prepayInfo.prepayId,
// nonceStr: prepayInfo.nonceStr,
// timeStamp: prepayInfo.timestamp,
// sign: prepayInfo.sign
// })
// return result.errCode === 0
console.info('[Payment] 微信支付调用')
return true
} catch (error) {
console.error(`[Payment] 微信支付失败: ${JSON.stringify(error)}`)
return false
}
}
// ========== 支付宝 ==========
private async invokeAlipay(
prepayInfo: Record<string, string>
): Promise<boolean> {
try {
// 实际项目:调用支付宝SDK
// const result = await alipay.pay(prepayInfo.orderStr)
// return result.resultStatus === '9000'
console.info('[Payment] 支付宝调用')
return true
} catch (error) {
console.error(`[Payment] 支付宝失败: ${JSON.stringify(error)}`)
return false
}
}
// ========== 确认支付结果(轮询服务端) ==========
private async confirmPaymentResult(orderId: string): Promise<PaymentResult> {
for (let i = 0; i < this.maxRetryCount; i++) {
try {
// 查询服务端订单状态
const orderStatus = await this.queryOrderStatus(orderId)
if (orderStatus === 'PAID') {
return {
success: true,
orderId: orderId,
transactionId: `txn_${Date.now()}`
}
}
if (orderStatus === 'FAILED' || orderStatus === 'CANCELLED') {
return {
success: false,
orderId: orderId,
errorCode: orderStatus,
errorMsg: '支付失败或已取消'
}
}
// 订单还在处理中,等待后重试
if (i < this.maxRetryCount - 1) {
await this.delay(this.retryInterval)
}
} catch (error) {
console.warn(`[Payment] 查询支付结果异常(第${i + 1}次): ${JSON.stringify(error)}`)
}
}
// 超过最大重试次数,返回未知状态
return {
success: false,
orderId: orderId,
errorCode: 'TIMEOUT',
errorMsg: '支付结果确认超时,请稍后查看订单状态'
}
}
// ========== 查询订单支付状态 ==========
private async queryOrderStatus(orderId: string): Promise<string> {
// 实际项目:请求服务端接口
// const response = await http.get(`/api/order/${orderId}/status`)
// return response.data.status
// 模拟:第2次查询时返回已支付
return 'PENDING' // 模拟还在处理中
}
// ========== 工具方法 ==========
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
getStatus(): PaymentStatus {
return this.paymentStatus
}
// 重置状态(用于支付完成后清理)
reset(): void {
this.paymentStatus = PaymentStatus.IDLE
this.currentOrderId = ''
}
}
进阶用法:支付页面与防重复
支付页面要处理:支付方式选择、支付状态展示、防重复点击、支付结果处理。
// PaymentPage.ets — 支付页面
import { router } from '@kit.ArkUI'
import { promptAction } from '@kit.ArkUI'
@Entry
@Component
struct PaymentPage {
@State orderId: string = ''
@State amount: number = 0
@State paymentMethods: PaymentMethodInfo[] = []
@State selectedMethod: PaymentMethod = PaymentMethod.HUAWEI_PAY
@State isPaying: boolean = false
@State paymentStatus: PaymentStatus = PaymentStatus.IDLE
@State countdown: number = 0 // 支付倒计时(秒)
private paymentManager: PaymentManager = PaymentManager.getInstance()
private countdownTimer: number = -1
aboutToAppear() {
const params = router.getParams() as Record<string, string | number>
this.orderId = (params?.orderId as string) || ''
this.amount = (params?.amount as number) || 0
this.initPaymentMethods()
this.startCountdown()
}
aboutToDisappear() {
// 清理倒计时
if (this.countdownTimer !== -1) {
clearInterval(this.countdownTimer)
}
}
build() {
Column() {
// 顶部导航
Row() {
Image($r('app.media.ic_back'))
.width(24)
.height(24)
.fillColor('#333333')
.onClick(() => {
if (this.isPaying) {
this.showCancelPaymentDialog()
} else {
router.back()
}
})
Text('收银台')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.margin({ left: 12 })
}
.width('100%')
.height(48)
.padding({ left: 16 })
.backgroundColor(Color.White)
Scroll() {
Column() {
// 支付金额
this.AmountSection()
// 支付倒计时
if (this.countdown > 0) {
Text(`请在 ${Math.floor(this.countdown / 60)}:${(this.countdown % 60).toString().padStart(2, '0')} 内完成支付`)
.fontSize(13)
.fontColor('#FF4444')
.margin({ top: 8 })
}
// 支付方式选择
this.PaymentMethodSection()
// 支付按钮
Button(this.getPayButtonText())
.width('90%')
.height(48)
.fontSize(16)
.fontColor(Color.White)
.backgroundColor(this.isPaying ? '#CCCCCC' : '#FF4444')
.borderRadius(24)
.margin({ top: 32 })
.enabled(!this.isPaying)
.onClick(() => {
this.startPayment()
})
// 安全提示
Row() {
Image($r('app.media.ic_shield'))
.width(14)
.height(14)
.fillColor('#999999')
Text('支付安全由银联保障')
.fontSize(11)
.fontColor('#999999')
.margin({ left: 4 })
}
.margin({ top: 16 })
}
.padding(16)
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
// ========== 支付金额 ==========
@Builder
AmountSection() {
Column() {
Text('支付金额')
.fontSize(14)
.fontColor('#999999')
Row() {
Text('¥')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Text((this.amount / 100).toFixed(2))
.fontSize(36)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
}
.margin({ top: 8 })
}
.width('100%')
.padding(24)
.backgroundColor(Color.White)
.borderRadius(8)
.alignItems(HorizontalAlign.Center)
}
// ========== 支付方式选择 ==========
@Builder
PaymentMethodSection() {
Column() {
Text('选择支付方式')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
.width('100%')
.margin({ bottom: 12 })
ForEach(this.paymentMethods, (method: PaymentMethodInfo) => {
Row() {
Image(method.icon)
.width(32)
.height(32)
.objectFit(ImageFit.Cover)
Column() {
Text(method.name)
.fontSize(15)
.fontColor('#333333')
if (method.desc) {
Text(method.desc)
.fontSize(12)
.fontColor('#999999')
.margin({ top: 2 })
}
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
Blank()
Radio()
.checked(this.selectedMethod === method.type)
.onChange((checked: boolean) => {
if (checked) {
this.selectedMethod = method.type
}
})
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor(Color.White)
.borderRadius(8)
.margin({ bottom: 8 })
.onClick(() => {
this.selectedMethod = method.type
})
}, (method: PaymentMethodInfo) => method.type)
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
.margin({ top: 12 })
.alignItems(HorizontalAlign.Start)
}
// ========== 发起支付 ==========
async startPayment() {
if (this.isPaying) return // 防重复点击
this.isPaying = true
this.paymentStatus = PaymentStatus.PAYING
try {
const result = await this.paymentManager.pay({
orderId: this.orderId,
amount: this.amount,
title: '商品支付',
method: this.selectedMethod
})
this.isPaying = false
this.paymentStatus = result.success ? PaymentStatus.SUCCESS : PaymentStatus.FAILED
if (result.success) {
// 支付成功,跳转结果页
router.replaceUrl({
url: 'pages/PaymentResultPage',
params: { success: true, orderId: this.orderId }
})
} else {
// 支付失败
promptAction.showToast({
message: result.errorMsg || '支付失败,请重试'
})
}
} catch (error) {
this.isPaying = false
this.paymentStatus = PaymentStatus.FAILED
promptAction.showToast({ message: '支付异常,请稍后重试' })
}
}
// ========== 取消支付确认 ==========
showCancelPaymentDialog() {
// 弹出确认对话框
promptAction.showDialog({
title: '确认离开',
message: '支付正在进行中,确认离开可能导致支付失败',
buttons: [
{ text: '继续支付', color: '#333333' },
{ text: '确认离开', color: '#FF4444' }
]
})
}
getPayButtonText(): string {
if (this.isPaying) return '支付中...'
return `确认支付 ¥${(this.amount / 100).toFixed(2)}`
}
// ========== 初始化支付方式 ==========
initPaymentMethods() {
this.paymentMethods = [
{ type: PaymentMethod.HUAWEI_PAY, name: '华为支付', desc: '推荐', icon: $r('app.media.ic_huawei_pay') },
{ type: PaymentMethod.WECHAT_PAY, name: '微信支付', desc: '', icon: $r('app.media.ic_wechat_pay') },
{ type: PaymentMethod.ALIPAY, name: '支付宝', desc: '', icon: $r('app.media.ic_alipay') },
]
}
// ========== 倒计时 ==========
startCountdown() {
this.countdown = 30 * 60 // 30分钟
this.countdownTimer = setInterval(() => {
this.countdown--
if (this.countdown <= 0) {
clearInterval(this.countdownTimer)
// 超时,返回订单页
promptAction.showToast({ message: '支付超时,订单已取消' })
router.back()
}
}, 1000)
}
}
// 支付方式信息
interface PaymentMethodInfo {
type: PaymentMethod
name: string
desc: string
icon: Resource
}
完整示例:支付结果处理与安全校验
把支付发起、结果回调、安全校验、异常处理串成完整链路。
// PaymentService.ets — 支付服务完整实现
// 支付回调数据(服务端接收)
interface PaymentCallback {
orderId: string // 业务订单号
transactionId: string // 第三方交易号
amount: number // 支付金额
status: string // 支付状态
sign: string // 签名
signType: string // 签名类型
timestamp: string // 回调时间
}
// 支付记录(用于防重复)
interface PaymentRecord {
orderId: string
status: PaymentStatus
transactionId?: string
createTime: number
updateTime: number
retryCount: number
}
class PaymentService {
private static instance: PaymentService
private paymentRecords: Map<string, PaymentRecord> = new Map()
private isProcessing: boolean = false // 全局处理锁
private constructor() {}
static getInstance(): PaymentService {
if (!PaymentService.instance) {
PaymentService.instance = new PaymentService()
}
return PaymentService.instance
}
// ========== 处理支付回调(服务端调用) ==========
async handleCallback(callback: PaymentCallback): Promise<{ success: boolean; message: string }> {
// 1. 验签
if (!this.verifySign(callback)) {
console.error('[PaymentService] 签名验证失败')
return { success: false, message: '签名验证失败' }
}
// 2. 防重复处理
const record = this.paymentRecords.get(callback.orderId)
if (record && record.status === PaymentStatus.SUCCESS) {
console.info('[PaymentService] 订单已处理,忽略重复回调')
return { success: true, message: '已处理' }
}
// 3. 幂等处理:同一笔支付只处理一次
if (this.isProcessing) {
return { success: false, message: '正在处理中,请稍后' }
}
this.isProcessing = true
try {
// 4. 验证金额
if (!this.verifyAmount(callback)) {
return { success: false, message: '金额不匹配' }
}
// 5. 更新支付记录
this.paymentRecords.set(callback.orderId, {
orderId: callback.orderId,
status: callback.status === 'SUCCESS' ? PaymentStatus.SUCCESS : PaymentStatus.FAILED,
transactionId: callback.transactionId,
createTime: record?.createTime || Date.now(),
updateTime: Date.now(),
retryCount: (record?.retryCount || 0) + 1
})
// 6. 通知业务层更新订单状态
if (callback.status === 'SUCCESS') {
await this.notifyOrderPaid(callback.orderId, callback.transactionId)
}
return { success: true, message: '处理成功' }
} finally {
this.isProcessing = false
}
}
// ========== 验签 ==========
private verifySign(callback: PaymentCallback): boolean {
// 实际项目:用第三方支付平台提供的公钥验签
// 1. 将回调参数按规则排序拼接
// 2. 用公钥验证签名
// 3. 确保数据未被篡改
// 简化实现
return callback.sign.length > 0
}
// ========== 验证金额 ==========
private verifyAmount(callback: PaymentCallback): boolean {
// 实际项目:查询订单的应付金额,和回调金额对比
// 防止金额被篡改
return true
}
// ========== 通知订单已支付 ==========
private async notifyOrderPaid(orderId: string, transactionId: string): Promise<void> {
// 实际项目:调用订单服务更新状态
console.info(`[PaymentService] 订单已支付: ${orderId}, 交易号: ${transactionId}`)
}
// ========== 客户端主动查询支付结果 ==========
async queryPaymentResult(orderId: string): Promise<PaymentResult> {
const record = this.paymentRecords.get(orderId)
if (record) {
return {
success: record.status === PaymentStatus.SUCCESS,
orderId: orderId,
transactionId: record.transactionId
}
}
// 本地没有记录,查询服务端
try {
// 实际项目:const response = await http.get(`/api/payment/${orderId}/result`)
return {
success: false,
orderId: orderId,
errorCode: 'NOT_FOUND',
errorMsg: '未找到支付记录'
}
} catch (error) {
return {
success: false,
orderId: orderId,
errorCode: 'QUERY_ERROR',
errorMsg: '查询失败'
}
}
}
// ========== 支付结果页逻辑 ==========
async handlePaymentResultPage(orderId: string): Promise<void> {
// 支付结果页加载时,主动查询服务端确认
const result = await this.queryPaymentResult(orderId)
if (result.success) {
// 确认支付成功,展示成功页面
console.info('[PaymentService] 支付确认成功')
} else if (result.errorCode === 'NOT_FOUND') {
// 支付可能还在处理中,提示用户稍后查看
console.info('[PaymentService] 支付结果未确认,请稍后查看')
} else {
// 支付失败
console.info('[PaymentService] 支付失败')
}
}
}
// ========== 支付结果页 ==========
@Entry
@Component
struct PaymentResultPage {
@State isSuccess: boolean = false
@State orderId: string = ''
@State isConfirming: boolean = true // 正在确认支付结果
aboutToAppear() {
const params = router.getParams() as Record<string, string | boolean>
this.orderId = (params?.orderId as string) || ''
this.isSuccess = (params?.success as boolean) || false
// 即使客户端返回成功,也要服务端确认
if (this.isSuccess) {
this.confirmFromServer()
} else {
this.isConfirming = false
}
}
build() {
Column() {
if (this.isConfirming) {
// 确认中
Column() {
LoadingProgress()
.width(48)
.height(48)
.color('#FF4444')
Text('正在确认支付结果...')
.fontSize(14)
.fontColor('#999999')
.margin({ top: 16 })
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
} else if (this.isSuccess) {
// 支付成功
Column() {
Text('✓')
.fontSize(48)
.fontColor('#2E7D32')
Text('支付成功')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.margin({ top: 16 })
Text(`¥${(0).toFixed(2)}`)
.fontSize(16)
.fontColor('#999999')
.margin({ top: 8 })
Button('查看订单')
.width('80%')
.height(44)
.fontSize(16)
.fontColor('#FF4444')
.backgroundColor('#FFF0F0')
.borderRadius(22)
.margin({ top: 32 })
.onClick(() => {
router.replaceUrl({
url: 'pages/OrderDetailPage',
params: { orderId: this.orderId }
})
})
Button('返回首页')
.width('80%')
.height(44)
.fontSize(16)
.fontColor('#666666')
.backgroundColor('#F5F5F5')
.borderRadius(22)
.margin({ top: 12 })
.onClick(() => {
router.clear()
router.replaceUrl({ url: 'pages/HomePage' })
})
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
} else {
// 支付失败
Column() {
Text('✗')
.fontSize(48)
.fontColor('#FF4444')
Text('支付失败')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.margin({ top: 16 })
Text('请重新下单或联系客服')
.fontSize(14)
.fontColor('#999999')
.margin({ top: 8 })
Button('重新支付')
.width('80%')
.height(44)
.fontSize(16)
.fontColor(Color.White)
.backgroundColor('#FF4444')
.borderRadius(22)
.margin({ top: 32 })
Button('查看订单')
.width('80%')
.height(44)
.fontSize(16)
.fontColor('#666666')
.backgroundColor('#F5F5F5')
.borderRadius(22)
.margin({ top: 12 })
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
}
.width('100%')
.height('100%')
.backgroundColor(Color.White)
}
async confirmFromServer() {
const service = PaymentService.getInstance()
await service.handlePaymentResultPage(this.orderId)
this.isConfirming = false
}
}
踩坑与注意事项
坑1:客户端支付成功但服务端没收到回调
用户付了钱,但服务端没收到支付回调,订单状态还是"待支付"。
解决方案:
- 客户端支付完成后,主动轮询服务端查询支付结果
- 服务端设置主动查询定时任务,对长时间未收到回调的订单主动查询第三方支付平台
- 支付回调接口做好重试机制,第三方一般会重试3-5次
坑2:重复支付
用户点了支付按钮,网络慢,又点了一次——两个支付请求发出去了。
解决方案:
- 客户端:支付按钮点击后立即禁用,加上
isPaying锁 - 服务端:支付前检查订单状态,已支付/支付中的订单不允许再次支付
- 数据库:订单号加唯一索引,同一个订单号只能创建一条支付记录
坑3:支付金额被篡改
黑客抓包修改了支付金额,把1000元的订单改成1元。
解决方案:
- 支付金额必须在服务端计算,不能使用客户端传的金额
- 服务端创建预支付订单时,从数据库读取订单金额,而不是接收客户端参数
- 回调时验证金额是否匹配
坑4:支付回调签名验证
支付回调可能被伪造,不验签就更新订单状态,等于给黑客开了后门。
解决方案:
- 必须验证回调签名,用第三方支付平台提供的公钥
- 验签失败直接拒绝,记录日志
- 回调接口只接受HTTPS请求
坑5:支付超时处理
用户打开支付页面后一直不操作,30分钟后才支付——这时候订单可能已经超时取消了。
解决方案:
- 支付页面加倒计时,超时自动关闭
- 服务端支付回调时检查订单状态,已取消的订单不允许支付成功
- 支付成功但订单已取消时,自动发起退款
HarmonyOS 6适配说明
HarmonyOS 6对支付相关能力做了以下更新:
-
华为IAP Kit增强:IAP Kit新增了订阅支付能力,支持自动续费和订阅管理。之前只支持一次性购买,HarmonyOS 6可以处理按月/按年订阅的场景。
-
安全支付环境:HarmonyOS 6新增了TEE(可信执行环境)支付模块,支付过程中的敏感操作(密码输入、指纹验证)在TEE中执行,即使操作系统被攻破,支付数据也是安全的。
-
支付结果通知优化:IAP Kit的支付结果回调从异步变成了可等待的Promise,不再需要手动轮询。
// HarmonyOS 6 IAP支付
import { iap } from '@kit.IAPKit'
const purchaseResult = await iap.createPurchase({
productId: 'product_001',
developerPayload: JSON.stringify({ orderId: 'order_001' })
})
// 直接获取结果,不需要轮询
if (purchaseResult.purchaseState === iap.PurchaseState.PURCHASED) {
// 支付成功
await iap.acknowledgePurchase(purchaseResult.purchaseToken)
}
-
生物识别支付:HarmonyOS 6的
@kit.UserAuthKit新增了支付级生物识别,指纹/人脸验证的安全等级更高,可以用于支付确认。 -
网络请求安全:
@kit.NetworkKit新增了证书固定(Certificate Pinning)能力,支付请求可以绑定服务端证书,防止中间人攻击。
总结
支付集成的核心不是"怎么调SDK",而是怎么保证支付结果的一致性。客户端发起支付、第三方处理、服务端确认——三方协同,任何一环出问题都可能导致资金异常。
核心记住三点:
- 客户端不信任客户端,支付结果必须以服务端回调为准,客户端只做展示
- 防重复支付是底线,客户端加锁、服务端校验、数据库唯一索引,三重保障
- 金额必须在服务端计算,客户端传的金额不可信,回调时必须验证金额匹配
| 评估维度 | 说明 |
|---|---|
| 学习难度 | ⭐⭐⭐⭐⭐ 支付安全要求极高,异步流程处理复杂 |
| 使用频率 | ⭐⭐⭐⭐⭐ 所有电商App都有支付,是核心功能 |
| 重要程度 | ⭐⭐⭐⭐⭐ 支付出问题就是钱的问题,没有比这更重要的了 |
支付金额被篡改,1块钱买走1000块的东西——这不是bug,这是事故。
- 点赞
- 收藏
- 关注作者
评论(0)