You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

262 lines
7.2 KiB

4 years ago
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-PC08-手机
"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-PC08-手机
"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-PC08-手机
"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)
}