|
|
|
|
package unionpay
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"crypto"
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
"crypto/rsa"
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/shopspring/decimal"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"log"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/url"
|
|
|
|
|
"recook/configs"
|
|
|
|
|
"sort"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var UnionPay *unionPay
|
|
|
|
|
|
|
|
|
|
type unionPay struct {
|
|
|
|
|
Url string // 请求地址
|
|
|
|
|
merId string // 商户号
|
|
|
|
|
callbackFront string //前台通知地址
|
|
|
|
|
callbackBack string //后台通知地址
|
|
|
|
|
public *rsa.PublicKey // 公钥
|
|
|
|
|
private *rsa.PrivateKey // 私钥
|
|
|
|
|
certId string
|
|
|
|
|
callbackRefundBack string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
AppPay = "appTransReq.do"
|
|
|
|
|
Query = "queryTrans.do"
|
|
|
|
|
Refund = "backTransReq.do"
|
|
|
|
|
FrontTransReq = "frontTransReq.do"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// @Style 初始化
|
|
|
|
|
func init() {
|
|
|
|
|
cert, err := ParseCertificateFromFile(configs.Config_Unionpay_Verify_Sign_Cer)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Panic("unionPay cert err: ", err)
|
|
|
|
|
}
|
|
|
|
|
pfx, Cert, err := ParserPfxToCert(configs.Config_Unionpay_Private_Pfx, configs.Config_Unionpay_Private_Pfx_Pwd)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Panic("unionPay pfx err: ", err)
|
|
|
|
|
}
|
|
|
|
|
UnionPay = &unionPay{
|
|
|
|
|
Url: configs.Config_Unionpay_URL,
|
|
|
|
|
merId: configs.Config_Unionpay_MerId,
|
|
|
|
|
public: cert.PublicKey.(*rsa.PublicKey),
|
|
|
|
|
private: pfx,
|
|
|
|
|
certId: fmt.Sprintf("%v", Cert.SerialNumber),
|
|
|
|
|
callbackFront: configs.Config_Unionpay_Front,
|
|
|
|
|
callbackBack: configs.Config_Unionpay_Back,
|
|
|
|
|
callbackRefundBack: configs.Config_Unionpay_RefundBack,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @Style AppPay
|
|
|
|
|
func (u *unionPay) AppPay(orderId string, amount decimal.Decimal, payTimeout string) (string, error) {
|
|
|
|
|
result, err := u.Exec(AppPay, &map[string]string{
|
|
|
|
|
"txnType": "01", //交易类型
|
|
|
|
|
"txnSubType": "01", //交易子类
|
|
|
|
|
"bizType": "000201", //业务类型
|
|
|
|
|
"channelType": "08", //渠道类型,07-PC,08-手机
|
|
|
|
|
"orderId": orderId,
|
|
|
|
|
"txnAmt": amount.Mul(decimal.NewFromInt(100)).String(),
|
|
|
|
|
"payTimeout": payTimeout,
|
|
|
|
|
"frontUrl": u.callbackFront, //前台通知地址
|
|
|
|
|
"backUrl": u.callbackBack, //后台通知地址
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println(err)
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return result["tn"], nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @Style AppPay
|
|
|
|
|
func (u *unionPay) H5Pay(orderId string, amount decimal.Decimal, payTimeout string) (*map[string]string, error) {
|
|
|
|
|
return u.pargams(&map[string]string{
|
|
|
|
|
"txnType": "01", //交易类型
|
|
|
|
|
"txnSubType": "01", //交易子类
|
|
|
|
|
"bizType": "000201", //业务类型
|
|
|
|
|
"channelType": "08", //渠道类型,07-PC,08-手机
|
|
|
|
|
"orderId": orderId,
|
|
|
|
|
"txnAmt": amount.Mul(decimal.NewFromInt(100)).String(),
|
|
|
|
|
"payTimeout": payTimeout,
|
|
|
|
|
"frontUrl": u.callbackFront, //前台通知地址
|
|
|
|
|
"backUrl": u.callbackBack, //后台通知地址
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @Style Query
|
|
|
|
|
func (u *unionPay) Query(orderId string) (map[string]string, error) {
|
|
|
|
|
result, err := u.Exec(Query, &map[string]string{
|
|
|
|
|
"bizType": "000000", //业务类型
|
|
|
|
|
"txnType": "00", //交易类型
|
|
|
|
|
"txnSubType": "00", //交易子类
|
|
|
|
|
"orderId": orderId,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return result, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @Style
|
|
|
|
|
func (u *unionPay) Refund(orderId, tn string, amount decimal.Decimal) (map[string]string, error) {
|
|
|
|
|
refund, err := u.Exec(Refund, &map[string]string{
|
|
|
|
|
"bizType": "000201", //业务类型
|
|
|
|
|
"txnType": "04", //交易类型
|
|
|
|
|
"txnSubType": "00", //交易子类
|
|
|
|
|
"channelType": "08", //渠道类型,07-PC,08-手机
|
|
|
|
|
"orderId": orderId,
|
|
|
|
|
"origQryId": tn,
|
|
|
|
|
"txnAmt": amount.Mul(decimal.NewFromInt(100)).String(), //退款金额
|
|
|
|
|
"backUrl": u.callbackRefundBack, //后台通知地址
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if refund["respCode"] != "00" {
|
|
|
|
|
return nil, errors.New(refund["respMsg"])
|
|
|
|
|
}
|
|
|
|
|
return refund, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (u *unionPay) pargams(data *map[string]string) (pargams *map[string]string, err error) {
|
|
|
|
|
pargams = &map[string]string{
|
|
|
|
|
"version": "5.1.0", //版本号
|
|
|
|
|
"encoding": "utf-8", //编码方式
|
|
|
|
|
"signMethod": "01", //签名方法
|
|
|
|
|
"accessType": "0", //接入类型
|
|
|
|
|
"currencyCode": "156", //交易币种,境内商户固定156
|
|
|
|
|
"merId": u.merId,
|
|
|
|
|
"certId": u.certId,
|
|
|
|
|
"txnTime": time.Now().Format("20060102150405"),
|
|
|
|
|
}
|
|
|
|
|
for key, value := range *data {
|
|
|
|
|
(*pargams)[key] = value
|
|
|
|
|
}
|
|
|
|
|
(*pargams)["signature"], err = u.Sign(pargams)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @Style 调用接口
|
|
|
|
|
func (u *unionPay) Exec(action string, data *map[string]string) (map[string]string, error) {
|
|
|
|
|
pargams, err := u.pargams(data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
request, err := u.request("post", u.Url+action, Http_build_query(*pargams), &map[string]string{"Content-Type": "application/x-www-form-urlencoded"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return u.Verify(request)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @Style 网络请求
|
|
|
|
|
func (u *unionPay) request(method string, host string, data string, headers ...*map[string]string) (string, error) {
|
|
|
|
|
client := &http.Client{}
|
|
|
|
|
method = strings.ToUpper(method)
|
|
|
|
|
|
|
|
|
|
req, err := http.NewRequest(method, host, strings.NewReader(data))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
if len(headers) > 0 {
|
|
|
|
|
for key, value := range *headers[0] {
|
|
|
|
|
req.Header.Add(key, value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return string(body), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sign 签名
|
|
|
|
|
func (u *unionPay) Sign(pargams *map[string]string) (string, error) {
|
|
|
|
|
str := u.mapSortByKey((*pargams), "=", "&")
|
|
|
|
|
rng := rand.Reader
|
|
|
|
|
hashed := sha256.Sum256([]byte(fmt.Sprintf("%x", sha256.Sum256([]byte(str)))))
|
|
|
|
|
signer, err := rsa.SignPKCS1v15(rng, u.private, crypto.SHA256, hashed[:])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return base64.StdEncoding.EncodeToString(signer), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 验签
|
|
|
|
|
func (u *unionPay) Verify(data string) (res map[string]string, err error) {
|
|
|
|
|
var fields []string
|
|
|
|
|
fields = strings.Split(data, "&")
|
|
|
|
|
|
|
|
|
|
vals := url.Values{}
|
|
|
|
|
for _, field := range fields {
|
|
|
|
|
f := strings.SplitN(field, "=", 2)
|
|
|
|
|
if len(f) >= 2 {
|
|
|
|
|
key, val := f[0], f[1]
|
|
|
|
|
vals.Set(key, val)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return u.VerifySign(vals)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @Style 验签
|
|
|
|
|
func (u *unionPay) VerifySign(vals url.Values) (res map[string]string, err error) {
|
|
|
|
|
var signature string
|
|
|
|
|
kvs := map[string]string{}
|
|
|
|
|
for k := range vals {
|
|
|
|
|
if k == "signature" {
|
|
|
|
|
signature = vals.Get(k)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if vals.Get(k) == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
kvs[k] = vals.Get(k)
|
|
|
|
|
}
|
|
|
|
|
str := u.mapSortByKey(kvs, "=", "&")
|
|
|
|
|
hashed := sha256.Sum256([]byte(fmt.Sprintf("%x", sha256.Sum256([]byte(str)))))
|
|
|
|
|
inSign, err := base64.StdEncoding.DecodeString(signature)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("解析返回signature失败 %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = rsa.VerifyPKCS1v15(u.public, crypto.SHA256, hashed[:], inSign)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("返回数据验签失败 ERR:%v", err)
|
|
|
|
|
}
|
|
|
|
|
return kvs, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @Style 数据处理
|
|
|
|
|
func (u *unionPay) mapSortByKey(m map[string]string, step1, step2 string) string {
|
|
|
|
|
ms := make(mapSorter, 0, len(m))
|
|
|
|
|
|
|
|
|
|
for k, v := range m {
|
|
|
|
|
ms = append(ms, sortItem{k, v})
|
|
|
|
|
}
|
|
|
|
|
sort.Sort(ms)
|
|
|
|
|
s := []string{}
|
|
|
|
|
for _, p := range ms {
|
|
|
|
|
s = append(s, p.Key+step1+p.Val.(string))
|
|
|
|
|
}
|
|
|
|
|
return strings.Join(s, step2)
|
|
|
|
|
}
|