DEV Community

SameX
SameX

Posted on

HarmonyOS Next in Practice: Building a Secure and Efficient Online Payment Application

This article aims to deeply explore the technical details of the Huawei HarmonyOS Next system (up to API 12 as of now) in developing multilingual e-commerce platforms, and is summarized based on actual development practices. It mainly serves as a vehicle for technical sharing and communication. Mistakes and omissions are inevitable. Colleagues are welcome to put forward valuable opinions and questions so that we can make progress together. This article is original content, and any form of reprint must indicate the source and the original author.
In today's booming digital finance era, the security and stability of online payment applications are of crucial importance. This time, we will explore in depth how to develop a fully functional online payment application based on the HarmonyOS Next system, covering the whole process from architecture design to the implementation of core functions, fully demonstrating the powerful capabilities of HarmonyOS Next in the field of fintech.

I. Architecture Design: Application of Clean Architecture

(I) Overview of Clean Architecture

Clean Architecture is a layered architecture pattern that divides an application into multiple independent layers, each with clear responsibilities, making the code structure clear, easy to maintain, and test. In an online payment application, this architecture can effectively isolate business logic, data storage, and the user interface, enhancing the security and stability of the application.

(II) Presentation Layer

The Presentation Layer is responsible for interacting with users, displaying the application's interface, and receiving user input. In an online payment application, it includes the login interface, bank card binding interface, payment password setting interface, payment order interface, etc. We will use the ArkUI framework to build a beautiful and user-friendly interface to ensure that users can operate smoothly. For example, the TextField component is used to receive user-inputted bank card numbers, passwords, etc., and the Button component implements the functions of various operation buttons.

(III) Application Layer

The Application Layer acts as a bridge between the Presentation Layer and the Domain Layer, responsible for coordinating the execution of business logic. It receives user requests from the Presentation Layer, calls the business logic processing methods in the Domain Layer, and returns the processing results to the Presentation Layer. For example, when the user clicks the bank card binding button, the Application Layer is responsible for validating the format of the user-inputted information and then calls the bank card binding method in the Domain Layer to perform the actual binding operation.

(IV) Domain Layer

The Domain Layer contains the core business logic of the application, such as bank card binding logic, payment password verification logic, payment order processing logic, etc. It does not depend on any external frameworks or technologies but focuses solely on the implementation of business rules. For example, in the bank card binding logic, operations such as validating the validity of the bank card number and interacting with the bank system for verification are all completed in the Domain Layer.

(V) Data Layer

The Data Layer is responsible for data storage and retrieval, interacting with databases, network services, etc. In an online payment application, it is responsible for storing sensitive data such as users' bank card information and payment passwords (after encryption processing), as well as communicating with the payment server to obtain payment result information. We will use the secure storage API and network communication API of HarmonyOS Next to implement the functions of the Data Layer.

(VI) Dependency Relationships between Layers

Clean Architecture emphasizes the unidirectional dependency between layers. The Presentation Layer depends on the Application Layer, the Application Layer depends on the Domain Layer, and the Domain Layer depends on the Data Layer. This dependency relationship ensures the independence and testability of each layer, making the maintenance and expansion of the application easier.

II. Permission Application and Security Mechanisms

(I) Permission Mechanism and Security Principles

Online payment applications involve users' sensitive information and financial transactions, so security is the top priority. The permission mechanism of HarmonyOS Next provides us with a solid security guarantee, ensuring that the application obtains user authorization under legal and secure conditions, protecting users' data and financial security.

(II) Application of User Authorization

  1. Read Clipboard Permission (for Pasting Bank Card Numbers) When the user selects to paste the bank card number on the bank card binding interface, the application needs to apply for the read clipboard permission (ohos.permission.READ_PASTEBOARD). When the user clicks the paste button, the application should dynamically apply for this permission. For example:
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;
  // Get the accessTokenID of the application
  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}`);
  }
  // Check whether the application has been granted the permission
  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.PERMISION_GRANTED) {
    // Have obtained the read clipboard permission and can perform the paste operation
    console.log('Have obtained the read clipboard permission and can continue the operation.');
  } else {
    // Apply for the read clipboard permission
    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) {
          // User has authorized and can continue to access the target operation
          console.log('User has authorized the read clipboard permission and can continue the operation.');
        } else {
          // User has refused to authorize, prompt the user that authorization is required to access the relevant function, and guide the user to open the corresponding permission in the system settings
          console.log('User has refused to authorize the read clipboard permission, please go to the system settings to manually grant the permission.');
          return;
        }
      }
      // Authorization successful
    }).catch((err: BusinessError) => {
      console.error(`Failed to request clipboard permission from user. Code is ${err.code}, message is ${err.message}`);
    });
  }
}
// Call the requestClipboardPermission() function when the user clicks the paste bank card number button
requestClipboardPermission();
Enter fullscreen mode Exit fullscreen mode

(III) Use of Security Controls

  1. Paste Control (for Reading Bank Card Numbers) To facilitate users' input of bank card numbers, we use the paste control. When the user clicks the paste button in the bank card number input box, the application calls the paste control to read the bank card number from the clipboard. For example:
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: 'Please enter bank card number', 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

After the user clicks the paste control, the application will fill the read bank card number into the input box, facilitating the user's operation.

III. Implementation of Core Functions

(I) Bank Card Binding Function

  1. Information Verification After the user inputs information such as the bank card number, the opening bank, and the cardholder's name, the application performs preliminary information format verification in the Application Layer, such as the number of digits of the bank card number and the validity of the opening bank. Then, in the Domain Layer, more rigorous verification is performed through the interface with the bank system (assuming interaction with the bank server through the network communication API) to ensure the accuracy and availability of the bank card information.
  2. Data Encryption and Storage After verification, the bank card information is encrypted in the Data Layer and then stored in the local secure storage area (using the secure storage API of HarmonyOS Next). The encryption algorithm can choose an industry-standard encryption algorithm, such as AES, to ensure the security of users' bank card information. For example:
import { secureStorage } from '@kit.SecurityKit';
// Encrypt and store bank card information
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);
}
// Function to encrypt data (assuming using AES encryption algorithm)
function encryptData(data: string): string {
  // Implement AES encryption logic here and return the encrypted string
  return encryptedData;
}
Enter fullscreen mode Exit fullscreen mode

(II) Payment Password Setting Function

  1. Password Strength Verification When the user sets the payment password, the password strength is verified in the Application Layer. The password is required to contain letters, numbers, special characters, etc., and the length should meet the requirements. For example:
function validatePasswordStrength(password: string): boolean {
  // Password strength verification logic, return true or false
  return password.length >= 8 && /[a-zA-Z]/.test(password) && /\d/.test(password) && /[^\w\s]/.test(password);
}
Enter fullscreen mode Exit fullscreen mode
  1. Password Encryption and Storage The payment password also needs to be encrypted and stored in the local secure storage area. The encryption method is similar to that of bank card information encryption to ensure the security of the password. For example:
// Encrypt and store payment password
async function storePaymentPassword(password: string): Promise<void> {
  let encryptedPassword = encryptData(password);
  await secureStorage.put('imageUpload() {
  let fileContent = await fileIo.readFile(filePath);
  let request = net.createHttpRequest();
  request.method = 'POST';
  request.url = 'https://your-server-url/upload-image';
  request.headers = { 'Content-Type': 'image/jpeg' };
  request.requestBody = fileContent;
  try {
    await request.send();
    if (request.responseCode === 200) {
      console.log('Image uploaded successfully.');
    } else {
      console.error('Image uploading failed, error code: ', request.responseCode);
    }
  } catch (error) {
    console.error('Error occurred during image uploading: ', error);
  }
}
// Function to receive an image message and display it (assuming this function is called when the server pushes the message)
function receiveImageMessage(fileContent: ArrayBuffer): void {
  // Convert the image data into a format that can be used for display (such as ImageSource). Here, it is assumed that there is a convertToImageSource() method.
  let imageSource = convertToImageSource(fileContent);
  // Display the image in the chat interface. Here, it is assumed that the view layer provides a method to display the image, showImage().
  view.showImage(imageSource);
}
Enter fullscreen mode Exit fullscreen mode

(II) Location Service Functions

  1. Obtaining the User's Current Location As described previously, use the location control to obtain the user's current location information. When the user clicks the location sharing button, obtain the location information, including longitude and latitude, through the geoLocationManager.getCurrentLocation() method. For example:
import { geoLocationManager } from '@kit.LocationKit';
import { promptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
// Function to obtain current location information
function getCurrentLocationInfo() {
  const requestInfo: geoLocationManager.LocationRequest = {
    'priority': geoLocationManager.LocationRequestPriority.FIRST_FIX,
    'scenario': geoLocationManager.LocationRequestScenario.UNSET,
    'timeInterval': 1,
    'distanceInterval': 0,
    'maxAccuracy': 0
  };
  try {
    geoLocationManager.getCurrentLocation(requestInfo)
  .then((location: geoLocationManager.Location) => {
      promptAction.showToast({ message: JSON.stringify(location) });
      // Here, the obtained location information can be shared, such as sending it to a friend.
    })
  .catch((err: BusinessError) => {
      console.error('Failed to obtain current location. Code is ', err.code, ', message is ', err.message);
    });
  } catch (err) {
    console.error('Failed to obtain current location. Code is ', err.code, ', message is ', err.message);
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Drawing Maps and Navigation (assuming integration with a third-party map library) If you want to implement the functions of drawing maps and navigation in the application, you can integrate a third-party map library (such as Amap, Baidu Map, etc., which are supported by HarmonyOS Next). After obtaining the user's location information, pass the location information to the map library to display the location marker on the map. For example, using the API of the Amap library (assuming it is the amap module):
import { amap } from '@kit.MapKit';
// Display the user's location on the map
function showUserLocationOnMap(location: geoLocationManager.Location): void {
  let map = amap.createMap('map-container');
  let marker = amap.createMarker({
    position: {
      longitude: location.longitude,
      latitude: location.latitude
    }
  };
  map.addMarker(marker);
}
Enter fullscreen mode Exit fullscreen mode

For the navigation function, according to the destination entered by the用户输入的目的地 and the current location, call the navigation interface of the map library to implement route planning and navigation guidance. For example:

// Navigate to the destination
async function navigateToDestination(destination: string): Promise<void> {
  let currentLocation = await getCurrentLocationInfo();
  let route = await amap.getRoute(currentLocation, destination);
  amap.startNavigation(route);
}
Enter fullscreen mode Exit fullscreen mode

(III) File Upload and Download Functions

  1. Uploading Photos When the user chooses to share a photo, in addition to sending an image message, you can also provide the function of uploading the photo to the server for storage or backup. Use the file reading API to read the photo data, and then upload the photo data to the server through the network communication API. For example:
import { fileIo } from '@kit.CoreFileKit';
import { net } from '@kit.NetworkKit';
// Function to upload a photo
async function uploadPhoto(filePath: string): Promise<void> {
  let fileContent = await fileIo.readFile(filePath);
  let request = net.createHttpRequest();
  request.method = 'POST';
  request.url = 'https://your-server-url/upload-photo';
  request.headers = { 'Content-Type': 'image/jpeg' };
  request.requestBody = fileContent;
  try {
    await request.send();
    if (request.responseCode === 200) {
      console.log('Photo uploaded successfully.');
    } else {
      console.error('Photo uploading failed, error code: ', request.responseCode);
    }
  } catch (error) {
    console.error('Error occurred during photo uploading: ', error);
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)