当前位置 博文首页 > golang实现京东支付v2版本的示例代码

    golang实现京东支付v2版本的示例代码

    作者:admin 时间:2021-05-01 17:56

    一、准备阶段

     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
    }
    js
    下一篇:没有了