HarmonyOS开发:支付集成支付流程

举报
Jack20 发表于 2026/06/26 17:15:58 2026/06/26
【摘要】 HarmonyOS开发:支付集成支付流程📌 核心要点:支付是电商最敏感的环节,流程设计要安全可靠,华为支付/微信支付/支付宝集成各有门道,防重复支付和支付结果回调是重中之重。 背景与动机用户选好了商品、填好了地址、点了"立即支付"——这一刻,钱要从用户口袋到你的账户。这一步出任何问题,要么用户付了钱没收到货,要么你发了货没收着钱。哪个都是事故。支付为什么难?不是因为对接SDK复杂,而是因...

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

支付安全三原则

  1. 客户端不信任客户端:客户端的支付结果可以被篡改,必须以服务端回调为准
  2. 服务端幂等处理:同一个支付可能回调多次,服务端必须幂等(处理一次和多次结果一样)
  3. 防重复支付:同一个订单不能支付两次,支付前必须检查订单状态

支付方式对比

支付方式 适用场景 集成难度 审核要求
华为支付 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对支付相关能力做了以下更新:

  1. 华为IAP Kit增强:IAP Kit新增了订阅支付能力,支持自动续费和订阅管理。之前只支持一次性购买,HarmonyOS 6可以处理按月/按年订阅的场景。

  2. 安全支付环境:HarmonyOS 6新增了TEE(可信执行环境)支付模块,支付过程中的敏感操作(密码输入、指纹验证)在TEE中执行,即使操作系统被攻破,支付数据也是安全的。

  3. 支付结果通知优化: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)
}
  1. 生物识别支付:HarmonyOS 6的@kit.UserAuthKit新增了支付级生物识别,指纹/人脸验证的安全等级更高,可以用于支付确认。

  2. 网络请求安全@kit.NetworkKit新增了证书固定(Certificate Pinning)能力,支付请求可以绑定服务端证书,防止中间人攻击。

总结

支付集成的核心不是"怎么调SDK",而是怎么保证支付结果的一致性。客户端发起支付、第三方处理、服务端确认——三方协同,任何一环出问题都可能导致资金异常。

核心记住三点:

  • 客户端不信任客户端,支付结果必须以服务端回调为准,客户端只做展示
  • 防重复支付是底线,客户端加锁、服务端校验、数据库唯一索引,三重保障
  • 金额必须在服务端计算,客户端传的金额不可信,回调时必须验证金额匹配
评估维度 说明
学习难度 ⭐⭐⭐⭐⭐ 支付安全要求极高,异步流程处理复杂
使用频率 ⭐⭐⭐⭐⭐ 所有电商App都有支付,是核心功能
重要程度 ⭐⭐⭐⭐⭐ 支付出问题就是钱的问题,没有比这更重要的了

支付金额被篡改,1块钱买走1000块的东西——这不是bug,这是事故。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。