DEV Community

SameX
SameX

Posted on

鸿蒙 Next 实战:构建安全高效的在线支付应用

本文旨在深入探讨华为鸿蒙HarmonyOS Next系统(截止目前 API12)在开发多语言电商平台方面的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。

在数字化金融蓬勃发展的今天,在线支付应用的安全性和稳定性至关重要。本次,我们将基于鸿蒙 Next 系统,深入探讨如何开发一个功能完备的在线支付应用,涵盖从架构设计到核心功能实现的全过程,全面展现鸿蒙 Next 在金融科技领域的强大实力。

一、架构设计:Clean Architecture 的应用

(一)Clean Architecture 概述

Clean Architecture 是一种分层架构模式,将应用划分为多个独立的层次,每个层次都有明确的职责,使得代码结构清晰、易于维护和测试。在在线支付应用中,这种架构能够有效隔离业务逻辑、数据存储和用户界面,提高应用的安全性和稳定性。

(二)表现层(Presentation Layer)

表现层负责与用户进行交互,展示应用的界面和接收用户输入。在在线支付应用中,包括登录界面、银行卡绑定界面、支付密码设置界面、支付订单界面等。我们将使用 ArkUI 框架构建美观、易用的用户界面,确保用户能够流畅地进行操作。例如,通过TextField组件接收用户输入的银行卡号、密码等信息,Button组件实现各种操作按钮的功能。

(三)应用层(Application Layer)

应用层作为表现层和领域层之间的桥梁,负责协调业务逻辑的执行。它接收表现层的用户请求,调用领域层的业务逻辑处理方法,并将处理结果返回给表现层。例如,当用户点击银行卡绑定按钮时,应用层负责验证用户输入的信息格式,然后调用领域层的银行卡绑定方法进行实际的绑定操作。

(四)领域层(Domain Layer)

领域层包含了应用的核心业务逻辑,如银行卡绑定逻辑、支付密码验证逻辑、支付订单处理逻辑等。它不依赖于任何外部框架或技术,只专注于业务规则的实现。例如,在银行卡绑定逻辑中,验证银行卡号的有效性、与银行系统进行交互验证等操作都在领域层完成。

(五)数据层(Data Layer)

数据层负责数据的存储和获取,与数据库、网络服务等进行交互。在在线支付应用中,它负责存储用户的银行卡信息、支付密码等敏感数据(经过加密处理),以及与支付服务器进行通信,获取支付结果等信息。我们将使用鸿蒙 Next 的安全存储 API 和网络通信 API 来实现数据层的功能。

(六)各层之间的依赖关系

Clean Architecture 强调各层之间的单向依赖,表现层依赖于应用层,应用层依赖于领域层,领域层依赖于数据层。这种依赖关系确保了各层的独立性和可测试性,使得应用的维护和扩展更加容易。

二、权限申请与安全机制

(一)权限机制与安全原则

在线支付应用涉及用户的敏感信息和资金交易,安全是首要任务。鸿蒙 Next 的权限机制为我们提供了坚实的安全保障,确保应用在合法、安全的前提下获取用户授权,保护用户数据和资金安全。

(二)用户授权的应用

  1. 读取剪贴板权限(用于粘贴银行卡号) 当用户在银行卡绑定界面选择粘贴银行卡号时,应用需要申请读取剪贴板权限(ohos.permission.READ_PASTEBOARD)。在用户点击粘贴按钮时,应用应动态申请该权限。例如:
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

const clipboardPermission: Permissions = 'ohos.permission.READ_PASTEBOARD';

async function checkClipboardPermissionGrant(): Promise<abilityAccessCtrl.GrantStatus> {
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
  // 获取应用程序的 accessTokenID
  let tokenId: number = 0;
  try {
    let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
    let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
    tokenId = appInfo.accessTokenId;
  } catch (error) {
    const err: BusinessError = error as BusinessError;
    console.error(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`);
  }
  // 校验应用是否被授予权限
  try {
    grantStatus = await atManager.checkAccessToken(tokenId, clipboardPermission);
  } catch (error) {
    const err: BusinessError = error as BusinessError;
    console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
  }
  return grantStatus;
}

async function requestClipboardPermission(): Promise<void> {
  let grantStatus: abilityAccessCtrl.GrantStatus = await checkClipboardPermissionGrant();
  if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
    // 已获得读取剪贴板权限,可以进行粘贴操作
    console.log('已获得读取剪贴板权限,可以继续操作。');
  } else {
    // 申请读取剪贴板权限
    const permissions: Array<Permissions> = [clipboardPermission];
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    atManager.requestPermissionsFromUser(globalThis.context as common.UIAbilityContext, permissions).then((data) => {
      let grantStatus: Array<number> = data.authResults;
      let length: number = grantStatus.length;
      for (let i = 0; i < length; i++) {
        if (grantStatus[i] === 0) {
          // 用户授权,可以继续访问目标操作
          console.log('用户已授权读取剪贴板权限,可以继续操作。');
        } else {
          // 用户拒绝授权,提示用户必须授权才能访问相关功能,并引导用户到系统设置中打开相应权限
          console.log('用户拒绝授权读取剪贴板权限,请前往系统设置中手动授予权限。');
          return;
        }
      }
      // 授权成功
    }).catch((err: BusinessError) => {
      console.error(`Failed to request clipboard permission from user. Code is ${err.code}, message is ${err.message}`);
    });
  }
}

// 在用户点击粘贴银行卡号按钮时调用 requestClipboardPermission() 函数
requestClipboardPermission();
Enter fullscreen mode Exit fullscreen mode

(三)安全控件的使用

  1. 粘贴控件(读取银行卡号) 为了方便用户输入银行卡号,我们使用粘贴控件。当用户在银行卡号输入框点击粘贴按钮时,应用调用粘贴控件读取剪贴板中的银行卡号。例如:
import { pasteboard, BusinessError } from '@kit.BasicServicesKit';
import { Component, State, ClickEvent, PasteButtonOnClickResult } from '@ohos.arkui.extension.component';

@Entry
@Component
struct Index {
  @State cardNumber: string = '';
  build() {
    Row() {
      Column({ space: 10 }) {
        TextField({ placeholder: '请输入银行卡号', text: this.cardNumber })
        PasteButton()
      .padding({ top: 12, bottom: 12, left: 24, right: 24 })
      .onClick((event: ClickEvent, result: PasteButtonOnClickResult) => {
            if (PasteButtonOnClickResult.SUCCESS === result) {
              pasteboard.getSystemPasteboard().getData((err: BusinessError, pasteData: pasteboard.PasteData) => {
                if (err) {
                  console.error(`Failed to get paste data. Code is ${err.code}, message is ${err.message}`);
                  return;
                }
                this.cardNumber = pasteData.getPrimaryText();
              });
            }
          })
      }
  .width('100%')
    }
.height('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

用户点击粘贴控件后,应用将读取到的银行卡号填充到输入框中,方便用户操作。

三、核心功能实现

(一)银行卡绑定功能

  1. 信息验证
    在用户输入银行卡号、开户行、持卡人姓名等信息后,应用在应用层进行初步的信息格式验证,如银行卡号的位数、开户行的有效性等。然后在领域层,通过与银行系统的接口(假设通过网络通信 API 与银行服务器进行交互)进行更严格的验证,确保银行卡信息的准确性和可用性。

  2. 数据加密与存储
    验证通过后,在数据层对银行卡信息进行加密处理,然后存储到本地安全存储区域(使用鸿蒙 Next 的安全存储 API)。加密算法可以选择行业标准的加密算法,如 AES 等,确保用户银行卡信息的安全性。例如:

import { secureStorage } from '@kit.SecurityKit';

// 加密并存储银行卡信息
async function storeBankCardInfo(cardNumber: string, bankName: string, cardholderName: string): Promise<void> {
  let encryptedCardNumber = encryptData(cardNumber);
  let encryptedBankName = encryptData(bankName);
  let encryptedCardholderName = encryptData(cardholderName);
  await secureStorage.put('bank_card_number', encryptedCardNumber);
  await secureStorage.put('bank_name', encryptedBankName);
  await secureStorage.put('cardholder_name', encryptedCardholderName);
}

// 加密数据的函数(假设使用 AES 加密算法)
function encryptData(data: string): string {
  // 这里实现 AES 加密逻辑,返回加密后的字符串
  return encryptedData;
}
Enter fullscreen mode Exit fullscreen mode

(二)支付密码设置功能

  1. 密码强度验证 用户设置支付密码时,在应用层对密码强度进行验证,要求密码包含字母、数字、特殊字符等,且长度符合要求。例如:
function validatePasswordStrength(password: string): boolean {
  // 密码强度验证逻辑,返回 true 或 false
  return password.length >= 8 && /[a-zA-Z]/.test(password) && /\d/.test(password) && /[^\w\s]/.test(password);
}
Enter fullscreen mode Exit fullscreen mode
  1. 密码加密与存储 支付密码同样需要进行加密处理后存储到本地安全存储区域。加密方式与银行卡信息加密类似,确保密码的安全性。例如:
// 加密并存储支付密码
async function storePaymentPassword(password: string): Promise<void> {
  let encryptedPassword = encryptData(password);
  await secureStorage.put('payment_password', encryptedPassword);
}
Enter fullscreen mode Exit fullscreen mode

(三)支付订单功能

  1. 订单信息生成与加密 当用户发起支付订单时,应用在应用层生成订单信息,包括商品信息、金额、订单号等,然后在领域层对订单信息进行加密处理(可以使用与银行卡信息不同的加密密钥)。例如:
import { uuid } from '@kit.UtilKit';

// 生成支付订单
async function createPaymentOrder(productInfo: string, amount: number): Promise<string> {
  let orderId = uuid.generate();
  let orderInfo = {
    orderId,
    productInfo,
    amount
  };
  let encryptedOrderInfo = encryptData(JSON.stringify(orderInfo));
  return encryptedOrderInfo;
}
Enter fullscreen mode Exit fullscreen mode
  1. 发送支付请求与接收结果 使用网络通信 API 将加密后的订单信息发送到支付服务器,支付服务器进行解密和验证后,返回支付结果。应用接收支付结果并在表现层展示给用户。例如:
import { net } from '@kit.NetworkKit';

// 发送支付请求
async function sendPaymentRequest(encryptedOrderInfo: string): Promise<void> {
  let request = net.createHttpRequest();
  request.method = 'POST';
  request.url = 'https://your-payment-server-url/pay';
  request.headers = { 'Content-Type': 'application/json' };
  request.requestBody = encryptedOrderInfo;
  try {
    await request.send();
    if (request.responseCode === 200) {
      let paymentResult = JSON.parse(request.responseData);
      if (paymentResult.success) {
        console.log('支付成功。');
        // 在表现层展示支付成功信息
      } else {
        console.error('支付失败,原因:', paymentResult.errorMessage);
        // 在表现层展示支付失败信息
      }
    } else {
      console.error('支付请求失败,错误码:', request.responseCode);
    }
  } catch (error) {
    console.error('支付请求过程中出错:', error);
  }
}
Enter fullscreen mode Exit fullscreen mode

(四)安全功能

  1. 防止数据泄露
    除了对敏感数据进行加密存储外,应用在运行过程中要确保数据在内存中的安全。例如,在处理银行卡信息和支付密码时,尽量减少数据在内存中的停留时间,使用后及时清除。同时,防止应用被调试或反编译,通过鸿蒙 Next 的安全机制,如代码混淆、加固等手段,增加攻击者获取数据的难度。

  2. 防止恶意攻击
    在网络通信方面,使用安全的网络协议(如 HTTPS),对网络请求进行签名和验证,防止中间人攻击和数据篡改。在应用层和领域层,对用户输入进行严格的验证和过滤,防止 SQL 注入、XSS 攻击等常见的网络攻击手段。例如,在处理用户输入的订单信息时,对特殊字符进行转义处理,防止 SQL 注入攻击。

四、总结与展望

通过本次实战,我们成功构建了一个基于鸿蒙 Next 系统的在线支付应用,涵盖了银行卡绑定、支付密码设置和支付订单等核心功能。在开发过程中,我们深入运用了鸿蒙 Next 的 Clean Architecture 架构、权限机制、安全控件、剪贴板操作和网络操作等关键技术,确保了应用的安全性、稳定性和功能性。

展望未来,随着金融科技的不断发展和用户需求的日益多样化,我们可以进一步优化在线支付应用的功能。例如,引入生物识别技术(如指纹识别、人脸识别),提升支付的便捷性和安全性。同时,加强与更多金融机构和商家的合作,拓展支付场景,为用户提供更加全面、便捷的支付服务。希望本文能够为鸿蒙 Next 同行者在金融应用开发领域提供有益的参考和借鉴,共同推动鸿蒙 Next 在金融科技领域的应用发展。

Image of Docusign

Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs