一、准备阶段
pc&h5 接入步骤
官方文档 https://payapi.jd.com/docList...
查看主要接入步骤
密钥生成
• 需要设置desc key
• md5 key 和 app id app对接会使用
• 证书文件名称
1
2
|
my_rsa_private_pkcs8_key.pem
wy_rsa_public_key.pem
|
示例程序使用私钥格式为 pkcs8 格式
官方的SDK中的数据可以在示例程序中使用
下载SDK地址 https://payapi.jd.com/docList...
找到接口文档中的Demo
还会用到的包
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import (
"encoding/base64"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"time"
)
|
加密、解密、验证签名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
|
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()
}
|
程序中加载密钥
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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
}
|
二、发起支付
常量
1
2
3
4
5
6
7
8
9
|
常量
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...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
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
}
|
三、异步通知
数据解密、签名校验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
// 异步通知信息解密
type NotifyQuery struct {
XMLName xml.Name `xml:"jdpay" json:"-"`
Version string `xml:"version" json:"version"` // 版本号
Merchant string `xml:"merchant" json:"merchant"` // 商户号
Result NotifyResult `xml:"result" json:"result"` // 交易结果
Encrypt string `xml:"encrypt" json:"encrypt"` // 加密信息
}
type NotifyDecrypt struct {
XMLName xml.Name `xml:"jdpay" json:"-"`
Version string `xml:"version" json:"version"` // 版本号
Merchant string `xml:"merchant" json:"merchant"` // 商户号
Result NotifyResult `xml:"result" json:"result"` // 交易结果
TradeNum string `xml:"tradeNum" json:"tradeNum"` // 订单号
TradeType int `xml:"tradeType" json:"tradeType"` // 交易类型
Sign string `xml:"sign" json:"sign"` // 数据签名
Amount int64 `xml:"amount" json:"amount"` // 人民币支付总金额
OrderId string `json:"order_id"` // 京东交易流水号
Status string `xml:"status" json:"status"` // 交易状态
PayList NotifyPayList `xml:"payList" json:"payList"` // 支付方式明细
}
type NotifyResult struct {
Code string `xml:"code" json:"code"` // 交易返回码
Desc string `xml:"desc" json:"desc"` // 返回码信息
}
type NotifyPayList struct {
Pay []NotifyPay `xml:"pay" json:"pay"`
}
type NotifyPay struct {
PayType int `xml:"payType" json:"payType"` // 支付方式
Amount int64 `xml:"amount" json:"amount"` // 交易金额
Currency string `xml:"currency" json:"currency"` // 交易币种
TradeTime string `xml:"tradeTime" json:"tradeTime"` // 交易时间
}
// 异步通知信息解密
func notifyDataDecrypt(rawPost string) (notifyDecrypt NotifyDecrypt, err error) {
// 解析加密的支付机构参数为结构体
var notifyQuery NotifyQuery
err = xml.Unmarshal([]byte(rawPost), ¬ifyQuery)
if err != nil {
return notifyDecrypt, err
}
// 解密支付机构参数
decryptBytes, err := decryptArg(notifyQuery, desKey)
if err != nil {
return notifyDecrypt, err
}
// 解析解密后的支付机构参数为结构体
err = xml.Unmarshal(decryptBytes, ¬ifyDecrypt)
if err != nil {
return notifyDecrypt, err
}
// 证书
publicKey, err := getKey("public_key")
if err != nil {
return notifyDecrypt, err
}
// 校验签名
if !checkSign(decryptBytes, notifyDecrypt.Sign, publicKey) {
return notifyDecrypt, err
}
return notifyDecrypt, nil
}
|
四、交易查询
查询订单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
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
}
|
五、申请退款
申请退款
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
// 退款
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版本的文章就介绍到这了,更多相关go京东支付v2内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!
原文链接:https://studygolang.com/articles/33666