First, the preparation stage

Pc&h 5 Access procedure

The official documentation payapi.jd.com/docList.htm… View main access steps

Key generation

• Need to set the DESC key • MD5 key and APP ID • Name of the certificate file used for app interconnect

my_rsa_private_pkcs8_key.pem
wy_rsa_public_key.pem
Copy the code

The sample program uses the private key format pkCS8

The official data can be used in the sample program in the SDK download the SDK address payapi.jd.com/docList.htm… Find the Demo in the interface documentation

Bags you’ll also need

import (
    "encoding/base64"
    "encoding/json"
    "encoding/xml"
    "errors"
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
    "strconv"
    "strings"
    "time"
)
Copy the code

Encrypt, decrypt, and verify signatures

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(a) 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
    // Signature verification
    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)
}
// ------
/ / encryption
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))
        iferr ! =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)
    iferr ! =nil {
        return nil, err
    }
    encryptBytes, err := base64.StdEncoding.DecodeString(notifyQuery.Encrypt)
    iferr ! =nil {
        return nil, err
    }
    encryptBytes, err = hexString2Bytes(string(encryptBytes))
    iferr ! =nil {
        return nil, err
    }
    decryptBytes, err = tripleEcbDesDecrypt(encryptBytes, desKeyBytes)
    iferr ! =nil {
        return nil, err
    }
    return decryptBytes, nil
}
// JDAPP populates rules
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
    // Padding the length of byte data
    for i := 0; i < 4; i++ {
        resultByte = append(resultByte, sizeByte[i])
    }
    // Fill the length of the original data
    for j := 0; j < merchantData; j++ {
        resultByte = append(resultByte, origData[j])
    }
    / / fill 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
}
// The actual length of the byte array
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 to a hexadecimal character string
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
}
// The hexadecimal string is converted to byte
func hexString2Bytes(str string) ([]byte, error) {
    Bys, err := hex.DecodeString(str)
    iferr ! =nil {
        return nil, err
    }
    return Bys, nil
}
/ / base decoding
func base64DecodeStr(src string) string {
    a, err := base64.StdEncoding.DecodeString(src)
    iferr ! =nil {
        return ""
    }
    return string(a)
}
/ / Des declassified
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)
    iferr ! =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
}
// [golang ECB 3DES Decrypt]
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)
    iferr ! =nil {
        return nil, err
    }
    buf2, err := encrypt(buf1, k2)
    iferr ! =nil {
        return nil, err
    }
    out, err := decrypt(buf2, k1)
    iferr ! =nil {
        return nil, err
    }
    out = jdUnPadding(out)
    return out, nil
}
/ / sha256 encryption
func hasha256(str string) string {
    h := sha256.New()
    h.Write([]byte(str))
    cipherStr := h.Sum(nil)
    //return cipherStr
    return hex.EncodeToString(cipherStr)
}
// base unencode
func base64EncodeStr(src string) string {
    return base64.StdEncoding.EncodeToString([]byte(src))
}
// Digitally sign the hash value of the message
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)
    iferr ! =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)
    iferr ! =nil {
        return nil, errors.New("sign error")}return sign, nil
}
/ / Des encryption
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)
    iferr ! =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
}
// [golang ECB 3DES Encrypt]
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)
    iferr ! =nil {
        return nil, err
    }
    buf2, err := decrypt(buf1, k2)
    iferr ! =nil {
        return nil, err
    }
    out, err := encrypt(buf2, k3)
    iferr ! =nil {
        return nil, err
    }
    return out, nil
}
// ------------
// Verify the signature
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)
    iferr ! =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))
    iferr ! =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)
    iferr ! =nil {
        return false
    }
    sha256Str := hasha256(paramStr)
    return verifyPKCS1v15([]byte(sha256Str), signByte, []byte(publicKey), crypto.Hash(0))}// -------
// String concatenation
// Pay string concatenation
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()
}
Copy the code

Program to load the key

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)
    iferr ! =nil {
        return "", err
    }
    defer fileHandler.Close()
    keyBytes, err := ioutil.ReadAll(fileHandler)
    iferr ! =nil {
        return "", err
    }
    return string(keyBytes), nil
}
Copy the code

2. Initiate payment

constant

constantconst version = "V2.0" // Jd Pay version
const merchantId = ""  / / merchant id
const desKey = ""      // desc key
const timeLayout = "20060102150405"
const cny = "CNY"
const practicalityGoodsType = "GT01" // Item type - physical object
const businessServiceConsumeCode = "100001"
const practicality = "0" // Item type - physical object
Copy the code

Invoke the online payment interface

PC h5 payapi.jd.com/docList.htm initiate payment official document…

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"`   / / version
    Merchant string   `xml:"merchant" json:"merchant"` / / merchants
    Encrypt  string   `xml:"encrypt" json:"encrypt"`   // Encrypt data
}
const version = "V2.0" // Jd Pay version
const merchantId = "22294531"
const desKey = "ta4E/aspLA3lgFGKmNDNRYU92RkZ4w2t"
const timeLayout = "20060102150405"
const cny = "CNY"
const practicalityGoodsType = "GT01" // Item type - physical object
const businessServiceConsumeCode = "100001"
const practicality = "0" // Item type - physical object
type GoodsInfo struct {
    Id    string `json:"id"`    // Product id
    Name  string `json:"name"`  // Product name
    Price int64  `json:"price"` // Unit price
    Num   uint32 `json:"num"`   // Quantity of goods
    Type  string `json:"type"`  // Item 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"` // Whether declare Y/N
    GoodsUnderBonded      string `json:"goodsUnderBonded"`      // Whether to pay Y/N under bonded goods
}
const jdPayUrl = "https://wepay.jd.com/jdpay/saveOrder"
// PC H5 form submits payment
func postFormPay(arg Order) (payStr string, err error) {
    // Payment parameters
    paramMap := make(map[string]string)
    amountStr := fmt.Sprintf("%.f", arg.Amount*100)
    totalFee, err := strconv.ParseInt(amountStr, 10.64)
    iferr ! =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)
        iferr ! =nil {
            return payStr, err
        }
        goodsInfos = append(goodsInfos, GoodsInfo{
            Id:    v.GoodsNo,
            Name:  v.GoodsName,
            Price: price,
            Num:   v.GoodsNum,
            Type:  practicalityGoodsType, // Item type - physical object
        })
    }
    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,     // Name of consignee
        Mobile:   arg.ShippingAddress.Mobile,   // Consignee's mobile phone number
        Address:  detailAddress,                // The address must be within the province
        Province: arg.ShippingAddress.Province, / / province
        City:     arg.ShippingAddress.City,     / / the city
        Country:  arg.ShippingAddress.Country,  / / area
    }
    receiverInfoBytes, _ := json.Marshal(receiverInfo)
    orderId := fmt.Sprintf("test%s%s", time.Now().Format("20060102150405"), randNumber())
    paramMap["version"] = version
    paramMap["merchant"] = merchantId
    paramMap["tradeNum"] = orderId   / / order number
    paramMap["tradeName"] = orderId  // Order description
    paramMap["tradeDesc"] = orderId  // Order description
    paramMap["payMerchant"] = "test" // Merchant name
    paramMap["tradeTime"] = time.Now().Format(timeLayout)
    paramMap["amount"] = fmt.Sprintf("%v", totalFee)
    paramMap["orderType"] = practicality
    paramMap["currency"] = cny
    paramMap["userId"] = "100"
    paramMap["expireTime"] = "3600" // Order expiration time, in seconds
    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)
    / / certificate
    privateKey, err := getKey("private_key")
    iferr ! =nil {
        return payStr, err
    }
    / / signature
    paramMap["sign"], err = getPaySign(paramMap, privateKey)
    iferr ! =nil {
        return payStr, err
    }
    // Data encryption
    paramMap, err = encrypt3DES(paramMap, desKey)
    iferr ! =nil {
        return payStr, err
    }
    // Splice the payment form
    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 += ""
    return payStr, nil
}
Copy the code

Asynchronous notification

Data decryption and signature verification

// Decrypt asynchronous notification messages
type NotifyQuery struct {
    XMLName  xml.Name     `xml:"jdpay" json:"-"`
    Version  string       `xml:"version" json:"version"`   / / version number
    Merchant string       `xml:"merchant" json:"merchant"` / / merchants
    Result   NotifyResult `xml:"result" json:"result"`     // Transaction result
    Encrypt  string       `xml:"encrypt" json:"encrypt"`   // Encrypt information
}
type NotifyDecrypt struct {
    XMLName   xml.Name      `xml:"jdpay" json:"-"`
    Version   string        `xml:"version" json:"version"`     / / version number
    Merchant  string        `xml:"merchant" json:"merchant"`   / / merchants
    Result    NotifyResult  `xml:"result" json:"result"`       // Transaction result
    TradeNum  string        `xml:"tradeNum" json:"tradeNum"`   / / order number
    TradeType int           `xml:"tradeType" json:"tradeType"` // Transaction type
    Sign      string        `xml:"sign" json:"sign"`           // Data signature
    Amount    int64         `xml:"amount" json:"amount"`       // Total amount paid in RMB
    OrderId   string        `json:"order_id"`                  // Jingdong transaction serial number
    Status    string        `xml:"status" json:"status"`       // Transaction status
    PayList   NotifyPayList `xml:"payList" json:"payList"`     // Details of payment methods
}
type NotifyResult struct {
    Code string `xml:"code" json:"code"` // Return code
    Desc string `xml:"desc" json:"desc"` // Return code information
}
type NotifyPayList struct {
    Pay []NotifyPay `xml:"pay" json:"pay"`
}
type NotifyPay struct {
    PayType   int    `xml:"payType" json:"payType"`     // Payment method
    Amount    int64  `xml:"amount" json:"amount"`       // Transaction amount
    Currency  string `xml:"currency" json:"currency"`   // Transaction currency
    TradeTime string `xml:"tradeTime" json:"tradeTime"` // Transaction time
}
// Decrypt asynchronous notification messages
func notifyDataDecrypt(rawPost string) (notifyDecrypt NotifyDecrypt, err error) {
    // Parse the encrypted payment mechanism argument as a structure
    var notifyQuery NotifyQuery
    err = xml.Unmarshal([]byte(rawPost), &notifyQuery)
    iferr ! =nil {
        return notifyDecrypt, err
    }
    // Decrypt the payment institution parameters
    decryptBytes, err := decryptArg(notifyQuery, desKey)
    iferr ! =nil {
        return notifyDecrypt, err
    }
    // Parse the decrypted payment mechanism parameters as structs
    err = xml.Unmarshal(decryptBytes, &notifyDecrypt)
    iferr ! =nil {
        return notifyDecrypt, err
    }
    / / certificate
    publicKey, err := getKey("public_key")
    iferr ! =nil {
        return notifyDecrypt, err
    }
    // Verify the signature
    if! checkSign(decryptBytes, notifyDecrypt.Sign, publicKey) {return notifyDecrypt, err
    }
    return notifyDecrypt, nil
}
Copy the code

Fourth, transaction inquiry

Query order

type SearchWithoutSignRequest struct {
    XMLName   xml.Name `xml:"jdpay" json:"-"`
    Version   string   `xml:"version" json:"version"`     / / version
    Merchant  string   `xml:"merchant" json:"merchant"`   / / merchants
    TradeNum  string   `xml:"tradeNum" json:"tradeNum"`   // Order no
    OTradeNum string   `xml:"oTradeNum" json:"oTradeNum"` // Original transaction serial number
    TradeType string   `xml:"tradeType" json:"tradeType"` // Transaction type
}
type SearchWithSignRequest struct {
    XMLName   xml.Name `xml:"jdpay" json:"-"`
    Version   string   `xml:"version" json:"version"`     / / version
    Merchant  string   `xml:"merchant" json:"merchant"`   / / merchants
    TradeNum  string   `xml:"tradeNum" json:"tradeNum"`   // Order no
    OTradeNum string   `xml:"oTradeNum" json:"oTradeNum"` // Original transaction serial number
    TradeType string   `xml:"tradeType" json:"tradeType"` // Transaction type
    Sign      string   `xml:"sign" json:"sign"`           / / signature
}
type SearchResult struct {
    XMLName  xml.Name        `xml:"jdpay" json:"-"`
    Version  string          `xml:"version" json:"version"`   / / version number
    Merchant string          `xml:"merchant" json:"merchant"` / / merchants
    Result   SearchResultRsp `xml:"result" json:"result"`     // Transaction result
    Encrypt  string          `xml:"encrypt" json:"encrypt"`   // Encrypt information
}
type SearchResultRsp struct {
    Code string `xml:"code" json:"code"` // Return code
    Desc string `xml:"desc" json:"desc"` // Return code information
}
type SearchDecryptRsp struct {
    XMLName   xml.Name         `xml:"jdpay" json:"-"`
    Merchant  string           `xml:"merchant" json:"merchant"`   / / merchants
    TradeNum  string           `xml:"tradeNum" json:"tradeNum"`   // Order no
    TradeType string           `xml:"tradeType" json:"tradeType"` // Transaction type
    Result    SearchResultRsp  `xml:"result" json:"result"`       // Transaction result
    Sign      string           `xml:"sign" json:"sign"`           // Data signature
    Amount    int64            `xml:"amount" json:"amount"`       // Total amount paid in RMB
    Status    string           `xml:"status" json:"status"`       // Transaction status
    PayList   SearchPayListRsp `xml:"payList" json:"payList"`     // Details of payment methods
}
type SearchPayListRsp struct {
    Pay []SearchPayRsp `xml:"pay" json:"pay"`
}
type SearchPayRsp struct {
    PayType   int    `xml:"payType" json:"payType"`     // Payment method
    Amount    int64  `xml:"amount" json:"amount"`       // Transaction amount
    Currency  string `xml:"currency" json:"currency"`   // Transaction currency
    TradeTime string `xml:"tradeTime" json:"tradeTime"` // Transaction time
}
const tradeWayUrl = "https://paygate.jd.com/service/query"
const customTradeType = "0" // Transaction type
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)
    / / certificate
    privateKey, err := getKey("private_key")
    iferr ! =nil {
        return searchDecryptRsp, err
    }
    / / signature
    sign, err := getRsaSign(xmlStr, privateKey)
    iferr ! =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)
    iferr ! =nil {
        return searchDecryptRsp, err
    }
    encryptBytes, err := tripleEcbDesEncrypt([]byte(xmlStr), desKeyBytes)
    iferr ! =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)
    iferr ! =nil {
        return searchDecryptRsp, err
    }
    xmlStr = strings.TrimRight(xml.Header, "\n") + string(xmlBytes)
    request, err := http.NewRequest(http.MethodPost, tradeWayUrl, strings.NewReader(xmlStr))
    iferr ! =nil {
        return searchDecryptRsp, err
    }
    request.Header.Add("content-type"."application/xml; charset=utf-8")
    client := http.DefaultClient
    response, err := client.Do(request)
    iferr ! =nil {
        return searchDecryptRsp, err
    }
    defer response.Body.Close()
    bodyBytes, err := ioutil.ReadAll(response.Body)
    iferr ! =nil {
        return searchDecryptRsp, err
    }
    searchResult := new(SearchResult)
    iferr = xml.Unmarshal(bodyBytes, searchResult); err ! =nil {
        return searchDecryptRsp, err
    }
    ifsearchResult.Result.Code ! = successCode {return searchDecryptRsp, errors.New(searchResult.Result.Desc)
    }
    // Decrypt data
    rspEncryptBytes, err := base64.StdEncoding.DecodeString(searchResult.Encrypt)
    rspEncryptBytes, err = hexString2Bytes(string(rspEncryptBytes))
    iferr ! =nil {
        return searchDecryptRsp, err
    }
    rspDecryptBytes, err := tripleEcbDesDecrypt(rspEncryptBytes, desKeyBytes)
    iferr ! =nil {
        return searchDecryptRsp, err
    }
    err = xml.Unmarshal(rspDecryptBytes, &searchDecryptRsp)
    iferr ! =nil {
        return searchDecryptRsp, err
    }
    / / certificate
    publicKey, err := getKey("public_key")
    iferr ! =nil {
        return searchDecryptRsp, err
    }
    // Verify the signature
    if! checkSign(rspDecryptBytes, searchDecryptRsp.Sign, publicKey) {return searchDecryptRsp, err
    }
    return searchDecryptRsp, nil
}
Copy the code

Apply for a refund

To apply for a refund

/ / refund
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"`   / / version number
    Merchant string             `xml:"merchant" json:"merchant"` / / merchants
    Result   RefundPayResultRsp `xml:"result" json:"result"`     // Result of refund
    Encrypt  string             `xml:"encrypt" json:"encrypt"`   // Encrypt information
}
type RefundPayResultRsp struct {
    Code string `xml:"code" json:"code"` // Return code
    Desc string `xml:"desc" json:"desc"` // Return code information
}
type RefundPayDecryptRsp struct {
    XMLName   xml.Name           `xml:"jdpay" json:"-"`
    Version   string             `xml:"version" json:"version"`   / / version number
    Merchant  string             `xml:"merchant" json:"merchant"` / / merchants
    TradeNum  string             `xml:"tradeNum" json:"tradeNum"`
    TradeType string             `xml:"tradeType"json:"tradeType"`
    Result    RefundPayResultRsp `xml:"result" json:"result"` // Result of refund
    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"
// Request a refund
func refundTrade(orderId string, amount float64) (refundPayDecryptRsp RefundPayDecryptRsp, err error) {
    totalFee, err := strconv.ParseUint(fmt.Sprintf("%.f", amount*100), 10.64)
    iferr ! =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)
    / / certificate
    privateKey, err := getKey("private_key")
    iferr ! =nil {
        return refundPayDecryptRsp, err
    }
    / / signature
    sign, err := getRsaSign(xmlStr, privateKey)
    iferr ! =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)
    iferr ! =nil {
        return refundPayDecryptRsp, err
    }
    encryptBytes, err := tripleEcbDesEncrypt([]byte(xmlStr), desKeyBytes)
    iferr ! =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))
    iferr ! =nil {
        return refundPayDecryptRsp, err
    }
    request.Header.Add("content-type"."application/xml; charset=utf-8")
    client := http.DefaultClient
    response, err := client.Do(request)
    iferr ! =nil {
        return refundPayDecryptRsp, err
    }
    defer response.Body.Close()
    bodyBytes, err := ioutil.ReadAll(response.Body)
    iferr ! =nil {
        return refundPayDecryptRsp, err
    }
    refundResult := new(RefundResult)
    iferr = xml.Unmarshal(bodyBytes, refundResult); err ! =nil {
        return refundPayDecryptRsp, err
    }
    // Decrypt data
    rspEncryptBytes, err := base64.StdEncoding.DecodeString(refundResult.Encrypt)
    iferr ! =nil {
        return refundPayDecryptRsp, err
    }
    rspEncryptBytes, err = hexString2Bytes(string(rspEncryptBytes))
    iferr ! =nil {
        return refundPayDecryptRsp, err
    }
    rspDecryptBytes, err := tripleEcbDesDecrypt(rspEncryptBytes, desKeyBytes)
    iferr ! =nil {
        return refundPayDecryptRsp, err
    }
    err = xml.Unmarshal(rspDecryptBytes, &refundPayDecryptRsp)
    iferr ! =nil {
        return refundPayDecryptRsp, err
    }
    
    / / certificate
	publicKey, err := getKey("public_key")
	iferr ! =nil {
		return refundPayDecryptRsp, err
	}
	// Verify the signature
	if! checkSign(rspDecryptBytes, refundPayDecryptRsp.Sign, publicKey) {return refundPayDecryptRsp, err
	}
    
    return refundPayDecryptRsp, nil
}
Copy the code