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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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)
}