+-
golang实现京东支付v2版本
首页 专栏 golang 文章详情
0

golang实现京东支付v2版本

快乐源泉 发布于 今天 00:07

一、准备阶段

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
}
// [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)
    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
}
// [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)
    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), &notifyQuery)
    if err != nil {
        return notifyDecrypt, err
    }
    // 解密支付机构参数
    decryptBytes, err := decryptArg(notifyQuery, desKey)
    if err != nil {
        return notifyDecrypt, err
    }
    // 解析解密后的支付机构参数为结构体
    err = xml.Unmarshal(decryptBytes, &notifyDecrypt)
    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
}
golang 京东支付v2 京东支付
阅读 108 更新于 今天 00:10
收藏
分享
本作品系原创, 采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议
avatar
快乐源泉
0 声望
0 粉丝
关注作者
0 条评论
得票 时间
提交评论
avatar
快乐源泉
0 声望
0 粉丝
关注作者
宣传栏
目录

一、准备阶段

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
}
// [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)
    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
}
// [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)
    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), &notifyQuery)
    if err != nil {
        return notifyDecrypt, err
    }
    // 解密支付机构参数
    decryptBytes, err := decryptArg(notifyQuery, desKey)
    if err != nil {
        return notifyDecrypt, err
    }
    // 解析解密后的支付机构参数为结构体
    err = xml.Unmarshal(decryptBytes, &notifyDecrypt)
    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
}