Bootstrap

鸿蒙开发-HMS Kit能力集(应用内支付、推送服务)

1 应用内支付

开发步骤

步骤一:判断当前登录的华为账号所在服务地是否支持应用内支付

在使用应用内支付之前,您的应用需要向IAP Kit发送queryEnvironmentStatus请求,以此判断用户当前登录的华为帐号所在的服务地是否在IAP Kit支持结算的国家/地区中。

// pages/Index.ets
/**
 * @description 应用内支付服务示例-消耗型商品
 * @author 白晓明
 * @organization 坚果派
 * @website: nutpi.com.cn
 * @date 2024-06-01
 */
import { common } from '@kit.AbilityKit'
import { iap } from '@kit.IAPKit';
import { JSON } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  private context: common.UIAbilityContext = {} as common.UIAbilityContext;
  @State querying: boolean = true;
  @State queryingFailed: boolean = false;
  @State productInfoArray: iap.Product[] = [];
  @State queryFailedText: string = "查询失败!";

  showLoadingPage() {
    this.queryingFailed = false;
    this.querying = true;
  }

  showFailedPage(failedText?: string) {
    if (failedText) {
      this.queryFailedText = failedText;
    }
    this.queryingFailed = true;
    this.querying = false;
  }

  showNormalPage() {
    this.queryingFailed = false;
    this.querying = false;
  }

  aboutToAppear(): void {
    this.showLoadingPage();
    this.context = getContext(this) as common.UIAbilityContext;
    this.onCase();
  }

  async onCase() {
    this.showLoadingPage();
    const queryEnvCode = await this.queryEnv();
    if (queryEnvCode !== 0) {
      let queryEnvFailedText = "当前应用不支持IAP Kit服务!";
      if (queryEnvCode === iap.IAPErrorCode.ACCOUNT_NOT_LOGGED_IN) {
        queryEnvFailedText = "请通过桌面设置入口登录华为账号后再次尝试!";
      }
      this.showFailedPage(queryEnvFailedText);
      return;
    }
  }

  // 判断当前登录的华为账号所在服务地是否支持应用内支付。
  async queryEnv(): Promise<number> {
    try {
      console.log("IAPKitDemo queryEnvironmentStatus begin.");
      await iap.queryEnvironmentStatus(this.context);
      return 0;
    } catch (error) {
      promptAction.showToast({
        message: "IAPKitDemo queryEnvironmentStatus failed. Cause: " + JSON.stringify(error)
      })
      return error.code;
    }
  }
  build() {...}
}

步骤二:确保权益发放

用户购买商品后,开发者需要及时发放相关权益。但实际应用场景中,若出现异常(网络错误、进程被中止等)将导致应用无法知道用户实际是否支付成功,从而无法及时发放权益,即出现掉单情况。为了确保权益发放,您需要在以下场景检查用户是否存在已购未发货的商品:

如果存在已购未发货商品,则发放相关权益,然后向IAP Kit确认发货,完成购买。

// pages/Index.ets
/**
 * @description 应用内支付服务示例-消耗型商品
 * @author 白晓明
 * @organization 坚果派
 * @website: nutpi.com.cn
 * @date 2024-06-01
 */
import { common } from '@kit.AbilityKit'
import { iap } from '@kit.IAPKit';
import { JSON } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  private context: common.UIAbilityContext = {} as common.UIAbilityContext;
  @State querying: boolean = true;
  @State queryingFailed: boolean = false;
  @State productInfoArray: iap.Product[] = [];
  @State queryFailedText: string = "查询失败!";
  
  showLoadingPage() {...}

  showFailedPage(failedText?: string) {...}

  showNormalPage() {...}

  aboutToAppear(): void {...}

  async onCase() {
    ...
    await this.queryPurchase();
  }

  // 判断当前登录的华为账号所在服务地是否支持应用内支付。
  async queryEnv(): Promise<number> {...}
  
  async queryPurchase() {
    console.log("IAPKitDemo queryPurchase begin.");
    const queryPurchaseParam: iap.QueryPurchasesParameter = {
      productType: iap.ProductType.CONSUMABLE,
      queryType: iap.PurchaseQueryType.UNFINISHED
    };
    const result: iap.QueryPurchaseResult = await iap.queryPurchases(this.context, queryPurchaseParam);
    // 处理订单信息
    if (result) {
      const purchaseDataList: string[] = result.purchaseDataList;
      if (purchaseDataList === undefined || purchaseDataList.length <= 0) {
        console.log("IAPKitDemo queryPurchase, list empty.");
        return;
      }
      for (let i = 0; i < purchaseDataList.length; i++) {
        const purchaseData = purchaseDataList[i];
        const jwsPurchaseOrder = (JSON.parse(purchaseData) as PurchaseData).jwsPurchaseOrder;
        if (!jwsPurchaseOrder) {
          console.log("IAPKitDemo queryPurchase, jwsPurchaseOrder invalid.");
          continue;
        }
        const purchaseStr = JWTUtil.decodeJwtObj(jwsPurchaseOrder);
        const purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseOrderPayload;
      }
    }
  }
  
  build() {...}
}

步骤三:查询商品信息

通过queryProducts来获取在AppGallery Connect上配置的商品信息。发起请求时,开发者需在请求参数QueryProductsParameter中携带相关的商品ID,并根据实际配置指定其productType。

当接口请求成功时,IAP Kit将返回商品信息Product的列表。 您可以使用Product包含的商品价格、名称和描述等信息,向用户展示可供购买的商品列表。

// pages/Index.ets
/**
 * @description 应用内支付服务示例-消耗型商品
 * @author 白晓明
 * @organization 坚果派
 * @website: nutpi.com.cn
 * @date 2024-06-01
 */
import { common } from '@kit.AbilityKit'
import { iap } from '@kit.IAPKit';
import { JSON } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  private context: common.UIAbilityContext = {} as common.UIAbilityContext;
  @State querying: boolean = true;
  @State queryingFailed: boolean = false;
  @State productInfoArray: iap.Product[] = [];
  @State queryFailedText: string = "查询失败!";
  
  showLoadingPage() {...}

  showFailedPage(failedText?: string) {...}

  showNormalPage() {...}

  aboutToAppear(): void {...}

  async onCase() {
    ...
    await this.queryProducts();
  }

  // 判断当前登录的华为账号所在服务地是否支持应用内支付。
  async queryEnv(): Promise<number> {...}
  
  // 查询商品信息
  async queryProducts() {
    try {
      console.log("IAPKitDemo queryProducts begin.");
      const queryProductParam: iap.QueryProductsParameter = {
        productType: iap.ProductType.CONSUMABLE,
        productIds: ['nutpi_course_1']
      };
      const result: iap.Product[] = await iap.queryProducts(this.context, queryProductParam);
      this.productInfoArray = result;
      this.showNormalPage();
    } catch (error) {
      this.showFailedPage();
    }
  }
  
  async queryPurchase() {...}
  
  build() {...}
}

步骤四:构建商品列表UI

// pages/Index.ets
/**
 * @description 应用内支付服务示例-消耗型商品
 * @author 白晓明
 * @organization 坚果派
 * @website: nutpi.com.cn
 * @date 2024-06-01
 */
import { common } from '@kit.AbilityKit'
import { iap } from '@kit.IAPKit';
import { JSON } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  private context: common.UIAbilityContext = {} as common.UIAbilityContext;
  @State querying: boolean = true;
  @State queryingFailed: boolean = false;
  @State productInfoArray: iap.Product[] = [];
  @State queryFailedText: string = "查询失败!";
  
  showLoadingPage() {...}

  showFailedPage(failedText?: string) {...}

  showNormalPage() {...}

  aboutToAppear(): void {...}

  async onCase() {...}

  // 判断当前登录的华为账号所在服务地是否支持应用内支付。
  async queryEnv(): Promise<number> {...}
  
  // 查询商品信息
  async queryProducts() {...}
  
  async queryPurchase() {...}
  
  build() {
    Column() {
      Column() {
        Text('应用内支付服务示例-消耗型')
          .fontSize(18)
          .fontWeight(FontWeight.Bolder)
      }
      .width('100%')
      .height(54)
      .justifyContent(FlexAlign.Center)
      .backgroundColor(Color.White)

      Column() {
        Column() {
          Row() {
            Text('Consumables')
              .fontSize(28)
              .fontWeight(FontWeight.Bold)
              .margin({ left: 24, right: 24 })
          }
          .margin({ top: 16, bottom: 12 })
          .height(48)
          .justifyContent(FlexAlign.Start)
          .width('100%')

          // 商品列表信息
          List({ space: 0, initialIndex: 0 }) {
            ForEach(this.productInfoArray, (item: iap.Product) => {
              ListItem() {
                Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
                  Image($r('app.media.app_icon'))
                    .height(48)
                    .width(48)
                    .objectFit(ImageFit.Contain)

                  Text(item.name)
                    .width('100%')
                    .height(48)
                    .fontSize(16)
                    .textAlign(TextAlign.Start)
                    .padding({ left: 12, right: 12 })

                  Button(item.localPrice)
                    .width(200)
                    .fontSize(16)
                    .height(30)
                    .onClick(() => {
                      this.createPurchase(item.id, item.type)
                    })
                    .stateEffect(true)
                }
                .borderRadius(16)
                .backgroundColor('#FFFFFF')
                .alignSelf(ItemAlign.Auto)
              }
            })
          }
          .divider({ strokeWidth: 1, startMargin: 2, endMargin: 2 })
          .padding({ left: 12, right: 12 })
          .margin({ left: 12, right: 12 })
          .borderRadius(16)
          .backgroundColor('#FFFFFF')
          .alignSelf(ItemAlign.Auto)
        }
        .backgroundColor('#F1F3F5')
        .width('100%')
        .height('100%')
        .visibility(this.querying || this.queryingFailed ? Visibility.None : Visibility.Visible)

        // 加载进度组件
        Stack() {
          LoadingProgress()
            .width(96)
            .height(96)
        }
        .backgroundColor('#F1F3F5')
        .width('100%')
        .height('100%')
        .visibility(this.querying ? Visibility.Visible : Visibility.None)

        // 异常文本提示
        Stack({ alignContent: Alignment.Center }) {
          Text(this.queryFailedText)
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
            .margin({ left: 24, right: 24 })
        }
        .backgroundColor('#F1F3F5')
        .width('100%')
        .height('100%')
        .visibility(this.queryingFailed ? Visibility.Visible : Visibility.None)
        .onClick(() => {
          this.onCase();
        })
      }
      .width('100%')
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor(0xF1F3F5)
  }
}

步骤五:发起购买

用户发起购买时,开发者的应用可通过向IAP Kit发送createPurchase请求来拉起IAP Kit收银台。发起请求时,需在请求参数PurchaseParameter中携带开发者此前已在华为AppGallery Connect网站上配置并生效的商品ID,并根据实际配置指定其productType。

// pages/Index.ets
/**
 * @description 应用内支付服务示例-消耗型商品
 * @author 白晓明
 * @organization 坚果派
 * @website: nutpi.com.cn
 * @date 2024-06-01
 */
import { common } from '@kit.AbilityKit'
import { iap } from '@kit.IAPKit';
import { JSON } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  private context: common.UIAbilityContext = {} as common.UIAbilityContext;
  @State querying: boolean = true;
  @State queryingFailed: boolean = false;
  @State productInfoArray: iap.Product[] = [];
  @State queryFailedText: string = "查询失败!";
  
  showLoadingPage() {...}

  showFailedPage(failedText?: string) {...}

  showNormalPage() {...}

  aboutToAppear(): void {...}

  async onCase() {...}

  // 判断当前登录的华为账号所在服务地是否支持应用内支付。
  async queryEnv(): Promise<number> {...}
  
  // 查询商品信息
  async queryProducts() {...}
  
  async queryPurchase() {...}
  
  /**
   * 发起购买
   * @param id AppGallery Connect控制台配置的商品ID
   * @param type 商品类型
   */
  createPurchase(id: string, type: iap.ProductType) {
    console.log("IAPKitDemo createPurchase begin.");
    try {
      const createPurchaseParam: iap.PurchaseParameter = {
        productId: id,
        productType: type
      };
      iap.createPurchase(this.context, createPurchaseParam).then(async (result) => {
        console.log("IAPKitDemo createPurchase success. Data: " + JSON.stringify(result));
        // 获取PurchaseOrderPayload的JSON字符串
        const purchaseData: PurchaseData = JSON.parse(result.purchaseData) as PurchaseData;
        const jwsPurchaseOrder: string = purchaseData.jwsPurchaseOrder;
        // 解码 JWTUtil为自定义类,可参见Sample Code工程
        const purchaseStr = JWTUtil.decodeJwtObj(jwsPurchaseOrder);
        const purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseOrderPayload;
        // 处理发货
        
      }).catch((error: BusinessError) => {
        promptAction.showToast({
          message: "IAPKitDemo createPurchase failed. Cause: " + JSON.stringify(error)
        })
        if (error.code === iap.IAPErrorCode.PRODUCT_OWNED || error.code === iap.IAPErrorCode.SYSTEM_ERROR) {
          // 参考权益发放检查是否需要补发货,确保权益发放
          this.queryPurchase();
        }
      })
    } catch (err) {
      promptAction.showToast({
        message: "IAPKitDemo createPurchase failed. Error: " + JSON.stringify(err)
      })
    }
  }
  
  build() {...}
}

步骤六:完成购买

PurchaseData.jwsPurchaseOrder解码验签成功后,如果PurchaseOrderPayload.purchaseOrderRevocationReasonCode为空,则代表购买成功,即可发放相关权益。

发货成功后,开发者需在应用中发送finishPurchase请求确认发货,以此通知IAP服务器更新商品的发货状态,完成购买流程。发送finishPurchase请求时,需在请求参数FinishPurchaseParameter中携带PurchaseOrderPayload中的productType、purchaseToken、purchaseOrderId。请求成功后,IAP服务器会将相应商品标记为已发货。

对于消耗型商品,应用成功执行finishPurchase之后,IAP服务器会将相应商品重新设置为可购买状态,用户即可再次购买该商品。

// pages/Index.ets
/**
 * @description 应用内支付服务示例-消耗型商品
 * @author 白晓明
 * @organization 坚果派
 * @website: nutpi.com.cn
 * @date 2024-06-01
 */
import { common } from '@kit.AbilityKit'
import { iap } from '@kit.IAPKit';
import { JSON } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  private context: common.UIAbilityContext = {} as common.UIAbilityContext;
  @State querying: boolean = true;
  @State queryingFailed: boolean = false;
  @State productInfoArray: iap.Product[] = [];
  @State queryFailedText: string = "查询失败!";
  
  showLoadingPage() {...}

  showFailedPage(failedText?: string) {...}

  showNormalPage() {...}

  aboutToAppear(): void {...}

  async onCase() {...}

  // 判断当前登录的华为账号所在服务地是否支持应用内支付。
  async queryEnv(): Promise<number> {...}
  
  // 查询商品信息
  async queryProducts() {...}
  
  async queryPurchase() {...}
  
  /**
   * 发起购买
   * @param id AppGallery Connect控制台配置的商品ID
   * @param type 商品类型
   */
  createPurchase(id: string, type: iap.ProductType) {
    console.log("IAPKitDemo createPurchase begin.");
    try {
      const createPurchaseParam: iap.PurchaseParameter = {
        productId: id,
        productType: type
      };
      iap.createPurchase(this.context, createPurchaseParam).then(async (result) => {
        console.log("IAPKitDemo createPurchase success. Data: " + JSON.stringify(result));
        // 获取PurchaseOrderPayload的JSON字符串
        const purchaseData: PurchaseData = JSON.parse(result.purchaseData) as PurchaseData;
        const jwsPurchaseOrder: string = purchaseData.jwsPurchaseOrder;
        // 解码 JWTUtil为自定义类,可参见Sample Code工程
        const purchaseStr = JWTUtil.decodeJwtObj(jwsPurchaseOrder);
        const purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseOrderPayload;
        // 处理发货
        this.finishPurchase(purchaseOrderPayload);
      }).catch((error: BusinessError) => {
        promptAction.showToast({
          message: "IAPKitDemo createPurchase failed. Cause: " + JSON.stringify(error)
        })
        if (error.code === iap.IAPErrorCode.PRODUCT_OWNED || error.code === iap.IAPErrorCode.SYSTEM_ERROR) {
          // 参考权益发放检查是否需要补发货,确保权益发放
          this.queryPurchase();
        }
      })
    } catch (err) {
      promptAction.showToast({
        message: "IAPKitDemo createPurchase failed. Error: " + JSON.stringify(err)
      })
    }
  }
  
  finishPurchase(purchaseOrder: PurchaseOrderPayload) {
    console.log("IAPKitDemo finishPurchase begin.");
    const finishPurchaseParam: iap.FinishPurchaseParameter = {
      productType: purchaseOrder.productType,
      purchaseToken: purchaseOrder.purchaseToken,
      purchaseOrderId: purchaseOrder.purchaseOrderId
    };
    iap.finishPurchase(this.context, finishPurchaseParam).then((result) => {
      console.log("IAPKitDemo finishPurchase success");
    }).catch((error: BusinessError) => {
      promptAction.showToast({
        message: "IAPKitDemo finishPurchase failed. Cause: " + JSON.stringify(error)
      })
    })
  }
  
  build() {...}
}

2 推送服务

开发步骤

步骤一:请求通知授权

为确保应用可正常收到消息,建议应用发送通知前调用requestEnableNotification()方法弹出提醒,告知用户需要允许接收通知消息。

// entryability/EntryAbility.ets
/**
 * @description 应用入口
 * @author 白晓明
 * @organization 坚果派
 * @website: nutpi.com.cn
 * @date 2024-06-13
 */
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { JSON } from '@kit.ArkTS';
import { notificationManager } from '@kit.NotificationKit';

export default class EntryAbility extends UIAbility {
  async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
    // 请求通知授权
    await this.requestNotification();
  }

  async requestNotification() {
    try {
      console.info("requestNotification: 请求通知授权开始。");
      // 查询通知是否授权
      const notificationEnabled: boolean = await notificationManager.isNotificationEnabled();
      console.info("requestNotification: " + (notificationEnabled ? '已' : '未') + "授权");
      if (!notificationEnabled) {
        // 请求通知授权
        await notificationManager.requestEnableNotification();
      }
    } catch (error) {
      const e: BusinessError = error as BusinessError;
      console.error("requestNotification failed. Cause: " + JSON.stringify(e));
    }
  }
}

步骤二:获取Push Token

导入pushService模块。建议在应用的UIAbility(例如EntryAbility)的onCreate()方法中调用getToken()接口获取Push Token并上报到开发者的服务端,方便开发者的服务端向终端推送消息。本示例便于应用端测试发送通知消息请求,将Push Token获取放置在Index.ets页面。

// pages/Index.ets
import { pushService } from '@kit.PushKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { JSON } from '@kit.ArkTS';
import { http } from '@kit.NetworkKit';
import { promptAction } from '@kit.ArkUI';
/**
 * @description 推送服务示例
 * @author 白晓明
 * @organization 坚果派
 * @website: nutpi.com.cn
 * @date 2024-06-13
 */
@Entry
@Component
struct Index {

  @State pushToken: string = "";

  async aboutToAppear(): Promise<void> {
    try {
      // 获取Push Token
      const pushToken: string = await pushService.getToken();
      console.log("getToken succeed. Token: " + pushToken);
      const now = new Date();
      const timestamp = now.getTime();

      console.log("getToken succeed. Time: " + Math.floor(timestamp / 1000));
      console.log("getToken succeed. Time: " + (Math.floor(timestamp / 1000) + 3600));
      this.pushToken = pushToken;
      // 此处需要上报Push Token到应用服务端
    } catch (error) {
      const e: BusinessError = error as BusinessError;
      console.error("getToken failed. Cause: " + JSON.stringify(e));
    }
  }

  build() {...}
}

步骤三:获取项目ID

登录AppGallery Connect控制台,选择“我的项目”,在项目列表中选择对应的项目,左侧导航栏选择“项目设置”,拷贝项目ID。

步骤四:创建服务账号密钥文件

  • 开发者需要在华为开发者联盟的API Console上创建并下载推送服务API的服务账号密钥文件。点击“管理中心 > API服务 > API库”,在API库页面选择“项目名称”,在展开的App Services列表中点击“推送服务”。

  • 点击推送服务页面中的“启用”,完成API添加。

  • 点击“管理中心 > API服务 > 凭证”,在凭证页面点击“服务账号密钥”卡片中的“创建凭证”按钮。

  • 在“创建服务账号密钥”页面输入信息并点击“生成公私钥”,点击“创建并下载JSON”,完成“服务账号密钥”凭证创建,需要开发者保存“支付公钥”,用于后期生成JWT鉴权令牌。

步骤五:生成JWT Token

开发者在正式开发前调试功能,可使用在线生成工具获取JWT Token,需要注意生成JWT Token时Algorithm请选择RS256或PS256。若用于正式环境,为了方便开发者生成服务账号鉴权令牌,华为提供了JWT开源组件,可根据开发者使用的开发语言选择进行开发。

  • HEADER中的kid指下载的服务账号密钥文件中key_id字段。
  • PAYLOAD数据中iss指下载的的服务账号密钥文件中sub_account字段。
  • VERIFY SIGNATURE中复制粘贴公钥和私钥。

步骤六:调用推送服务REST API

该模块需要开发者在应用服务端自行开发,需要结合用户信息留存设备Token,本课程中该功能位于应用端仅用于学习,不推荐该方法。应用服务端调用Push Kit服务端的REST API推送通知消息,需要传递的参数说明如下所示:

  • [projectId]:项目ID。
  • Authorization:JWT格式字符串,JWT Token前加“Bearer ”,需注意“Bearer”和JWT格式字符串中间的空格不能丢。
  • push-type:0表示Alert消息,此处为通知消息场景。
  • category:表示通知消息自分类的类别,MARKETING为资讯营销类消息。
  • actionType:0表示点击消息打开应用首页。
  • token:Push Token。
  • testMessage:测试消息标识,true标识测试消息。
  • notifyId:(选填)自定义消息标识字段,仅支持数字,范围[0, 2147483647],若要用于消息撤回则必填。

在应用端按钮组件Button的点击事件onClick中通过数据请求API实现发送通知消息。

// pages/Index.ets
import { pushService } from '@kit.PushKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { JSON } from '@kit.ArkTS';
import { http } from '@kit.NetworkKit';
import { promptAction } from '@kit.ArkUI';
/**
 * @description 推送服务示例
 * @author 白晓明
 * @organization 坚果派
 * @website: nutpi.com.cn
 * @date 2024-06-13
 */
@Entry
@Component
struct Index {

  @State pushToken: string = "";
  @State isLoading: boolean = false;
  // 步骤五生成的JWT Token
  authorization: string = "Bearer ****";

  async aboutToAppear(): Promise<void> {
    try {
      // 获取Push Token
      const pushToken: string = await pushService.getToken();
      console.log("getToken succeed. Token: " + pushToken);
      const now = new Date();
      const timestamp = now.getTime();

      console.log("getToken succeed. Time: " + Math.floor(timestamp / 1000));
      console.log("getToken succeed. Time: " + (Math.floor(timestamp / 1000) + 3600));
      this.pushToken = pushToken;
      // 上报Push Token
    } catch (error) {
      const e: BusinessError = error as BusinessError;
      console.error("getToken failed. Cause: " + JSON.stringify(e));
    }
  }

  async deletePushTokenFunc() {
    try {
      await pushService.deleteToken();
    } catch (error) {
      const e: BusinessError = error as BusinessError;
      console.error("deleteToken failed. Cause: " + JSON.stringify(e));
    }
  }

  build() {
    Column() {
      Row() {
        Text('推送服务示例')
          .fontSize(18)
          .fontWeight(FontWeight.Bolder)
      }
      .width('100%')
      .height(54)
      .justifyContent(FlexAlign.Center)
      .alignItems(VerticalAlign.Center)

      Column({ space: 16 }) {

        Row() {
          LoadingProgress()
          Text('等待通知发送完成')
            .fontSize(16)
        }
        .width('100%')
        .height(64)
        .justifyContent(FlexAlign.Center)
        .visibility(this.isLoading ? Visibility.Visible : Visibility.Hidden)

        Button('发送通知消息')
          .type(ButtonType.Normal)
          .borderRadius(8)
          .enabled(!this.isLoading)
          .onClick(async () => {
            try {
              this.isLoading = true;
              const url = "https://push-api.cloud.huawei.com/v3/388421841222199046/messages:send";
              const httpRequest = http.createHttp();
              const response: http.HttpResponse = await httpRequest.request(url, {
                header: {
                  "Content-Type": "application/json",
                  "Authorization": this.authorization,
                  "push-type": 0
                },
                method: http.RequestMethod.POST,
                extraData: {
                  "payload": {
                    "notification": {
                      "category": "MARKETING",
                      "title": "普通通知标题",
                      "body": "普通通知内容",
                      "clickAction": {
                        "actionType": 0
                      },
                      "notifyId": 12345
                    }
                  },
                  "target": {
                    "token": [this.pushToken]
                  },
                  "pushOptions": {
                    "testMessage": true
                  }
                }
              })
              if (response.responseCode === 200) {
                const result = response.result as string;
                const data = JSON.parse(result) as ResultData;
                promptAction.showToast({
                  message: data.msg
                })
              }
            } catch (error) {
              const e: BusinessError = error as BusinessError;
              console.error("getToken failed. Cause: " + JSON.stringify(e));
            } finally {
              this.isLoading = false;
            }
          })
      }
      .width('100%')
      .layoutWeight(1)
    }
    .height('100%')
    .width('100%')
  }
}

// 接口返回数据类
interface ResultData {
  code: string;
  msg: string;
  requestId: string;
}

;