开始使用
请求鉴权

请求鉴权

申请安全凭证

本文使用的安全凭证为密钥,密钥包括 SecretIdSecretKey

  • SecretId:用于标识 API 调用者身份,是调用方的唯一标识。
  • SecretKey:用于验证 API 调用者的身份,可以简单类比为密码。
  • 调用方必须严格保管安全凭证,避免泄露,否则将危及财产安全。如已泄漏,请立刻禁用该安全凭证。

1.签名计算过程

下面以此示例详细介绍签名过程

https://p.wecard.tencent.com/cloudpay/v1/pay/query_order
 
Authorization: TC3-HMAC-SHA256 Credential=AKID********EXAMPLE/2023-04-13/wxpay/tc3_request, SignedHeaders=content-type;host;x-tc-ocode;x-tc-action, Signature=582c400e06b5924a6f2b5d7d672d79c15b13162d9279b0855cfba6789a8edb4c
Content-Type: application/json
Host: p.wecard.tencent.com
X-TC-Action: wxpay
X-TC-Version: 2023-04-13
X-TC-Timestamp: 1681301329
X-TC-Region: ap-shanghai
X-TC-Ocode: 123456
 
{"orderId":123}

1.1. 拼接规范请求串

按如下伪代码格式拼接规范请求串(CanonicalRequest):

CanonicalRequest =
    HTTPRequestMethod + '\n' +
    CanonicalURI + '\n' +
    CanonicalQueryString + '\n' +
    CanonicalHeaders + '\n' +
    SignedHeaders + '\n' +
    HashedRequestPayload
参数名称解释
HTTPRequestMethodHTTP 请求方法(GET、POST )。此示例取值为 POST
CanonicalURIURI 参数
CanonicalQueryString发起 HTTP 请求 URL 中的查询字符串,为 URL 中问号(?)后面的字符串内容,例如:orderId=xs1。
注意:CanonicalQueryString 需要参考 RFC3986 进行 URLEncode,字符集 UTF-8,推荐使用编程语言标准库,所有特殊字符均需编码,大写形式
CanonicalHeaders参与签名的头部信息;
1. 拼接规则:头部 key 和 value 统一转成小写,并去掉首尾空格,按照 key:value 格式拼接;
2. 多个头部,按照头部 key(小写)的 ASCII 升序进行拼接。
3. 此示例计算结果是 content-type:application/json; charset=utf-8 host:p.wecard.tencent.com x-tc-ocode:123456x-tc-action:wxpay。
注意:content-type 必须和实际发送的相符合,有些编程语言网络库即使未指定也会自动添加 charset 值,如果签名时和发送时不一致,服务器会返回签名校验失败。
SignedHeaders参与签名的头部信息,说明此次请求有哪些头部参与了签名,和 CanonicalHeaders 包含的头部内容是一一对应的。content-type 和 host 为必选头部。
1. 拼接规则:头部 key 统一转成小写;
2. 多个头部 key(小写)按照 ASCII 升序进行拼接,并且以分号(;)分隔。
3. 此示例为 content-type;host;x-tc-ocode;x-tc-action
HashedRequestPayload请求正文(payload,即 body的哈希值,计算伪代码为 Lowercase(HexEncode(Hash.SHA256(RequestPayload))),即对 HTTP 请求正文做 SHA256 哈希,然后十六进制编码,最后编码串转换成小写字母。
对于 GET 请求,RequestPayload 固定为空字符串。此示例计算结果是 35e9c5b0e3ae67532d3c9f17ead6c90222632e5b1ff7f6e。

根据以上规则,示例中得到的规范请求串如下:

POST
 
content-type:application/json; charset=utf-8
host:p.wecard.tencent.com
x-tc-action:wxpay
x-tc-ocode: 123456
 
content-type;host;x-tc-ocode;x-tc-action
35e9c5b0e3ae67532d3c9f17ead6c90222632e5b1ff7f6ef89887f1398934f064
 

1.2. 拼接待签名字符串

按如下格式拼接待签名字符串:

StringToSign =
    Algorithm + \n +
    RequestTimestamp + \n +
    CredentialScope + \n +
    HashedCanonicalRequest
参数名称解释
Algorithm签名算法,目前固定为TC3-HMAC-SHA256。
RequestTimestamp请求时间戳,即请求头部的公共参数 X-TC-Timestamp 取值,取当前时间 UNIX 时间戳,精确到秒。此示例取值为1681301329。
CredentialScope凭证范围,格式为 Date/service/tc3_request,包含日期、所请求的服务和终止字符串(tc3_request)。
Date 为 UTC 标准时间的日期,取值需要和公共参数 X-TC-Timestamp 换算的 UTC 标准时间日期一致;
service 为产品名,必须与调用的产品域名一致。
此示例计算结果是 2023-04-13/wxpay/tc3_request。
HashedCanonicalRequest前述步骤拼接所得规范请求串的哈希值,计算伪代码为 Lowercase(HexEncode(Hash.SHA256(CanonicalRequest)))。此示例计算结果是 7019a55be8395899b900fb5564e4200d984910f34794a27cb3fb7d10ff6a1e84。

根据以上规则,示例中得到的待签名字符串如下:

TC3-HMAC-SHA256
1681301329
2023-04-13/wxpay/tc3_request
7019a55be8395899b900fb5564e4200d984910f34794a27cb3fb7d10ff6a1e84

1.3.计算签名

1)计算派生签名密钥,伪代码如下:

SecretKey = "Gu5t9xGARNpq86cd98joQYCN3*******"
SecretDate = HMAC_SHA256("TC3" + SecretKey, Date)
SecretService = HMAC_SHA256(SecretDate, Service)
SecretSigning = HMAC_SHA256(SecretService, "tc3_request")

派生出的密钥 SecretDate、SecretService 和 SecretSigning 是二进制的数据,可能包含不可打印字符,此处不展示中间结果。
请注意,不同的编程语言,HMAC 库函数中参数顺序可能不一样,请以实际情况为准。此处的伪代码密钥参数 key 在前,消息参数 data 在后。通常标准库函数会提供二进制格式的返回值,也可能会提供打印友好的十六进制格式的返回值,此处使用的是二进制格式。

2)计算签名,伪代码如下:

Signature = HexEncode(HMAC_SHA256(SecretSigning, StringToSign))

此示例计算结果是 be4f67d323c78ab9acb7395e43c0dbcf822a9cfac32fea2449a7bc7726b770a3。

1.4. 拼接Authorization

按如下格式拼接 Authorization:

Authorization =
    Algorithm + ' ' +
    'Credential=' + SecretId + '/' + CredentialScope + ', ' +
    'SignedHeaders=' + SignedHeaders + ', ' +
    'Signature=' + Signature
参数名称解释
Algorithm签名方法,固定为 TC3-HMAC-SHA256。
SecretId密钥对中的 SecretId,即 AKIDz8krbsJ5yKBZQpn74WFkmLPx3*******。
CredentialScope见上文,凭证范围。此示例计算结果是2023-04-13/wxpay/tc3_request。
SignedHeaders见上文,参与签名的头部信息。此示例取值为 content-type;host;x-tc-ocode;x-tc-action。
Signature签名值。此示例计算结果是 be4f67d323c78ab9acb7395e43c0dbcf822a9cfac32fea2449a7bc7726b770a3。

根据以上规则,示例中得到的值为:

TC3-HMAC-SHA256 Credential=AKIDz8krbsJ5yKBZQpn74WFkmLPx3*******/2023-04-13/wxpay/tc3_request, SignedHeaders=content-type;host;x-tc-ocode;x-tc-action, Signature=be4f67d323c78ab9acb7395e43c0dbcf822a9cfac32fea2449a7bc7726b770a3

最终完整的调用信息如下:

POST
/
 
content-type:application/json; charset=utf-8
host:p.wecard.tencent.com
x-tc-action:wxpay
x-tc-ocode: 123456
 
content-type;host;x-tc-ocode;x-tc-action
35e9c5b0e3ae67532d3c9f17ead6c90222632e5b1ff7f6ef89887f1398934f064

2. 回调请求验签

对于需要接收微卡收付通回调请求的业务,可以针对请求数据进行验签,判断回调的合法性;
验签的签名算法与业务系统请求微卡收付通的算法一致,只需修改验签逻辑中的加签元素即可。 需要替换的数据元素获取方式:

  • host: 替换为业务方回调地址的域名
  • uri: 替换为业务方回调地址中的 uri
  • timestamp: 替换为从回调请求 header 中获取的 X-TC-Timestamp
  • payload: 替换为从回调请求中获取的请求内容 body

将通过上述验签流程获取的签名数据,与回调请求 header 中 Authorization 比对是否相等,如果相等则认为验签通过,否则认为验签失败;
具体流程可以参考以下的代码演示。

3. 代码演示

JAVA

 
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
 
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
 
import com.alibaba.fastjson.JSONObject;
 
@Slf4j
public class Sign {
    private final static String ocode = "";     // 客户 appid
    private final static String SECRET_ID = ""; // 客户的secretId,值为示例的 AKIDz8krbsJ5yKBZQpn74WFkmLPx3*******
    private final static String SECRET_KEY = ""; // 客户的 secretKey,值为示例的 Gu5t9xGARNpq86cd98joQYCN3*******
    private final static String CT_JSON = "application/json; charset=utf-8";
    private final static Charset UTF8 = StandardCharsets.UTF_8;
 
    private final static String region = "ap-shanghai";
    private final static String version = "2023-04-13";
 
    public static byte[] hmac256(byte[] key, String msg) throws NoSuchAlgorithmException, InvalidKeyException {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
        mac.init(secretKeySpec);
        return mac.doFinal(msg.getBytes(UTF8));
    }
 
    public static String sha256Hex(String s) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] d = md.digest(s.getBytes(UTF8));
        return DatatypeConverter.printHexBinary(d).toLowerCase();
    }
 
    // genAuthorization 计算签名
    private static String genAuthorization(String host, String payload, String uri, String action, String timestamp) throws NoSuchAlgorithmException, InvalidKeyException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        // 注意时区,否则容易出错
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
        String algorithm = "TC3-HMAC-SHA256";
 
        // ************* 步骤 1:拼接规范请求串 *************
        String httpRequestMethod = "POST";
        String service = "wxpay";
        String canonicalUri = uri;
        String canonicalQueryString = "";
        String canonicalHeaders = "content-type:application/json; charset=utf-8\n"
                + "host:" + host + "\n" + "x-tc-ocode:" + ocode + "\n" + "x-tc-action:" + action.toLowerCase() + "\n";
        String signedHeaders = "content-type;host;x-tc-ocode;x-tc-action";
 
        String hashedRequestPayload = sha256Hex(payload);
        String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
                + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
        System.out.println(canonicalRequest); // 调试日志 todo
 
        // ************* 步骤 2:拼接待签名字符串 *************
        String credentialScope = date + "/" + service + "/" + "tc3_request";
        String hashedCanonicalRequest = sha256Hex(canonicalRequest);
        String stringToSign = algorithm + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
        System.out.println(stringToSign); // 调试日志 todo
 
        // ************* 步骤 3:计算签名 *************
        byte[] secretDate = hmac256(("TC3" + SECRET_KEY).getBytes(UTF8), date);
        byte[] secretService = hmac256(secretDate, service);
        byte[] secretSigning = hmac256(secretService, "tc3_request");
        String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase();
        System.out.println(signature);  // 调试日志 todo
 
        // ************* 步骤 4:拼接 Authorization *************
        String authorization = algorithm + " " + "Credential=" + SECRET_ID + "/" + credentialScope + ", "
                + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
        System.out.println(authorization); // 调试日志 todo
 
        return authorization;
    }
 
    // 请求开放接口通用方法
    private static void doPost(String uri, String payload) {
        try{
            // 准备加签数据
            String host = "p.wecard.tencent.com"; // 微卡接口域名
            String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
            String action = "wxpay" ;// 不用修改
            // 获取加签数据
            String authorization = genAuthorization(host, payload, uri, action, timestamp);
 
            // 组装请求数据
            HttpPost httpPost = new HttpPost("https://" + host + uri);
            httpPost.addHeader("Authorization", authorization);
            httpPost.addHeader("Content-Type", CT_JSON);
            httpPost.addHeader("Host", host);
            httpPost.addHeader("X-TC-Action", action);
            httpPost.addHeader("X-TC-Timestamp", timestamp);
            httpPost.addHeader("X-TC-Version", version);
            httpPost.addHeader("X-TC-Region", region);
            httpPost.addHeader("X-TC-Ocode", ocode);
 
            // 发起请求
            CloseableHttpClient httpClient = HttpClients.createDefault();
            httpPost.setEntity(new StringEntity(payload, "UTF-8"));
            CloseableHttpResponse response = httpClient.execute(httpPost);
            if (response.getStatusLine().getStatusCode() != 200) {
                System.err.println(response);
                throw new RuntimeException("HTTP request Exception");
            }
            String resultdata = EntityUtils.toString(response.getEntity(), "UTF-8");
            System.err.println(resultdata);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
 
    /*
     * checkCallBackSign 回调的验签流程
     * 回调时请求回调接口的加签逻辑,与请求开放接口的加签逻辑一致。只需要修改加签的参数即可
     * 1、准备主体的appid、secretId、secretKey
     * 2、从 http-header 获取 X-TC-Timestamp 计算签名的秒级时间戳(建议校验回调服务器的当前时间与该时间戳的时间差)
     * 3、获取回调接口的域名
     * 4、获取回调接口的uri
     * 5、获取回调的请求body
     * 6、将以上数据调用 genAuthorization 方法计算签名,与从 http-header 获取 Authorization 签名字符串比较是否相等
     *
     */
    private static void checkCallBackSign(HttpServletRequest request, HttpServletResponse response, String requestBody) throws  Exception {
        String requestAuth = request.getHeader("Authorization");
        if (requestAuth == null || requestAuth.isEmpty()) {
            // 验签失败返回的状态码不能为 200
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }
 
        // 准备验签数据
        String host = ""; // 回调接口的域名domain,如回调地址为http://xxx.com/t/pay_notify, 则 domain 为 xxx.com
        String canonicalUri = ""; // 回调接口的路由uri,如回调地址为http://xxx.com/order/pay_notify, 则 uri 为 /order/pay_notify
        String timestamp =  request.getHeader("X-TC-Timestamp");// 从header中获取X-TC-Timestamp加签使用的秒级的时间戳
        String action = "Notify"; // 不用修改
 
        System.out.println(host); // 调试日志 todo
        System.out.println(canonicalUri); // 调试日志 todo
        System.out.println(timestamp); // 调试日志 todo
        System.out.println(requestAuth); // 调试日志 todo
 
        // 计算加签
        String calculatedAuth  = genAuthorization(host, requestBody, canonicalUri, action, timestamp);
        System.out.println(calculatedAuth); // 调试日志 todo
 
        // 进行签名对比
        if (!calculatedAuth.equals(requestAuth)) {
            System.out.println("验签失败");
            // 验签失败返回的状态码不能为 200
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            return;
        }
 
        // todo 客户的业务逻辑
        System.out.println("验签成功");
 
        // 验签成功返回200 HTTP状态码
        response.setStatus(HttpServletResponse.SC_OK);
    }
 
 
 
    public static void main(String[] args) throws Exception {
        // 请求查单接口示例
        String uri = "/cloudpay/v1/pay/query_order";
        JSONObject params = new JSONObject();
        params.put("OutOrderId", "demoOrderId20231107170000");
        doPost(uri, JSONObject.toJSONString(params));
    }
}
 

GOLANG

 
package main
 
import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"time"
)
 
// 填充一下客户资料
var appId = ""     // 客户的appid
var secretId = ""  //客户的secretId
var secretKey = "" // 客户的 secretKey
 
func main() {
	// 开放接口调用示例
	payload := ``      // 需改成接口实际请求body,如 {"OutOrderId": "order202211200001"}
	host := ""         // 需改成接口域名,如地址为 http://p.wecard.tencent.com/cloudpay/v1/pay/unified_order, 则 host 为 p.wecard.tencent.com
	canonicalUri := "" // 需改成接口实际uri,如地址为 http://p.wecard.tencent.com/cloudpay/v1/pay/unified_order, 则 uri 为 /cloudpay/v1/pay/unified_order
	rsp, err := CallOpenApi(host, canonicalUri, payload)
	fmt.Println(err, rsp)
}
 
// CallOpenApi 请求微卡收付通开放接口
func CallOpenApi(host, canonicalUri, payload string) (string, error) {
	// 请求参数构造
	timestamp := time.Now().Unix() // 每次请求时的秒级时间戳
	action := "wxpay"              // 固定不用修改
 
	// 获取签名
	authorization := genAuthorization(host, payload, canonicalUri, action, timestamp)
 
	// 构造请求参数
	buf := bytes.NewReader([]byte(payload))
	req, err := http.NewRequest("POST", "https://"+host+canonicalUri, buf)
	if err != nil {
		return "", fmt.Errorf("new request err:%v", err)
	}
	req.Header.Set("Authorization", authorization)
	req.Header.Set("Content-Type", "application/json; charset=utf-8")
	req.Header.Set("Host", host)
	req.Header.Set("X-TC-Action", action)
	req.Header.Set("X-TC-Timestamp", strconv.FormatInt(timestamp, 10))
	req.Header.Set("X-TC-Version", "2023-04-13")
	req.Header.Set("X-TC-Region", "ap-shanghai")
	req.Header.Set("X-TC-Ocode", appId)
 
	// 发起请求
	rsp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", fmt.Errorf("do request err:%v", err)
	}
	if rsp == nil {
		return "", fmt.Errorf("do request fail rsp is nil")
	}
 
	// 获取接口返回
	response, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return "", fmt.Errorf("read body err:%v", err)
	}
	return string(response), nil
}
 
// CheckCallBackSign 验证微卡收付通回调的签名
/*
 * 回调时请求回调接口的加签逻辑,与请求开放接口的加签逻辑一致。只需要修改加签的参数即可
 * 1、准备主体的appid、secretId、secretKey
 * 2、从 http-header 获取 X-TC-Timestamp 计算签名的秒级时间戳(建议校验回调服务器的当前时间与该时间戳的时间差)
 * 3、获取回调接口的域名
 * 4、获取回调接口的uri
 * 5、获取回调的请求body
 * 6、将以上数据调用checkReqAuthorization 方法计算签名,与从 http-header 获取 Authorization 签名字符串比较是否相等
 *
 */
func CheckCallBackSign(w http.ResponseWriter, r *http.Request) {
	auth := r.Header.Get("Authorization")
	if auth == "" {
		w.WriteHeader(400) // 验签失败返回的状态码不能为 200
		return
	}
	timestamp := r.Header.Get("X-TC-Timestamp")
	host := r.Host               // 回调接口的域名domain
	canonicalUri := r.RequestURI // 回调接口的路由uri
	payload, _ := ioutil.ReadAll(r.Body)
	fmt.Printf("timestamp:%s, host:%s, canonicalUri:%s,payload:%s", timestamp, host, canonicalUri, payload)
 
	timestampInt, _ := strconv.ParseInt(timestamp, 10, 64)
	if genAuthorization(host, string(payload), canonicalUri, "Notify", timestampInt) != auth {
		w.WriteHeader(403) // 验签失败返回的状态码不能为 200
		return
	}
	// todo 使用body处理业务逻辑
	w.WriteHeader(200)
	return
}
 
// genAuthorization 计算签名
func genAuthorization(host, payload, canonicalURI, action string, timestamp int64) string {
	// step 1: build canonical request string
	httpRequestMethod := "POST"
	service := "wxpay"
	algorithm := "TC3-HMAC-SHA256"
	canonicalQueryString := ""
	canonicalHeaders := fmt.Sprintf("content-type:%s\nhost:%s\nx-tc-ocode:%s\nx-tc-action:%s\n",
		"application/json; charset=utf-8", host, appId, strings.ToLower(action))
	signedHeaders := "content-type;host;x-tc-ocode;x-tc-action"
 
	hashedRequestPayload := sha256hex(payload)
	canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s",
		httpRequestMethod,
		canonicalURI,
		canonicalQueryString,
		canonicalHeaders,
		signedHeaders,
		hashedRequestPayload)
	fmt.Println(canonicalRequest)
 
	// step 2: build string to sign
	date := time.Unix(timestamp, 0).UTC().Format("2006-01-02")
	credentialScope := fmt.Sprintf("%s/%s/tc3_request", date, service)
	hashedCanonicalRequest := sha256hex(canonicalRequest)
	string2sign := fmt.Sprintf("%s\n%d\n%s\n%s",
		algorithm,
		timestamp,
		credentialScope,
		hashedCanonicalRequest)
	fmt.Println(string2sign)
 
	// step 3: sign string
	secretDate := hmacsha256(date, "TC3"+secretKey)
	secretService := hmacsha256(service, secretDate)
	secretSigning := hmacsha256("tc3_request", secretService)
	signature := hex.EncodeToString([]byte(hmacsha256(string2sign, secretSigning)))
	fmt.Println(signature)
 
	// step 4: build authorization
	authorization := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s",
		algorithm,
		secretId,
		credentialScope,
		signedHeaders,
		signature)
	fmt.Println(authorization)
	return authorization
}
 
func sha256hex(s string) string {
	b := sha256.Sum256([]byte(s))
	return hex.EncodeToString(b[:])
}
 
func hmacsha256(s, key string) string {
	hashed := hmac.New(sha256.New, []byte(key))
	hashed.Write([]byte(s))
	return string(hashed.Sum(nil))
}
 
 

PHP

 
<?php
/*
 * CallOpenApi 请求开放接口通用方法
 */
function CallOpenApi() {
    // 客户的资料
    $appId = ""; // 客户的appid
    $secretId = ""; //客户的secretId
    $secretKey = ""; // 客户的 secretKey
 
    // 请求参数构造
    $timestamp = time(); // 每次请求时的秒级时间戳
    $payload = ''; // 需改成接口实际请求body,如 {"OutOrderId": "order202211200001"}
    $host = ""; // 需改成接口域名,如地址为 http://p.wecard.tencent.com/cloudpay/v1/pay/unified_order, 则 host 为 p.wecard.tencent.com
    $canonicalUri = ""; // 需改成接口实际uri,如地址为 http://p.wecard.tencent.com/cloudpay/v1/pay/unified_order, 则 uri 为 /cloudpay/v1/pay/unified_order
    $action = "wxpay";  // 固定不用修改
 
    // 获取签名
    $authorization = genAuthorization($host, $appId, $payload, $canonicalUri, $action, $timestamp, $secretId, $secretKey);
 
    // 组装请求内容并发起请求
    $version = "2023-04-13"; // 固定不用修改
    $region = "ap-shanghai";  // 固定不用修改
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://".$host.$canonicalUri);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        "Authorization: ".$authorization,
        "Content-Type: application/json; charset=utf-8",
        "Host: ".$host,
        "X-TC-Action: ".$action,
        "X-TC-Timestamp: ".$timestamp,
        "X-TC-Version: ".$version,
        "X-TC-Region: ".$region,
        "X-TC-Ocode: ".$appId,
    ));
    $result = curl_exec($ch);
    curl_close($ch);
    var_dump($result);
    // 根据接口定义返回体处理结果
}
 
/*
 * CheckCallBackSign 回调的验签流程
 * 回调时请求回调接口的加签逻辑,与请求开放接口的加签逻辑一致。只需要修改加签的参数即可
 * 1、准备主体的appid、secretId、secretKey
 * 2、从 http-header 获取 X-TC-Timestamp 计算签名的秒级时间戳(建议校验回调服务器的当前时间与该时间戳的时间差)
 * 3、获取回调接口的域名
 * 4、获取回调接口的uri
 * 5、获取回调的请求body
 * 6、将以上数据调用checkReqAuthorization 方法计算签名,与从 http-header 获取 Authorization 签名字符串比较是否相等
 *
 */
function CheckCallBackSign()
{
    $requestAuth = $_SERVER['HTTP_AUTHORIZATION']; // 从header中获取签名,header中的key为Authorization
    if ($requestAuth == "") {
        http_response_code(400); // 验签失败返回的状态码不能为 200
        return;
    }
 
    // 客户的资料
    $appId = ""; // 客户的appid
    $secretId = ""; //客户的secretId
    $secretKey = ""; // 客户的 $secretKey
 
    // 从请求数据中获取加签资料
    $timestamp = $_SERVER['HTTP_X_TC_TIMESTAMP'];   // 从header中获取X-TC-Timestamp加签使用的秒级的时间戳
    $host = $_SERVER["HTTP_HOST"];     // 回调接口的域名domain,如回调地址为http://xxx.com/t/pay_notify, 则 domain 为 xxx.com
    $canonicalUri = $_SERVER["REQUEST_URI"];   // 回调接口的路由uri,如回调地址为http://xxx.com/order/pay_notify, 则 uri 为 /order/pay_notify
    $payload = file_get_contents('php://input'); // 获取回调请求的 body
    $action = "Notify";
 
    // 验证请求的签名数据,和计算出来的签名是否一致
    if (genAuthorization($host, $appId, $payload, $canonicalUri, $action, $timestamp, $secretId, $secretKey) != $requestAuth) {
        echo "验签失败\n";
        http_response_code(403); // 验签失败返回的状态码不能为 200
        return;
    }
 
    // todo  客户的业务逻辑
 
    echo "验签成功";
    http_response_code(200); // 验签成功返回200 http状态码
}
 
// 计算签名方法
function genAuthorization($host, $appId, $payload, $canonicalUri, $action, $timestamp, $secretId, $secretKey)
{
    $httpRequestMethod = "POST";
    $service = "wxpay";
    $algorithm = "TC3-HMAC-SHA256";
    $canonicalQueryString = "";
 
    $canonicalHeaders = implode("\n", [
        "content-type:application/json; charset=utf-8",
        "host:" . $host,
        "x-tc-ocode:" . $appId,
        "x-tc-action:" . strtolower($action),
        ""
    ]);
    $signedHeaders = implode(";", [
        "content-type",
        "host",
        "x-tc-ocode",
        "x-tc-action",
    ]);
 
    $hashedRequestPayload = hash("SHA256", $payload);
    $canonicalRequest = $httpRequestMethod . "\n"
        . $canonicalUri . "\n"
        . $canonicalQueryString . "\n"
        . $canonicalHeaders . "\n"
        . $signedHeaders . "\n"
        . $hashedRequestPayload;
    echo $canonicalRequest . PHP_EOL;
 
    // step 2: build string to sign
    $date = gmdate("Y-m-d", $timestamp);
    $credentialScope = $date . "/" . $service . "/tc3_request";
    $hashedCanonicalRequest = hash("SHA256", $canonicalRequest);
    echo "stringToSign\n";
    $stringToSign = $algorithm . "\n"
        . $timestamp . "\n"
        . $credentialScope . "\n"
        . $hashedCanonicalRequest;
    echo $stringToSign . PHP_EOL;
 
    // step 3: sign string
    $secretDate = hash_hmac("SHA256", $date, "TC3" . $secretKey, true);
    $secretService = hash_hmac("SHA256", $service, $secretDate, true);
    $secretSigning = hash_hmac("SHA256", "tc3_request", $secretService, true);
    $signature = hash_hmac("SHA256", $stringToSign, $secretSigning);
    echo $signature . PHP_EOL;
 
    // step 4: build authorization
    $authorization = $algorithm
        . " Credential=" . $secretId . "/" . $credentialScope
        . ", SignedHeaders=" . $signedHeaders . ", Signature=" . $signature;
 
    return $authorization;
}
 

腾讯微卡收付通接口文档