# 服务端关键事件通知

如果接入了IAP Kit订单/订阅功能，建议在AppGallery Connect网站配置通知接收地址，用于接收IAP服务器发送的关键事件通知。IAP关键事件通知版本只支持v3。

建议所有提供消耗型/非消耗型/自动续期订阅/非续期订阅商品的App均配置该通知接收地址，以便接收关键事件通知，为用户提供更好、更及时的服务。另外，通知接收地址必须基于HTTPS并且配置有商业域名机构颁发的证书。

#### 功能介绍

用户购买消耗型/非消耗型/自动续期订阅/非续期订阅商品后，IAP服务器调用此接口向开发者服务器发送关键事件通知。

#### 场景描述

如果在AppGallery Connect申请支付服务时提前配置通知接收地址，并且通知版本设置为v3，用户购买消耗型/非消耗型/自动续期订阅/非续期订阅商品后，IAP服务器会在某些关键事件发生时调用此接口通知已配置的服务器。

消耗型/非消耗型/非续期订阅商品主要涉及的场景如下：

*   支付成功。
    
*   退款成功。
    

自动续期订阅商品主要涉及的场景如下：

*   用户第一次订阅成功。
    
*   已过期的订阅自动续期成功。
    
*   用户主动恢复一个已过期的自动续期订阅商品。
    
*   用户调整为相同等级的自动续期订阅商品，如果订阅商品的周期相同，则新订阅立即生效。
    
*   用户调整为相同等级的自动续期订阅商品，如果订阅商品的周期不相同，则新订阅在下个续期日生效。
    
*   用户调整为更高等级的自动续期订阅商品，则新订阅立即生效，原订阅的剩余金额将折算成新订阅的天数，延长新订阅的有效期。
    
*   用户调整为更低等级的自动续期订阅商品，则新订阅在下个续期日生效
    
*   用户主动或者App取消一个自动续期订阅商品，已经收费的服务仍然有效，但是后续续期会停止。
    
*   一个自动续期订阅商品成功续期。
    
*   一个到期的自动续期订阅商品进入账号保留期。
    
*   订阅的续期时间已延期。
    

若开发者服务器返回结果为非成功响应（请求返回的HTTP状态码不为200），IAP服务器将对本次关键事件的通知进行周期性重发。建议在收到通知后立即返回成功响应，避免通知消息堆积。

为保证可靠性，系统具备补偿机制，所以可能出现重发的通知比预期的多。

沙盒测试场景下通知失败不会进行重发。

#### 关键事件通知处理建议

1.  开发者服务器收到IAP服务器的关键事件通知（下文简称事件通知）时，建议根据通知内容中的购买Token，请求IAP服务器校验购买Token获取当前购买Token关联订单/订阅的最新状态，并根据订单/订阅最新状态决定是否需要提供商品服务。
    
2.  事件通知仅作为订单/订阅状态发生变化的一个事件通知，由于网络延迟等导致服务器收到通知事件有延迟，通知中携带的信息可能与IAP服务器的最新的状态不一致，建议不要根据事件通知来决定是否提供商品服务。
    
3.  收到DID\_NEW\_TRANSACTION通知类型后，建议根据如下通知处理流程进行业务逻辑处理，提供商品服务。
    

关键事件通知处理流程建议如下：

1.  IAP服务器发送订单/订阅关键事件通知。
    
2.  应用服务器收到通知请求后，从通知中获取购买Token。
    
3.  调用IAP服务器提供的订单状态查询/订阅状态查询接口，查询购买数据及其签名数据。
    
4.  IAP服务器返回购买数据及其签名数据。
    
5.  校验订单/订阅状态提供商品服务。消耗型/非消耗型/非续期订阅商品请根据订单状态查询接口获取消耗型/非消耗型/非续期订阅的订单最新状态，再根据订单状态调用订单确认发货接口执行发货操作。自动续期订阅商品请根据订阅状态查询接口响应经JWS解码之后SubGroupStatusPayload中的status字段决定是否发货。若status为1，则调用订阅确认发货接口执行发货操作。
    
6.  应用服务器返回通知响应给IAP服务器。
    

#### 接口约束

不允许开发者服务器设置IP允许清单用于限制华为侧的出口IP地址。IP允许清单本身并不能提高安全性且会给业务发展带来约束，在消息层面已有更安全的JWS签名机制条件下，没有存在价值。不遵守该约定而导致的后果将由开发者自行承担。

必须在开发前在AppGallery Connect中配置开发者服务器的回调地址，地址必须支持HTTPS协议且具有合法商用证书，出于安全考虑，cipher需要支持ECDHE-RSA-AES128-GCM-SHA256、ECDHE-ECDSA-AES128-GCM-SHA256、ECDHE-RSA-AES256-GCM-SHA384或ECDHE-ECDSA-AES256-GCM-SHA384中至少一种，否则无法正常接收通知消息。

#### 接口原型

*   **承载协议：** HTTPS POST
    
*   **接口方向：** IAP服务器 -> 开发者服务器
    
*   **接口URL：** URL由开发者在申请支付服务时配置
    
*   **数据格式：**
    
    请求消息：Content-Type: application/json; charset=UTF-8
    
    响应消息：Content-Type: application/json; charset=UTF-8
    

#### 请求参数

#### Request Body

| 参数 | 是否必选 | 参数类型 | 描述 |
| --- | --- | --- | --- |
| jwsNotification | 是 | String | 通知内容信息，对通知内容NotificationPayload签名后的字符串。具体请参见表NotificationPayload。 需要参见对返回结果验签使用JWS签名方式对NotificationPayload消息体进行验签。 |

#### NotificationPayload

| 参数 | 是否必选 | 参数类型 | 描述 |
| --- | --- | --- | --- |
| notificationType | 是 | String | 通知主类型，具体请参见表NotificationType说明。 |
| notificationSubtype | 否 | String | 通知子类型，具体请参见表NotificationSubtype说明。后续子类型会增加，开发者需要设计相应的处理机制。 |
| notificationRequestId | 是 | String | 通知唯一请求ID。 |
| notificationMetaData | 是 | Object | 通知元数据，具体请参见表NotificationMetaData说明。 |
| notificationVersion | 是 | String | 通知版本：v3。 |
| signedTime | 是 | Long | 通知签名时间，UTC时间戳，以毫秒为单位。 |

#### NotificationType

| 取值 | 取值说明 |
| --- | --- |
| DID_NEW_TRANSACTION | 订单已购买/订阅已购买/订阅续订成功。 |
| DID_CHANGE_RENEWAL_STATUS | 订阅状态发生改变。 |
| REVOKE | 订单退款/撤销订阅。 |
| RENEWAL_TIME_MODIFIED | 订阅过期时间调整。 |
| EXPIRE | 订阅已过期。 |
| TEST | 测试服务端通知，仅开发者调用测试服务端通知接口才会发送此类型通知。 此场景下无notificationSubtype。 |

#### NotificationSubtype

| 取值 | 取值说明 |
| --- | --- |
| INITIAL_BUY | 消耗型/非消耗型/非续期订阅商品购买成功。 自动续期订阅商品的第一次购买成功。 使用主类型：DID_NEW_TRANSACTION |
| DID_RENEW | 续期成功。 使用主类型：DID_NEW_TRANSACTION |
| RESTORE | 用户主动恢复了一个订阅型商品，续期恢复正常。 使用主类型：DID_NEW_TRANSACTION |
| AUTO_RENEW_ENABLED | 自动续期功能开启。 使用主类型：DID_CHANGE_RENEWAL_STATUS |
| AUTO_RENEW_DISABLED | 自动续期功能关闭。 使用主类型：DID_CHANGE_RENEWAL_STATUS |
| DOWNGRADE | 用户调整自动续期订阅商品降级或跨级且在下个续订生效。 使用主类型：DID_CHANGE_RENEWAL_STATUS或DID_NEW_TRANSACTION |
| UPGRADE | 用户调整自动续期订阅商品升级或跨级且立即生效。 使用主类型：DID_NEW_TRANSACTION |
| REFUND_TRANSACTION | 消耗型/非消耗型/非续期订阅商品订单退款成功。 自动续期订阅商品订单退款成功。 使用主类型：REVOKE |
| BILLING_RETRY | 一个到期的自动续期订阅商品进入账号保留期。 使用主类型：EXPIRE |
| PRICE_INCREASE | 用户同意了涨价。 使用主类型：DID_CHANGE_RENEWAL_STATUS |
| BILLING_RECOVERY | 订阅重试扣费成功。 使用主类型：DID_NEW_TRANSACTION |
| VOLUNTARY | 主动退订后订阅过期。 使用主类型：EXPIRE |
| PRODUCT_NOT_FOR_SALE | 商品不存在。 使用主类型：EXPIRE |
| APPLICATION_DELETE_SUBSCRIPTION_HOSTING | 撤销订阅成功，订阅权益会立即取消。 使用主类型：REVOKE |
| RENEWAL_EXTENDED | 延迟订阅续订日期成功，订阅的下一个续订日期将推迟。 使用主类型：RENEWAL_TIME_MODIFIED |

#### NotificationMetaData

| 参数 | 是否必选 | 参数类型 | 描述 |
| --- | --- | --- | --- |
| environment | 是 | String | 环境类型。 - NORMAL：正式环境 - SANDBOX：沙盒环境 |
| applicationId | 是 | String | 应用Id。 |
| packageName | 是 | String | 应用包名。 |
| type | 是 | Integer | 商品类型。 0：消耗型商品 1：非消耗型商品 2：自动续期订阅商品 3：非续期订阅商品 |
| currentProductId | 否 | String | 最近一个有效订阅的商品ID。仅自动续期订阅商品场景下存在值。 |
| subGroupId | 否 | String | 订阅组ID。仅自动续期订阅商品场景下存在值。 |
| subGroupGenerationId | 否 | String | 订阅组的代ID。 - 用户切换订阅商品时，此ID不会改变。 - 订阅失效且超出保留期后，用户重新购买商品时，此ID会改变。 |
| subscriptionId | 否 | String | 商品的订阅ID。以下场景，此ID会发生改变： - 用户切换订阅商品时。 - 订阅失效且超出保留期后，用户重新购买商品时。 |
| purchaseToken | 是 | String | 商品的购买Token，发起购买和查询订阅信息均会返回。最大长度256。 |
| purchaseOrderId | 否 | String | 具体一笔订单中对应的购买订单号。当NotificationType为DID_CHANGE_RENEWAL_STATUS且NotificationSubtype为DOWNGRADE时不返回purchaseOrderId。 最大长度256。 |

#### 请求示例

```
POST /notify/address
Content-Type: application/json;charset=UTF-8
Accept: application/json
{
  "jwsNotification": "***"
}
```

NotificationPayload样例（消耗型/非消耗型）

```
{
  "notificationType": "DID_NEW_TRANSACTION",
  "notificationSubtype": "INITIAL_BUY",
  "notificationRequestId": "7f67e39aa72d1a55f36293047f9769c0aa47a467ffb110eaeeeb888def9f9713",
  "notificationMetaData": {
    "environment": "NORMAL",
    "applicationId": "***",
    "packageName": "***",
    "type": 0,
    "purchaseToken": "***.*.***",
    "purchaseOrderId": "***.***"
  },
  "notificationVersion": "v3",
  "signedTime": 1702607152698
}
```

NotificationPayload样例（非续期订阅商品）

```
{
  "notificationType": "DID_NEW_TRANSACTION",
  "notificationSubtype": "INITIAL_BUY",
  "notificationRequestId": "7f67e39aa72d1a55f36293047f9769c0aa47a467ffb110eaeeeb888def9f9713",
  "notificationMetaData": {
    "environment": "NORMAL",
    "applicationId": "***",
    "packageName": "***",
    "type": 3,
    "purchaseToken": "***.*.***",
    "purchaseOrderId": "***.***"
  },
  "notificationVersion": "v3",
  "signedTime": 1702607152698
}
```

NotificationPayload样例（自动续期订阅商品）

```
{
  "notificationType": "DID_NEW_TRANSACTION",
  "notificationSubtype": "INITIAL_BUY",
  "notificationRequestId": "6d5882d55c8e5136f2348b8e13b4aaad6d0938898b7e5efdc13f7c7130286f8c",
  "notificationMetaData": {
    "environment": "NORMAL",
    "applicationId": "***",
    "packageName": "***",
    "type": 2,
    "currentProductId": "1701072721154732",
    "subGroupId": "1701072721154732",
    "subGroupGenerationId": "f7967e2439769fbb3c155a50d2cdd6a8cc8f7750cac7bf78b6f5d65c97c34deb",
    "subscriptionId": "1701072722814.ADE66B39.4732",
    "purchaseToken": "***.*.***",
    "purchaseOrderId": "***.*.***"
  },
  "notificationVersion": "v3",
  "signedTime": 1701072726860
}
```

#### 响应参数

通过HTTP状态码来标识IAP服务器通知开发者服务器是否发送成功：

*   如果通知发送成功，则发送HTTP 200，不需要返回响应体。
    
*   如果通知发送失败，则通过发送HTTP 40X或者HTTP 50X，告知IAP服务器进行重试，IAP服务器会在一段时间内重试多次。
    

#### 响应示例

```
HTTP/1.2 200 OK
```