快钱支付接入 - H5和SDK网关支付

 提示:转载请注明原文链接

 本文链接:https://360us.net/article/100.html

快钱官方没有提供完整的SDK,仅有一些demo,要集成进现有go mod项目要花一点时间集成和测试,记录一下签名验证的关键点,支付方式是SDK网关支付和H5支付。

快钱会提供三个密钥文件:

  • 商户应用私钥文件,pfx格式,有密码,用于签名数据。
  • ssl证书,pfx格式,有密码,用于https双向认证,快钱的证书测试下来会报错,所以用openssl提取了证书和密钥使用,下面有写。
  • 快钱公钥证书,用于加/解密和验证数据。

场景是把以上证书内容base64编码成字符串存数据库,提供页面UI配置。

1、支付下单数据签名

按照文档里面的字段顺序排序字段,拼接成查询字符串,用商户私钥加签。

//SignData data 拼接的待签名字符串
func SignData(data string) (string, error) {
    // merchantKey私钥
	certBytes, err := base64.StdEncoding.DecodeString(merchantKey)
	if err != nil {
		return "", err
	}

    // merchantKeyPwd 私钥密码
    // "golang.org/x/crypto/pkcs12"
	pkey, _, err := pkcs12.Decode(certBytes, merchantKeyPwd)
	if err != nil {
		return "", err
	}
	privateKey, ok := pkey.(*rsa.PrivateKey)
	if !ok {
		return "", errors.New("parse private key fail")
	}
	hashed := sha256.Sum256([]byte(data))
    // "crypto/rsa"
	signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed[:])
	if err != nil {
		return "", err
	}

    // 返回签名结果
	return base64.StdEncoding.EncodeToString(signature), nil
}

2、http请求发送

SDK网关支付的下单,订单查询(支付和退款查询接口相同)和退款接口把数据编码成json普通post请求即可。

H5支付的有点特殊:

1. 组装数据

post json body数据分为两部分:

  • head:接口和商户信息
  • requestBody:接口业务数据,需要签名和加密

首先用户商户私钥调用p7crypto.GetSignedData生成签名signedData,再用快钱公钥调用p7crypto.GetEnvelopedData加密数据envelopedData

上面两个函数在官方给的h5 demo里面有,p7crypto目录(pkcs7加解密/签名),还有依赖kernel目录,复制进自己的项目,函数里面写死的密钥文件改成传参即可。

最后组成requestBody:

requestBody = map[string]interface{}{
    "signedData":    signedData,
    "envelopedData": envelopedData,
}

2. 发送请求

然后是Post josn数据的双向认证。

官方提供的pfx证书有点问题,测试下来会报错,所以用openssl从里面分别提取了crt证书和key内容使用:

openssl pkcs12 -in ./server.pfx -clcerts -nokeys -out ./server.crt 
openssl pkcs12 -in ./server.pfx -nocerts -nodes -out ./server.key

如果上面命令报错类似下面这样,则加上-legacy参数:

Error outputting keys and certificates
4077214A00710000:error:0308010C:digital envelope routines..............

post关键代码:

//SSLCert: server.crt的base64
certPem, err := base64.StdEncoding.DecodeString(SSLCert)
if err != nil {
    return "", err
}
//SSLKey: server.key的base64
keyPem, err := base64.StdEncoding.DecodeString(SSLKey)
if err != nil {
    return "", err
}
cert, err := tls.X509KeyPair(certPem, keyPem)
if err != nil {
    return "", err
}

client := &http.Client{
    Transport: &http.Transport{
        TLSClientConfig: &tls.Config{
            //Certificates和RootCAs二选一即可
            Certificates: []tls.Certificate{cert},
            //RootCAs:            caCertPool,
            InsecureSkipVerify: true,
            MinVersion:         tls.VersionTLS12,
        },
    },
}
contentType := "application/json; charset=utf-8"
rawData := strings.NewReader(requestData)
resp, err := client.Post(reqUrl, contentType, rawData)
if err != nil {
    return "", err
}

3. 解析响应

  • 解密:p7crypto.GetDecryptData,用商户私钥
  • 验证签名:p7crypto.GetVerifyResult,上一步解密得到的数据,用快钱公钥验证

3、 支付成功异步通知验签

提取快钱证书里面的公钥,用rsa.VerifyPKCS1v15验证签名。

H5的回调待签名字符串排序是按照文档给出的字段顺序。

SDK网关支付的待签名字段排序是字典序从小到大。

关键代码:

// KQCert 快钱公钥
certBytes, err := base64.StdEncoding.DecodeString(KQCert)
if err != nil {
    return err
}

// "encoding/pem"
block, _ := pem.Decode(certBytes)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
    return err
}

log.Info("signStr = ", signStr)
pubkey, ok := cert.PublicKey.(*rsa.PublicKey)
if !ok {
    return errors.New("parse public key fail")
}
// signStr 待签名字符串,按文档字段顺序把异步通知数据组合成query格式的字符串
hashed := sha256.Sum256([]byte(signStr))
// signByte 异步通知数据的签名,base64 decode得到
return rsa.VerifyPKCS1v15(pubkey, crypto.SHA256, hashed[:], signByte)

4、小程序调起支付

快钱小程序支付是通过小程序方法navigateToMiniProgram来调起的。

三个关键参数:

  • appId:下单的接口返回数据的路径mpayInfo.appletInfo.kqWechatAppletAppId
  • envVersion:按需填,小程序文档里面列出的值
  • path: 格式化值得代码如下
// appInfo和jsonObj都是用gjson库解析下单返回数据后的对象
fmt.Sprintf(
    "%s?order=%s&appletInfo=%s&envVersion=%s",
    appInfo.Get("kqWechatAppletAppUrl").String(), // mpayInfo.appletInfo.kqWechatAppletAppUrl
    jsonObj.Get("mpayInfo.orderRequestInfo.orderRequestKey").String(),
    jsonObj.Get("mpayInfo.appletInfo").String(),
    "release", //可选值和envVersion一样
)

本文链接:https://360us.net/article/100.html