共计 22582 个字符,预计需要花费 57 分钟才能阅读完成。
一、筹备阶段
pc&h5 接入步骤
官网文档 https://payapi.jd.com/docList…
查看次要接入步骤
密钥生成
• 须要设置 desc key
• md5 key 和 app id app 对接会应用
• 证书文件名称
my_rsa_private_pkcs8_key.pem
wy_rsa_public_key.pem
示例程序应用私钥格局为 pkcs8 格局
官网的 SDK 中的数据能够在示例程序中应用
下载 SDK 地址 https://payapi.jd.com/docList…
找到接口文档中的 Demo
还会用到的包
import (
"encoding/base64"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"time"
)
加密、解密、验证签名
package main
import (
"bytes"
"crypto"
"crypto/des"
cryptoRand "crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"math/rand"
"regexp"
"sort"
"strings"
"time"
)
func randNumber() string {return fmt.Sprintf("%05v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(100000))
}
func checkSign(decryptBytes []byte, sign, publicKey string) bool {decrypt := string(decryptBytes)
clipStartIndex := strings.Index(decrypt, "<sign>")
clipEndIndex := strings.Index(decrypt, "</sign>")
xmlStart := decrypt[0:clipStartIndex]
xmlEnd := decrypt[clipEndIndex+7 : len(decrypt)]
originXml := xmlStart + xmlEnd
// 签名校验
if sign == "" {return false}
return checkRsaSign(originXml, publicKey, sign)
}
func replaceXmlStrBlankChar(str string) string {str = strings.Replace(str, "\r", "", -1)
str = strings.Replace(str, "\n", "", -1)
str = strings.Replace(str, "\t", "", -1)
reg, _ := regexp.Compile(">\\s+<")
str = reg.ReplaceAllString(str, "><")
reg, _ = regexp.Compile("\\s+\\/>")
str = reg.ReplaceAllString(str, "/>")
return str
}
func getPaySign(paramMap map[string]string, privateKey string) (string, error) {payString := getSortString(paramMap)
return getRsaSign(payString, privateKey)
}
// ------
// 加密
func encrypt3DES(paramMap map[string]string, desKey string) (map[string]string, error) {desKey = base64DecodeStr(desKey)
for k, v := range paramMap {
if k == "sign" || k == "merchant" || k == "version" {continue}
encrypt, err := tripleEcbDesEncrypt([]byte(v), []byte(desKey))
if err != nil {return paramMap, err}
paramMap[k] = decimalByteSlice2HexString(encrypt)
}
return paramMap, nil
}
func decryptArg(notifyQuery NotifyQuery, desKey string) (decryptBytes []byte, err error) {desKeyBytes, err := base64.StdEncoding.DecodeString(desKey)
if err != nil {return nil, err}
encryptBytes, err := base64.StdEncoding.DecodeString(notifyQuery.Encrypt)
if err != nil {return nil, err}
encryptBytes, err = hexString2Bytes(string(encryptBytes))
if err != nil {return nil, err}
decryptBytes, err = tripleEcbDesDecrypt(encryptBytes, desKeyBytes)
if err != nil {return nil, err}
return decryptBytes, nil
}
// JDAPP 填充规定
func jdPadding(origData []byte) []byte {merchantData := len(origData)
x := (merchantData + 4) % 8
y := 0
if x == 0 {y = 0} else {y = 8 - x}
sizeByte := integerToBytes(merchantData)
var resultByte []byte
// 填充 byte 数据长度
for i := 0; i < 4; i++ {resultByte = append(resultByte, sizeByte[i])
}
// 填充原数据长度
for j := 0; j < merchantData; j++ {resultByte = append(resultByte, origData[j])
}
// 填充 0
for k := 0; k < y; k++ {resultByte = append(resultByte, 0x00)
}
return resultByte
}
func jdUnPadding(unPaddingResult []byte) []byte {var Result []byte
var dataSizeByte []byte
for i := 0; i < 4; i++ {dataSizeByte = append(dataSizeByte, unPaddingResult[i])
}
decimalDataSize := byteArrayToInt(dataSizeByte)
for j := 0; j < decimalDataSize; j++ {Result = append(Result, unPaddingResult[4+j])
}
return Result
}
// 字节数组示意的理论长度
func byteArrayToInt(dataSizeByte []byte) int {
value := 0
for i := 0; i < 4; i++ {shift := byte((4 - 1 - i) * 8)
value = value + int(dataSizeByte[i]&0x000000FF)<<shift
}
return value
}
func integerToBytes(val int) [4]byte {byt := [4]byte{}
byt[0] = byte(val >> 24 & 0xff)
byt[1] = byte(val >> 16 & 0xff)
byt[2] = byte(val >> 8 & 0xff)
byt[3] = byte(val & 0xff)
return byt
}
// byte 转 16 进制字符串
func decimalByteSlice2HexString(DecimalSlice []byte) string {var sa = make([]string, 0)
for _, v := range DecimalSlice {sa = append(sa, fmt.Sprintf("%02X", v))
}
ss := strings.Join(sa, "")
return ss
}
// 十六进制字符串转 byte
func hexString2Bytes(str string) ([]byte, error) {Bys, err := hex.DecodeString(str)
if err != nil {return nil, err}
return Bys, nil
}
// base 解码
func base64DecodeStr(src string) string {a, err := base64.StdEncoding.DecodeString(src)
if err != nil {return ""}
return string(a)
}
// Des 解密
func decrypt(crypted, key []byte) ([]byte, error) {if len(crypted) < 1 || len(key) < 1 {return nil, errors.New("wrong data or key")
}
block, err := des.NewCipher(key)
if err != nil {return nil, err}
out := make([]byte, len(crypted))
dst := out
bs := block.BlockSize()
if len(crypted)%bs != 0 {return nil, errors.New("wrong crypted size")
}
for len(crypted) > 0 {block.Decrypt(dst, crypted[:bs])
crypted = crypted[bs:]
dst = dst[bs:]
}
return out, nil
}
//
func tripleEcbDesDecrypt(crypted, key []byte) ([]byte, error) {tkey := make([]byte, 24, 24)
copy(tkey, key)
k1 := tkey[:8]
k2 := tkey[8:16]
k3 := tkey[16:]
buf1, err := decrypt(crypted, k3)
if err != nil {return nil, err}
buf2, err := encrypt(buf1, k2)
if err != nil {return nil, err}
out, err := decrypt(buf2, k1)
if err != nil {return nil, err}
out = jdUnPadding(out)
return out, nil
}
// sha256 加密
func hasha256(str string) string {h := sha256.New()
h.Write([]byte(str))
cipherStr := h.Sum(nil)
//return cipherStr
return hex.EncodeToString(cipherStr)
}
// base 解编码
func base64EncodeStr(src string) string {return base64.StdEncoding.EncodeToString([]byte(src))
}
// 对音讯的散列值进行数字签名
func signPKCS1v15(msg, privateKey []byte, hashType crypto.Hash) ([]byte, error) {block, _ := pem.Decode(privateKey)
if block == nil {return nil, errors.New("private key format error")
}
pri, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {return nil, errors.New("parse private key error")
}
key, ok := pri.(*rsa.PrivateKey)
if ok == false {return nil, errors.New("private key format error")
}
sign, err := rsa.SignPKCS1v15(cryptoRand.Reader, key, hashType, msg)
if err != nil {return nil, errors.New("sign error")
}
return sign, nil
}
// Des 加密
func encrypt(origData, key []byte) ([]byte, error) {if len(origData) < 1 || len(key) < 1 {return nil, errors.New("wrong data or key")
}
block, err := des.NewCipher(key)
if err != nil {return nil, err}
bs := block.BlockSize()
if len(origData)%bs != 0 {return nil, errors.New("wrong padding")
}
out := make([]byte, len(origData))
dst := out
for len(origData) > 0 {block.Encrypt(dst, origData[:bs])
origData = origData[bs:]
dst = dst[bs:]
}
return out, nil
}
//
func tripleEcbDesEncrypt(origData, key []byte) ([]byte, error) {tkey := make([]byte, 24, 24)
copy(tkey, key)
k1 := tkey[:8]
k2 := tkey[8:16]
k3 := tkey[16:]
origData = jdPadding(origData) // PKCS5Padding(origData, bs)
buf1, err := encrypt(origData, k1)
if err != nil {return nil, err}
buf2, err := decrypt(buf1, k2)
if err != nil {return nil, err}
out, err := encrypt(buf2, k3)
if err != nil {return nil, err}
return out, nil
}
// ------------
// 验证签名
func verifyPKCS1v15(msg, sign, publicKey []byte, hashType crypto.Hash) bool {block, _ := pem.Decode(publicKey)
if block == nil {return false}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {panic(err)
}
err = rsa.VerifyPKCS1v15(pub.(*rsa.PublicKey), hashType, msg, sign)
return err == nil
}
func getRsaSign(paramStr string, privateKey string) (string, error) {sha256Str := hasha256(paramStr)
sign, err := signPKCS1v15([]byte(sha256Str), []byte(privateKey), crypto.Hash(0))
if err != nil {return "", err}
base64String := base64.StdEncoding.EncodeToString(sign)
return base64String, nil
}
func checkRsaSign(paramStr string, publicKey, sign string) bool {signByte, err := base64.StdEncoding.DecodeString(sign)
if err != nil {return false}
sha256Str := hasha256(paramStr)
return verifyPKCS1v15([]byte(sha256Str), signByte, []byte(publicKey), crypto.Hash(0))
}
// -------
// 字符串拼接
// 领取字符串拼接
func getSortString(m map[string]string) string {
var buf bytes.Buffer
keys := make([]string, 0, len(m))
for k := range m {keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {vs := m[k]
if buf.Len() > 0 {buf.WriteByte('&')
}
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(vs)
}
return buf.String()}
程序中加载密钥
func getKey(keyType string) (string, error) {keyMap := map[string]string{
"private_key": "./private.pem",
"public_key": "./public.pem",
}
path, ok := keyMap[keyType]
if !ok {return "", errors.New("key path not exists")
}
fileHandler, err := os.Open(path)
if err != nil {return "", err}
defer fileHandler.Close()
keyBytes, err := ioutil.ReadAll(fileHandler)
if err != nil {return "", err}
return string(keyBytes), nil
}
二、发动领取
常量
常量
const version = "V2.0" // 京东领取版本
const merchantId = "" // 商户 id
const desKey = "" // desc key
const timeLayout = "20060102150405"
const cny = "CNY"
const practicalityGoodsType = "GT01" // 商品类型 - 实物
const businessServiceConsumeCode = "100001"
const practicality = "0" // 商品类型 - 实物
调用在线领取接口
pc h5 发动领取
官网文档 https://payapi.jd.com/docList…
type Order struct {
OrderId string `json:"order_id"`
Amount float64 `json:"amount"`
Items []*OrderItem `json:"items"`
ShippingAddress *OrderShippingAddress `json:"shipping_address"`
}
type OrderItem struct {
GoodsNo string `json:"goods_no"`
GoodsName string `json:"goods_name"`
GoodsPrice float64 `json:"goods_price"`
GoodsNum uint32 `json:"goods_num"`
}
type OrderShippingAddress struct {
Name string `json:"name"`
Mobile string `json:"mobile"`
Address string `json:"address"`
Province string `json:"province"`
City string `json:"city"`
Country string `json:"country"`
}
type ReqWithEncrypt struct {
XMLName xml.Name `xml:"jdpay" json:"-"`
Version string `xml:"version" json:"version"` // 版本
Merchant string `xml:"merchant" json:"merchant"` // 商户号
Encrypt string `xml:"encrypt" json:"encrypt"` // 加密数据
}
const version = "V2.0" // 京东领取版本
const merchantId = "22294531"
const desKey = "ta4E/aspLA3lgFGKmNDNRYU92RkZ4w2t"
const timeLayout = "20060102150405"
const cny = "CNY"
const practicalityGoodsType = "GT01" // 商品类型 - 实物
const businessServiceConsumeCode = "100001"
const practicality = "0" // 商品类型 - 实物
type GoodsInfo struct {
Id string `json:"id"` // 商品编号
Name string `json:"name"` // 商品名称
Price int64 `json:"price"` // 商品单价,单位分
Num uint32 `json:"num"` // 商品数量
Type string `json:"type"` // 商品类型
}
type ReceiverInfo struct {
Name string `json:"name"`
Address string `json:"address"`
Mobile string `json:"mobile"`
Province string `json:"province"`
City string `json:"city"`
Country string `json:"country"`
}
type KjInfo struct {
GoodsSubmittedCustoms string `json:"goodsSubmittedCustoms"` // 是否报关 Y /N
GoodsUnderBonded string `json:"goodsUnderBonded"` // 是否保税货物项下付款 Y /N
}
const jdPayUrl = "https://wepay.jd.com/jdpay/saveOrder"
// pc h5 form 表单提交领取
func postFormPay(arg Order) (payStr string, err error) {
// 领取参数
paramMap := make(map[string]string)
amountStr := fmt.Sprintf("%.f", arg.Amount*100)
totalFee, err := strconv.ParseInt(amountStr, 10, 64)
if err != nil {return payStr, err}
var goodsInfos []GoodsInfo
for _, v := range arg.Items {priceStr := fmt.Sprintf("%.f", v.GoodsPrice*100)
price, err := strconv.ParseInt(priceStr, 10, 64)
if err != nil {return payStr, err}
goodsInfos = append(goodsInfos, GoodsInfo{
Id: v.GoodsNo,
Name: v.GoodsName,
Price: price,
Num: v.GoodsNum,
Type: practicalityGoodsType, // 商品类型 - 实物
})
}
goodsInfoBytes, _ := json.Marshal(goodsInfos)
kjInfo := KjInfo{GoodsSubmittedCustoms: "N", GoodsUnderBonded: "N"}
kjInfoBytes, _ := json.Marshal(kjInfo)
detailAddress := arg.ShippingAddress.Province + arg.ShippingAddress.City + arg.ShippingAddress.Country + arg.ShippingAddress.Address
receiverInfo := ReceiverInfo{
Name: arg.ShippingAddress.Name, // 收货人姓名
Mobile: arg.ShippingAddress.Mobile, // 收货人手机号
Address: detailAddress, // 地址要求包过省市区
Province: arg.ShippingAddress.Province, // 省
City: arg.ShippingAddress.City, // 市
Country: arg.ShippingAddress.Country, // 区
}
receiverInfoBytes, _ := json.Marshal(receiverInfo)
orderId := fmt.Sprintf("test%s%s", time.Now().Format("20060102150405"), randNumber())
paramMap["version"] = version
paramMap["merchant"] = merchantId
paramMap["tradeNum"] = orderId // 订单号
paramMap["tradeName"] = orderId // 订单形容
paramMap["tradeDesc"] = orderId // 订单形容
paramMap["payMerchant"] = "test" // 商户名称
paramMap["tradeTime"] = time.Now().Format(timeLayout)
paramMap["amount"] = fmt.Sprintf("%v", totalFee)
paramMap["orderType"] = practicality
paramMap["currency"] = cny
paramMap["userId"] = "100"
paramMap["expireTime"] = "3600" // 订单生效时长,单位秒
paramMap["goodsInfo"] = string(goodsInfoBytes)
paramMap["settleCurrency"] = cny
paramMap["kjInfo"] = string(kjInfoBytes)
paramMap["bizTp"] = businessServiceConsumeCode
paramMap["notifyUrl"] = "http://tools.localhost/notify"
paramMap["callbackUrl"] = "http://tools.localhost/verify"
paramMap["receiverInfo"] = string(receiverInfoBytes)
// 证书
privateKey, err := getKey("private_key")
if err != nil {return payStr, err}
// 签名
paramMap["sign"], err = getPaySign(paramMap, privateKey)
if err != nil {return payStr, err}
// 数据加密
paramMap, err = encrypt3DES(paramMap, desKey)
if err != nil {return payStr, err}
// 拼接领取表单
payStr = "<form action='" + jdPayUrl + "'method='post'id='pay_form'>"
for k, v := range paramMap {payStr += "<input value='" + v + "'name='" + k + "'type='hidden'/>"}
payStr += "</form>"
payStr += "<script>var form = document.getElementById('pay_form');form.submit()</script>"
return payStr, nil
}
三、异步告诉
数据解密、签名校验
// 异步告诉信息解密
type NotifyQuery struct {
XMLName xml.Name `xml:"jdpay" json:"-"`
Version string `xml:"version" json:"version"` // 版本号
Merchant string `xml:"merchant" json:"merchant"` // 商户号
Result NotifyResult `xml:"result" json:"result"` // 交易后果
Encrypt string `xml:"encrypt" json:"encrypt"` // 加密信息
}
type NotifyDecrypt struct {
XMLName xml.Name `xml:"jdpay" json:"-"`
Version string `xml:"version" json:"version"` // 版本号
Merchant string `xml:"merchant" json:"merchant"` // 商户号
Result NotifyResult `xml:"result" json:"result"` // 交易后果
TradeNum string `xml:"tradeNum" json:"tradeNum"` // 订单号
TradeType int `xml:"tradeType" json:"tradeType"` // 交易类型
Sign string `xml:"sign" json:"sign"` // 数据签名
Amount int64 `xml:"amount" json:"amount"` // 人民币领取总金额
OrderId string `json:"order_id"` // 京东交易流水号
Status string `xml:"status" json:"status"` // 交易状态
PayList NotifyPayList `xml:"payList" json:"payList"` // 领取形式明细
}
type NotifyResult struct {
Code string `xml:"code" json:"code"` // 交易返回码
Desc string `xml:"desc" json:"desc"` // 返回码信息
}
type NotifyPayList struct {Pay []NotifyPay `xml:"pay" json:"pay"`
}
type NotifyPay struct {
PayType int `xml:"payType" json:"payType"` // 领取形式
Amount int64 `xml:"amount" json:"amount"` // 交易金额
Currency string `xml:"currency" json:"currency"` // 交易币种
TradeTime string `xml:"tradeTime" json:"tradeTime"` // 交易工夫
}
// 异步告诉信息解密
func notifyDataDecrypt(rawPost string) (notifyDecrypt NotifyDecrypt, err error) {
// 解析加密的领取机构参数为构造体
var notifyQuery NotifyQuery
err = xml.Unmarshal([]byte(rawPost), ¬ifyQuery)
if err != nil {return notifyDecrypt, err}
// 解密领取机构参数
decryptBytes, err := decryptArg(notifyQuery, desKey)
if err != nil {return notifyDecrypt, err}
// 解析解密后的领取机构参数为构造体
err = xml.Unmarshal(decryptBytes, ¬ifyDecrypt)
if err != nil {return notifyDecrypt, err}
// 证书
publicKey, err := getKey("public_key")
if err != nil {return notifyDecrypt, err}
// 校验签名
if !checkSign(decryptBytes, notifyDecrypt.Sign, publicKey) {return notifyDecrypt, err}
return notifyDecrypt, nil
}
四、交易查问
查问订单
type SearchWithoutSignRequest struct {
XMLName xml.Name `xml:"jdpay" json:"-"`
Version string `xml:"version" json:"version"` // 版本
Merchant string `xml:"merchant" json:"merchant"` // 商户号
TradeNum string `xml:"tradeNum" json:"tradeNum"` // 订单编号
OTradeNum string `xml:"oTradeNum" json:"oTradeNum"` // 原交易流水号
TradeType string `xml:"tradeType" json:"tradeType"` // 交易类型
}
type SearchWithSignRequest struct {
XMLName xml.Name `xml:"jdpay" json:"-"`
Version string `xml:"version" json:"version"` // 版本
Merchant string `xml:"merchant" json:"merchant"` // 商户号
TradeNum string `xml:"tradeNum" json:"tradeNum"` // 订单编号
OTradeNum string `xml:"oTradeNum" json:"oTradeNum"` // 原交易流水号
TradeType string `xml:"tradeType" json:"tradeType"` // 交易类型
Sign string `xml:"sign" json:"sign"` // 签名
}
type SearchResult struct {
XMLName xml.Name `xml:"jdpay" json:"-"`
Version string `xml:"version" json:"version"` // 版本号
Merchant string `xml:"merchant" json:"merchant"` // 商户号
Result SearchResultRsp `xml:"result" json:"result"` // 交易后果
Encrypt string `xml:"encrypt" json:"encrypt"` // 加密信息
}
type SearchResultRsp struct {
Code string `xml:"code" json:"code"` // 交易返回码
Desc string `xml:"desc" json:"desc"` // 返回码信息
}
type SearchDecryptRsp struct {
XMLName xml.Name `xml:"jdpay" json:"-"`
Merchant string `xml:"merchant" json:"merchant"` // 商户号
TradeNum string `xml:"tradeNum" json:"tradeNum"` // 订单编号
TradeType string `xml:"tradeType" json:"tradeType"` // 交易类型
Result SearchResultRsp `xml:"result" json:"result"` // 交易后果
Sign string `xml:"sign" json:"sign"` // 数据签名
Amount int64 `xml:"amount" json:"amount"` // 人民币领取总金额
Status string `xml:"status" json:"status"` // 交易状态
PayList SearchPayListRsp `xml:"payList" json:"payList"` // 领取形式明细
}
type SearchPayListRsp struct {Pay []SearchPayRsp `xml:"pay" json:"pay"`
}
type SearchPayRsp struct {
PayType int `xml:"payType" json:"payType"` // 领取形式
Amount int64 `xml:"amount" json:"amount"` // 交易金额
Currency string `xml:"currency" json:"currency"` // 交易币种
TradeTime string `xml:"tradeTime" json:"tradeTime"` // 交易工夫
}
const tradeWayUrl = "https://paygate.jd.com/service/query"
const customTradeType = "0" // 交易类型
const successCode = "000000"
func searchTrade(orderId string) (searchDecryptRsp SearchDecryptRsp, err error) {
searchWithoutSignRequest := SearchWithoutSignRequest{
Version: version,
Merchant: merchantId,
TradeNum: orderId,
OTradeNum: "",
TradeType: customTradeType,
}
xmlBytes, err := xml.Marshal(searchWithoutSignRequest)
xmlStr := xml.Header + string(xmlBytes)
xmlStr = replaceXmlStrBlankChar(xmlStr)
// 证书
privateKey, err := getKey("private_key")
if err != nil {return searchDecryptRsp, err}
// 签名
sign, err := getRsaSign(xmlStr, privateKey)
if err != nil {return searchDecryptRsp, err}
searchWithSignRequest := SearchWithSignRequest{
Version: searchWithoutSignRequest.Version,
Merchant: searchWithoutSignRequest.Merchant,
TradeNum: searchWithoutSignRequest.TradeNum,
OTradeNum: searchWithoutSignRequest.OTradeNum,
TradeType: searchWithoutSignRequest.TradeType,
Sign: sign,
}
xmlBytes, err = xml.Marshal(searchWithSignRequest)
xmlStr = strings.TrimRight(xml.Header, "\n") + string(xmlBytes)
desKeyBytes, err := base64.StdEncoding.DecodeString(desKey)
if err != nil {return searchDecryptRsp, err}
encryptBytes, err := tripleEcbDesEncrypt([]byte(xmlStr), desKeyBytes)
if err != nil {return searchDecryptRsp, err}
reqEncrypt := decimalByteSlice2HexString(encryptBytes)
reqEncrypt = base64.StdEncoding.EncodeToString([]byte(reqEncrypt))
searchWithEncrypt := ReqWithEncrypt{
Version: version,
Merchant: merchantId,
Encrypt: reqEncrypt,
}
xmlBytes, err = xml.Marshal(searchWithEncrypt)
if err != nil {return searchDecryptRsp, err}
xmlStr = strings.TrimRight(xml.Header, "\n") + string(xmlBytes)
request, err := http.NewRequest(http.MethodPost, tradeWayUrl, strings.NewReader(xmlStr))
if err != nil {return searchDecryptRsp, err}
request.Header.Add("content-type", "application/xml; charset=utf-8")
client := http.DefaultClient
response, err := client.Do(request)
if err != nil {return searchDecryptRsp, err}
defer response.Body.Close()
bodyBytes, err := ioutil.ReadAll(response.Body)
if err != nil {return searchDecryptRsp, err}
searchResult := new(SearchResult)
if err = xml.Unmarshal(bodyBytes, searchResult); err != nil {return searchDecryptRsp, err}
if searchResult.Result.Code != successCode {return searchDecryptRsp, errors.New(searchResult.Result.Desc)
}
// 解密数据
rspEncryptBytes, err := base64.StdEncoding.DecodeString(searchResult.Encrypt)
rspEncryptBytes, err = hexString2Bytes(string(rspEncryptBytes))
if err != nil {return searchDecryptRsp, err}
rspDecryptBytes, err := tripleEcbDesDecrypt(rspEncryptBytes, desKeyBytes)
if err != nil {return searchDecryptRsp, err}
err = xml.Unmarshal(rspDecryptBytes, &searchDecryptRsp)
if err != nil {return searchDecryptRsp, err}
// 证书
publicKey, err := getKey("public_key")
if err != nil {return searchDecryptRsp, err}
// 校验签名
if !checkSign(rspDecryptBytes, searchDecryptRsp.Sign, publicKey) {return searchDecryptRsp, err}
return searchDecryptRsp, nil
}
五、申请退款
申请退款
// 退款
type Refund struct {
Merchant string `json:"merchant"`
TradeNum string `json:"tradeNum"`
OTradeNum string `json:"oTradeNum"`
Amount uint64 `json:"amount"`
Currency string `json:"currency"`
DesKey string `json:"desKey"`
PublicKey string `json:"publicKey"`
PrivateKey string `json:"privateKey"`
}
type RefundReqWithoutSign struct {
XMLName xml.Name `xml:"jdpay" json:"-"`
Version string `xml:"version" json:"version"`
Merchant string `xml:"merchant" json:"merchant"`
TradeNum string `xml:"tradeNum" json:"tradeNum"`
OTradeNum string `xml:"oTradeNum" json:"oTradeNum"`
Amount uint64 `xml:"amount" json:"amount"`
Currency string `xml:"currency" json:"currency"`
}
type RefundReqWithSign struct {
XMLName xml.Name `xml:"jdpay" json:"-"`
Version string `xml:"version" json:"version"`
Merchant string `xml:"merchant" json:"merchant"`
TradeNum string `xml:"tradeNum" json:"tradeNum"`
OTradeNum string `xml:"oTradeNum" json:"oTradeNum"`
Amount uint64 `xml:"amount" json:"amount"`
Currency string `xml:"currency" json:"currency"`
Sign string `xml:"sign" json:"sign"`
}
type RefundResult struct {
XMLName xml.Name `xml:"jdpay" json:"-"`
Version string `xml:"version" json:"version"` // 版本号
Merchant string `xml:"merchant" json:"merchant"` // 商户号
Result RefundPayResultRsp `xml:"result" json:"result"` // 退款后果
Encrypt string `xml:"encrypt" json:"encrypt"` // 加密信息
}
type RefundPayResultRsp struct {
Code string `xml:"code" json:"code"` // 交易返回码
Desc string `xml:"desc" json:"desc"` // 返回码信息
}
type RefundPayDecryptRsp struct {
XMLName xml.Name `xml:"jdpay" json:"-"`
Version string `xml:"version" json:"version"` // 版本号
Merchant string `xml:"merchant" json:"merchant"` // 商户号
TradeNum string `xml:"tradeNum" json:"tradeNum"`
TradeType string `xml:"tradeType"json:"tradeType"`
Result RefundPayResultRsp `xml:"result" json:"result"` // 退款后果
Sign string `xml:"sign" json:"sign"`
Amount uint64 `xml:"amount" json:"amount"`
Currency string `xml:"currency" json:"currency"`
TradeTime string `xml:"tradeTime" json:"tradeTime"`
Status string `xml:"status" json:"status"`
}
const refundGatewayUrl = "https://paygate.jd.com/service/refund"
// 申请退款
func refundTrade(orderId string, amount float64) (refundPayDecryptRsp RefundPayDecryptRsp, err error) {totalFee, err := strconv.ParseUint(fmt.Sprintf("%.f", amount*100), 10, 64)
if err != nil {return refundPayDecryptRsp, err}
refundReqWithoutSign := RefundReqWithoutSign{
Version: version,
Merchant: merchantId,
TradeNum: orderId + "-1",
OTradeNum: orderId,
Amount: totalFee,
Currency: cny,
}
xmlBytes, err := xml.Marshal(refundReqWithoutSign)
xmlStr := xml.Header + string(xmlBytes)
xmlStr = replaceXmlStrBlankChar(xmlStr)
// 证书
privateKey, err := getKey("private_key")
if err != nil {return refundPayDecryptRsp, err}
// 签名
sign, err := getRsaSign(xmlStr, privateKey)
if err != nil {return refundPayDecryptRsp, err}
refundReqWithSign := RefundReqWithSign{
Version: refundReqWithoutSign.Version,
Merchant: refundReqWithoutSign.Merchant,
TradeNum: refundReqWithoutSign.TradeNum,
OTradeNum: refundReqWithoutSign.OTradeNum,
Amount: refundReqWithoutSign.Amount,
Currency: refundReqWithoutSign.Currency,
Sign: sign,
}
xmlBytes, err = xml.Marshal(refundReqWithSign)
xmlStr = strings.TrimRight(xml.Header, "\n") + string(xmlBytes)
desKeyBytes, err := base64.StdEncoding.DecodeString(desKey)
if err != nil {return refundPayDecryptRsp, err}
encryptBytes, err := tripleEcbDesEncrypt([]byte(xmlStr), desKeyBytes)
if err != nil {return refundPayDecryptRsp, err}
reqEncrypt := decimalByteSlice2HexString(encryptBytes)
reqEncrypt = base64.StdEncoding.EncodeToString([]byte(reqEncrypt))
refundReqWithEncrypt := ReqWithEncrypt{
Version: version,
Merchant: merchantId,
Encrypt: reqEncrypt,
}
xmlBytes, err = xml.Marshal(refundReqWithEncrypt)
xmlStr = strings.TrimRight(xml.Header, "\n") + string(xmlBytes)
request, err := http.NewRequest(http.MethodPost, refundGatewayUrl, strings.NewReader(xmlStr))
if err != nil {return refundPayDecryptRsp, err}
request.Header.Add("content-type", "application/xml; charset=utf-8")
client := http.DefaultClient
response, err := client.Do(request)
if err != nil {return refundPayDecryptRsp, err}
defer response.Body.Close()
bodyBytes, err := ioutil.ReadAll(response.Body)
if err != nil {return refundPayDecryptRsp, err}
refundResult := new(RefundResult)
if err = xml.Unmarshal(bodyBytes, refundResult); err != nil {return refundPayDecryptRsp, err}
// 解密数据
rspEncryptBytes, err := base64.StdEncoding.DecodeString(refundResult.Encrypt)
if err != nil {return refundPayDecryptRsp, err}
rspEncryptBytes, err = hexString2Bytes(string(rspEncryptBytes))
if err != nil {return refundPayDecryptRsp, err}
rspDecryptBytes, err := tripleEcbDesDecrypt(rspEncryptBytes, desKeyBytes)
if err != nil {return refundPayDecryptRsp, err}
err = xml.Unmarshal(rspDecryptBytes, &refundPayDecryptRsp)
if err != nil {return refundPayDecryptRsp, err}
// 证书
publicKey, err := getKey("public_key")
if err != nil {return refundPayDecryptRsp, err}
// 校验签名
if !checkSign(rspDecryptBytes, refundPayDecryptRsp.Sign, publicKey) {return refundPayDecryptRsp, err}
return refundPayDecryptRsp, nil
}
正文完