FinClip为企业提供小程序生态圈技术产品,开发者可在FinClip小程序开发帮助中心找到相关FinClip小程序指引

# 小程序消息订阅推送流程

小程序订阅消息是小程序能力中的重要组成部分,通过该功能,开发者可以在获得用户授权后,向用户发送服务通知,实现服务的闭环和提供更优质的用户体验。订阅消息的实现方式由 APP 自定义,可以实现类似站内信、通知栏等。

本文档将从以下三个部分说明小程序消息订阅推送的完整流程:

  1. 管理后台消息模板和渠道的配置:介绍开发者如何配置消息模板、配置应用消息渠道、关联消息模板和渠道、配置应用Webhook
  2. 用户授权流程:说明用户如何授权接收消息以及授权信息的处理过程
  3. 消息推送流程:详述从消息触发到发送、接收和展示的完整过程

# 1. 管理后台消息模板和渠道的配置

# 1.1 配置应用的消息渠道

说明 应用的消息渠道是由应用方自行维护的配置,属于应用维度的设置。平台侧不限定也无法限定应用的消息渠道实现方式,应用方可根据自身业务需求进行灵活配置。应用可以根据自身需求设置多种消息渠道,如站内信、短信、邮件等,以满足不同场景下的消息推送需求。

入口

开发中心-「应用」-「消息渠道」-「消息渠道配置」

新增消息渠道

# 1.2 配置应用的消息推送 Webhook

说明 Webhook 是平台向应用推送消息的目标地址。在消息推送过程中,平台会将消息内容通过 HTTP POST 请求发送到应用配置的 Webhook 地址。由于平台侧不限定应用的消息推送实现方式,因此通过 Webhook 机制实现消息的灵活推送,应用方可自行处理接收到的消息并进行后续业务处理,如用户通知等。

入口

开发中心-「应用」-「消息渠道」-「消息推送 Webhook 配置」

配置消息推送的 Webhook

# 1.3 创建小程序消息模板

说明 小程序消息模板的创建需要经过「数字中心」的审核流程。开发者可在管理后台创建消息模板,定义消息的内容格式和变量,提交后由数字中心进行审核,确保消息内容符合规范要求。审核通过后的消息模板才能用于实际的消息推送,这一机制有助于保障消息内容的合规性和用户体验。

入口

开发中心-「小程序」-「消息模板」-「申请创建」

申请消息模板

# 1.4 消息模板申请关联应用的消息渠道

说明 消息模板需要与应用的消息渠道关联才能实现消息推送。当消息模板申请关联应用的渠道时,需要应用方进行审核确认。这一步骤确保了消息模板与应用渠道的合理匹配,应用方可根据自身业务需求决定是否批准关联请求。关联成功后,平台可通过该渠道向用户推送基于此模板的消息。

入口

开发中心-「小程序」-「关联应用」-「消息渠道关联」

申请关联渠道

# 2. 用户订阅消息流程

# 2.1 小程序服务端调用 OpenAPI 获取可用的模板列表

接口详见: 开发中心 OpenAPI - 获取小程序可用的消息模板 (opens new window)

# 2.2 用户授权请求

  • 触发授权请求:用户在使用小程序相关功能时,小程序客户端可以通过wx.requestSubscribeMessage({tmplIds: [...]})接口触发授权请求
  • 展示授权界面:小程序展示授权请求界面,向用户说明授权的目的和内容。

# 2.3 授权处理

  • 用户操作:用户可以选择同意或拒绝授权请求,授权选择会通过 FinClip SDK 记录在 FinClip 服务端中。如果用户同意授权,系统会记录用户的授权信息,并允许向该用户发送消息。

# 3. 消息推送流程

# 3.1 消息触发

当特定业务事件发生时(如订单状态变更、活动提醒等),「小程序服务端」可以主动选择触发消息推送。 调用如下的 OpenAPI 接口进行消息的推送:

开发中心 OpenAPI - 推送小程序消息模板 (opens new window)

# 4. 其他

# 4.1 应用的消息推送 Webhook 的 body 长什么样?

webhook 的推送 body 示例如下

{
    "miniAppId": "fc1111", // 小程序 id
    "userId": "zhangsan", // userId,和 sdk 初始化的时候传入的 userId 一致
    "templates": [
        {
            "messageDeliveryTemplateId": "2696141363995781", // 消息模板 id
            "text": "your order has been shipped, express number is 123456", // 消息内容
            "link": "/order/orderDetail", // 消息跳转链接
            "desc": "this is a description", // 消息描述
            "channelCodes": [
                "notificationCenter" // 消息渠道代码
            ]
        }
    ]
}

# 4.2 应用的消息推送 Webhook 如何保障传输来源的可信?

为了使得您的服务 (webhook事件的接收方) 有途径校验请求来自可信任的调用方,提供了签名的方式。为此您需要:

  1. 在配置Webhook的时候,手动填写 Secret
  2. Webhook接收方中校验:Secret+请求的入参body 计算出来的值需要等于 header中的 X-Fc-Webhook-Sign
    • 配置Webhook的时候,如果没有填写 Secret, 不会传X-Fc-Webhook-Sign的header

# 签名方式说明

X-Fc-Webhook-Sign 的签名使用了请求body的 HMAC 十六进制摘要,并使用 SHA-256 哈希函数生成,并将 作为webhookTokenHMAC key

验证 webhook 有效负载时需要牢记以下几件重要的事情:

  • FinClip 使用 HMAC 十六进制摘要来计算哈希值。
  • 哈希签名始终以 开头sha256=
  • 哈希签名是使用您的 webhook 的 webhookTokenwebhook请求体内容 生成的。
  • 如果您的语言和服务器实现指定了字符编码,请确保将负载处理为 UTF-8。Webhook 负载可以包含 Unicode 字符。

# 签名示例

Java

您可以定义以下verifySignature方法并在收到 webhook 请求时调用它:

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public class WebhookVerifier {
    /**
     * 验证webhook签名
     * 
     * @param payload webhook请求的request body
     * @param webhookToken 生成的webhookToken
     * @param signature webhook请求中header中的 X-Fc-Webhook-Sign
     * @return 签名是否有效
     */
    public static boolean verifySignature(String payload, String webhookToken, String signature) {
        String expectedSignature = computeSign(payload, webhookToken);
        return signature.equals(expectedSignature);
    }

    /**
     * 计算签名
     * 
     * @param payload webhook请求的request body
     * @param webhookToken 生成的webhookToken
     * @return 签名
     */
    public static String computeSign(String payload, String webhookToken) {
        try {
            String algorithm = "HmacSHA256";
            Mac mac = Mac.getInstance(algorithm);
            SecretKeySpec keySpec = new SecretKeySpec(webhookToken.getBytes(StandardCharsets.UTF_8), algorithm);
            mac.init(keySpec);

            // 计算签名
            byte[] signatureBytes = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));

            // 转换为16进制字符串
            String hexDigest = bytesToHex(signatureBytes);

            return "sha256=" + hexDigest;
        } catch (Exception e) {
            throw new RuntimeException("err", e);
        }
    }
    
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }
}

Golang

您可以定义以下VerifySignature函数并在收到 webhook 请求时调用它:

// 参数说明
// payload: webhook请求的request body
// webHookToken: 生成的webhookToken
// signature: webhook请求中header中的 X-Fc-Webhook-Sign
func VerifySignature(payload []byte, webHookToken string, signature string) bool {
	expectedSignature := computeWebHookSign(payload, webHookToken)
	return hmac.Equal([]byte(expectedSignature), []byte(signature))
}

func computeWebHookSign(payload []byte, webhookToken string) string {
	mac := hmac.New(sha256.New, []byte(webhookToken))
	mac.Write(payload)
	return "sha256=" + hex.EncodeToString(mac.Sum(nil))
}

Python

您可以定义以下verify_signature函数并在收到 webhook 请求时调用它:

import hashlib
import hmac
def verify_signature(payload_body, secret_token, signature_header):
    """Verify that the payload was sent from GitHub by validating SHA256.

    Raise and return 403 if not authorized.

    Args:
        payload_body: original request body to verify (request.body())
        secret_token: GitHub app webhook token (WEBHOOK_SECRET)
        signature_header: header received from GitHub (x-hub-signature-256)
    """
    if not signature_header:
        raise HTTPException(status_code=403, detail="x-hub-signature-256 header is missing!")
    hash_object = hmac.new(secret_token.encode('utf-8'), msg=payload_body, digestmod=hashlib.sha256)
    expected_signature = "sha256=" + hash_object.hexdigest()
    if not hmac.compare_digest(expected_signature, signature_header):
        raise HTTPException(status_code=403, detail="Request signatures didn't match!")

JavaScript

let encoder = new TextEncoder();

async function verifySignature(secret, header, payload) {
    let parts = header.split("=");
    let sigHex = parts[1];

    let algorithm = { name: "HMAC", hash: { name: 'SHA-256' } };

    let keyBytes = encoder.encode(secret);
    let extractable = false;
    let key = await crypto.subtle.importKey(
        "raw",
        keyBytes,
        algorithm,
        extractable,
        [ "sign", "verify" ],
    );

    let sigBytes = hexToBytes(sigHex);
    let dataBytes = encoder.encode(payload);
    let equal = await crypto.subtle.verify(
        algorithm.name,
        key,
        sigBytes,
        dataBytes,
    );

    return equal;
}

function hexToBytes(hex) {
    let len = hex.length / 2;
    let bytes = new Uint8Array(len);

    let index = 0;
    for (let i = 0; i < hex.length; i += 2) {
        let c = hex.slice(i, i + 2);
        let b = parseInt(c, 16);
        bytes[index] = b;
        index += 1;
    }

    return bytes;
}
© FinClip with ❤ , Since 2017