├── constant └── pay.go ├── util ├── xml.go └── util.go ├── pay_test.go ├── pay.go ├── common ├── common.go ├── wechat.go └── alipay.go ├── README.md ├── client ├── http.go ├── aliapp.go ├── wechatapp.go ├── aliweb.go └── wechatweb.go └── callback.go /constant/pay.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | ALI_WEB = iota + 1 5 | ALI_APP 6 | WECHAT 7 | ) 8 | -------------------------------------------------------------------------------- /util/xml.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | func XmlToMap(xmlData []byte) (map[string]string, error) { 11 | decoder := xml.NewDecoder(bytes.NewReader(xmlData)) 12 | m := make(map[string]string) 13 | var token xml.Token 14 | var err error 15 | var k string 16 | for token, err = decoder.Token(); err == nil; token, err = decoder.Token() { 17 | if v, ok := token.(xml.StartElement); ok { 18 | k = v.Name.Local 19 | continue 20 | } 21 | if v, ok := token.(xml.CharData); ok { 22 | data := string(v.Copy()) 23 | if strings.TrimSpace(data) == "" { 24 | continue 25 | } 26 | m[k] = data 27 | } 28 | } 29 | 30 | if err != nil && err != io.EOF { 31 | return nil, err 32 | } 33 | return m, nil 34 | } 35 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net" 7 | "time" 8 | ) 9 | 10 | //RandomStr 获取一个随机字符串 11 | func RandomStr() string { 12 | return fmt.Sprintf("%d", time.Now().UnixNano()) 13 | } 14 | 15 | // LocalIP 获取机器的IP 16 | func LocalIP() string { 17 | info, _ := net.InterfaceAddrs() 18 | for _, addr := range info { 19 | ipNet, ok := addr.(*net.IPNet) 20 | if !ok { 21 | continue 22 | } 23 | if !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil { 24 | return ipNet.IP.String() 25 | } 26 | } 27 | return "" 28 | } 29 | 30 | func MapStringToStruct(m map[string]string, i interface{}) error { 31 | bin, err := json.Marshal(m) 32 | if err != nil { 33 | return err 34 | } 35 | err = json.Unmarshal(bin, i) 36 | if err != nil { 37 | return err 38 | } 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /pay_test.go: -------------------------------------------------------------------------------- 1 | package pays_with_go 2 | 3 | import ( 4 | "fmt" 5 | "./client" 6 | "./common" 7 | "./constant" 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | //测试 13 | func TestPay(t *testing.T) { 14 | initClient() 15 | initHandle() 16 | charge := new(common.Charge) 17 | charge.PayMethod = constant.WECHAT 18 | charge.MoneyFee = 1 19 | charge.Describe = "测试订单" 20 | charge.TradeNum = "8888888888" 21 | 22 | fdata, err := do(charge) 23 | if err != nil { 24 | t.Error(err) 25 | } 26 | fmt.Println(fdata) 27 | } 28 | 29 | //初始化客户端 30 | func initClient() { 31 | client.InitAliAppClient(&client.AliAppClient{ 32 | PartnerID: "xxx", 33 | SellerID: "xxxx", 34 | AppID: "xxx", 35 | PrivateKey: nil, 36 | PublicKey: nil, 37 | }) 38 | } 39 | 40 | //初始化处理 41 | func initHandle() { 42 | http.HandleFunc("callback/aliappcallback", func(w http.ResponseWriter, r *http.Request) { 43 | aliResult, err := AliAppCallback(w, r) 44 | if err != nil { 45 | fmt.Println(err) 46 | //log.xxx 47 | return 48 | } 49 | selfHandler(aliResult) 50 | }) 51 | } 52 | 53 | func selfHandler(i interface{}) { 54 | } 55 | 56 | -------------------------------------------------------------------------------- /pay.go: -------------------------------------------------------------------------------- 1 | package pays_with_go 2 | 3 | import ( 4 | "errors" 5 | "./client" 6 | "./common" 7 | "./constant" 8 | "strconv" 9 | ) 10 | //pay 11 | func do(charge *common.Charge) (string, error) { 12 | err := checkCharge(charge) 13 | if err != nil { 14 | //log.Error(err, charge) 15 | return "", err 16 | } 17 | 18 | ct := getPayClient(charge.PayMethod) 19 | re, err := ct.Pay(charge) 20 | if err != nil { 21 | //log.Error("支付失败:", err, charge) 22 | return "", err 23 | } 24 | return re, err 25 | } 26 | 27 | func checkCharge(charge *common.Charge) error { 28 | var id uint64 29 | var err error 30 | if charge.UserID == "" { 31 | id = 0 32 | } else { 33 | id, err = strconv.ParseUint(charge.UserID, 10, -1) 34 | if err != nil { 35 | return err 36 | } 37 | } 38 | if id < 0 { 39 | return errors.New("userID less than 0") 40 | } 41 | if charge.PayMethod < 0 { 42 | return errors.New("payMethod less than 0") 43 | } 44 | if charge.MoneyFee < 0 { 45 | return errors.New("totalFee less than 0") 46 | } 47 | 48 | if charge.CallbackURL == "" { 49 | return errors.New("callbackURL is NULL") 50 | } 51 | return nil 52 | } 53 | 54 | // getPayClient 得到需要支付的客户端 55 | 56 | func getPayClient(payMethod int64) common.PayClient { 57 | //如果使用余额支付 58 | switch payMethod { 59 | case constant.ALI_WEB: 60 | return client.DefaultAliWebClient() 61 | case constant.ALI_APP: 62 | return client.DefaultAliAppClient() 63 | case constant.WECHAT: 64 | return client.DefaultWechatAppClient() 65 | } 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import () 4 | 5 | // PayClient 支付客户端接口 6 | type PayClient interface { 7 | Pay(charge *Charge) (string, error) 8 | //检查签名 9 | //CheckSign(data []byte, sign []byte) error 10 | } 11 | 12 | // Charge 支付参数 13 | type Charge struct { 14 | TradeNum string `json:"tradeNum,omitempty"` 15 | Origin string `json:"origin,omitempty"` 16 | UserID string `json:"userId,omitempty"` 17 | PayMethod int64 `json:"payMethod,omitempty"` 18 | MoneyFee int64 `json:"MoneyFee,omitempty"` 19 | CallbackURL string `json:"callbackURL,omitempty"` 20 | ReturnURL string `json:"returnURL,omitempty"` 21 | ShowURL string `json:"showURL,omitempty"` 22 | Describe string `json:"describe,omitempty"` 23 | OpenID string `json:"openid,omitempty"` 24 | } 25 | 26 | //PayCallback 支付返回 27 | type PayCallback struct { 28 | Origin string `json:"origin"` 29 | TradeNum string `json:"trade_num"` 30 | OrderNum string `json:"order_num"` 31 | CallBackURL string `json:"callback_url"` 32 | Status int64 `json:"status"` 33 | } 34 | 35 | // CallbackReturn 回调业务代码时的参数 36 | type CallbackReturn struct { 37 | IsSucceed bool `json:"isSucceed"` 38 | OrderNum string `json:"orderNum"` 39 | TradeNum string `json:"tradeNum"` 40 | UserID string `json:"userID"` 41 | MoneyFee int64 `json:"moneyFee"` 42 | Sign string `json:"sign"` 43 | ThirdDiscount int64 `json:"thirdDiscount"` 44 | } 45 | 46 | // BaseResult 支付结果 47 | type BaseResult struct { 48 | IsSucceed bool // 是否交易成功 49 | TradeNum string // 交易流水号 50 | MoneyFee int64 // 支付金额 51 | TradeTime string // 交易时间 52 | ContractNum string // 交易单号 53 | UserInfo string // 支付账号信息(有可能有,有可能没有) 54 | ThirdDiscount int64 // 第三方优惠 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # golang支付合集(包括微信支付,支付宝,后期会加入聚合支付) 2 | 3 | * golang支付库 4 | 想必gopher们都会抱怨go成熟的包太少,特别是go语言支付这块,网上的代码基本没有能用的,要么不全,要么有硬伤,所以结合网上和自己经验,抽出时间写的一部分代码,封装下分享出来,希望能给大家一点借鉴意义。 5 | * 支持的支付方式 6 | 目前支持微信app,支付宝网页版,支付宝app。要是谁有新的支付方式也可以合并。 7 | 8 | * 项目开源协议:[GPL v3](https://opensource.org/licenses/GPL-3.0) 9 | 10 | * 使用方法 11 | 12 | * #####1. git clone https://github.com/shirdonliao/pays_with_go.git 或者 go get github.com/shirdonliao/pays_with_go 13 | 14 | * #####2. 在自己的项目里调用,示例代码如下,确保填入自己的配置参数,开发中有问题欢迎留言或者关注公众号:codebigdata 15 | 16 | ```javascript 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "github.com/shirdonliao/pays_with_go" 22 | "github.com/shirdonliao/pays_with_go/client" 23 | "github.com/shirdonliao/pays_with_go/common" 24 | "github.com/shirdonliao/pays_with_go/constant" 25 | "net/http" 26 | ) 27 | func main() { 28 | //设置支付宝账号信息 29 | initClient() 30 | //设置回调函数 31 | initHandle() 32 | 33 | //支付 34 | charge := new(common.Charge) 35 | charge.PayMethod = constant.WECHAT //支付方式 36 | charge.MoneyFee = 1 // 支付钱单位分 37 | charge.Describe = "测试订单" //支付描述 38 | charge.TradeNum = "88888888" //交易号 39 | charge.CallbackURL = "http://127.0.0.1/callback/aliappcallback" //回调地址必须跟下面一样 40 | 41 | fdata, err := gopay.Pay(charge) 42 | if err != nil { 43 | fmt.Println(err) 44 | return 45 | } 46 | fmt.Println(fdata) 47 | } 48 | 49 | // 50 | func initClient() { 51 | client.InitAliAppClient(&client.AliAppClient{ 52 | PartnerID: "xxx", 53 | SellerID: "xxxx", 54 | AppID: "xxx", 55 | PrivateKey: nil, 56 | PublicKey: nil, 57 | }) 58 | } 59 | 60 | 61 | func initHandle() { 62 | http.HandleFunc("callback/aliappcallback", func(w http.ResponseWriter, r *http.Request) { 63 | //返回支付结果 64 | aliResult, err := gopay.AliAppCallback(w, r) 65 | if err != nil { 66 | fmt.Println(err) 67 | //log.xxx 68 | return 69 | } 70 | //接下来处理自己的逻辑 71 | fmt.Println(aliResult) 72 | }) 73 | } 74 | ``` 75 | -------------------------------------------------------------------------------- /client/http.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "crypto/tls" 5 | "io/ioutil" 6 | "net/http" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | var ( 12 | tokenServer string 13 | tokenUsername string 14 | tokenPassword string 15 | payClientCertFile string 16 | payClientKeyFile string 17 | payClientRootCert string 18 | 19 | HTTPC *HTTPClient 20 | HTTPSC *HTTPSClient 21 | ) 22 | 23 | func init() { 24 | HTTPC = &HTTPClient{} 25 | HTTPSC = NewHTTPSClient() 26 | } 27 | 28 | // HTTPSClient HTTPS客户端结构 29 | type HTTPSClient struct { 30 | http.Client 31 | } 32 | 33 | // GetDefaultClient 返回默认的客户端 34 | func GetDefaultClient() *HTTPSClient { 35 | return HTTPSC 36 | } 37 | 38 | // NewHTTPSClient 新建https客户端 39 | // func NewHTTPSClient(certFile string, keyFile string) *HTTPSClient { 40 | // cert, err := tls.LoadX509KeyPair(certFile, keyFile) 41 | // if err != nil { 42 | // log.Error("load x509 cert error:", err) 43 | // return nil 44 | // } 45 | // config := &tls.Config{ 46 | // Certificates: []tls.Certificate{cert}, 47 | // } 48 | // config.BuildNameToCertificate() 49 | // tr := &http.Transport{TLSClientConfig: config} 50 | // client := &http.Client{Transport: tr} 51 | // return &HTTPSClient{ 52 | // Client: *client, 53 | // } 54 | // } 55 | 56 | // NewHTTPSClient 获取默认https客户端 57 | func NewHTTPSClient() *HTTPSClient { 58 | config := &tls.Config {InsecureSkipVerify:true} 59 | tr := &http.Transport{TLSClientConfig: config} 60 | client := http.Client{ 61 | Transport: tr, 62 | Timeout: 15 * time.Second, 63 | } 64 | return &HTTPSClient{ 65 | Client: client, 66 | } 67 | } 68 | 69 | // PostData 提交post数据 70 | func (c *HTTPSClient) PostData(url string, contentType string, data string) ([]byte, error) { 71 | resp, err := c.Post(url, contentType, strings.NewReader(data)) 72 | if err != nil { 73 | return nil, err 74 | } 75 | defer resp.Body.Close() 76 | return ioutil.ReadAll(resp.Body) 77 | } 78 | 79 | // HTTPClient http客户端 80 | type HTTPClient struct { 81 | http.Client 82 | } 83 | 84 | // PostData post数据 85 | func (c *HTTPClient) PostData(url, format string, data string) ([]byte, error) { 86 | resp, err := c.Post(url, format, strings.NewReader(data)) 87 | if err != nil { 88 | return nil, err 89 | } 90 | defer resp.Body.Close() 91 | return ioutil.ReadAll(resp.Body) 92 | } 93 | -------------------------------------------------------------------------------- /common/wechat.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | // WeChatResult 微信支付返回 4 | type WeChatReResult struct { 5 | ReturnCode string `xml:"return_code"` 6 | ReturnMsg string `xml:"return_msg"` 7 | 8 | AppID string `xml:"appid"` 9 | MchID string `xml:"mch_id"` 10 | DeviceInfo string `xml:"device_info"` 11 | NonceStr string `xml:"nonce_str"` 12 | Sign string `xml:"sign"` 13 | ResultCode string `xml:"result_code"` 14 | ErrCode string `xml:"err_code"` 15 | ErrCodeDes string `xml:"err_code_des"` 16 | 17 | TradeType string `xml:"trade_type"` 18 | PrepayID string `xml:"prepay_id"` 19 | CodeURL string `xml:"code_url"` 20 | } 21 | 22 | // WechatBaseResult 基本信息 23 | type WechatBaseResult struct { 24 | ReturnCode string `xml:"return_code"` 25 | ReturnMsg string `xml:"return_msg"` 26 | } 27 | 28 | // WechatReturnData 返回通用数据 29 | type WechatReturnData struct { 30 | AppID string `xml:"appid,emitempty"` 31 | MchID string `xml:"mch_id,emitempty"` 32 | DeviceInfo string `xml:"device_info,emitempty"` 33 | NonceStr string `xml:"nonce_str,emitempty"` 34 | Sign string `xml:"sign,emitempty"` 35 | ResultCode string `xml:"result_code,emitempty"` 36 | ErrCode string `xml:"err_code,emitempty"` 37 | ErrCodeDes string `xml:"err_code_des,emitempty"` 38 | } 39 | 40 | // WechatResultData 结果通用数据 41 | type WechatResultData struct { 42 | OpenID string `xml:"openid,emitempty"` 43 | IsSubscribe string `xml:"is_subscribe,emitempty"` 44 | TradeType string `xml:"trade_type,emitempty"` 45 | BankType string `xml:"bank_type,emitempty"` 46 | FeeType string `xml:"fee_type,emitempty"` 47 | TotalFee int64 `xml:"total_fee,emitempty"` 48 | CashFeeType string `xml:"cash_fee_type,emitempty"` 49 | CashFee int64 `xml:"cash_fee,emitempty"` 50 | TransactionID string `xml:"transaction_id,emitempty"` 51 | OutTradeNO string `xml:"out_trade_no,emitempty"` 52 | Attach string `xml:"attach,emitempty"` 53 | TimeEnd string `xml:"time_end,emitempty"` 54 | } 55 | 56 | type WeChatPayResult struct { 57 | WechatBaseResult 58 | WechatReturnData 59 | WechatResultData 60 | } 61 | 62 | type WeChatQueryResult struct { 63 | WechatBaseResult 64 | WechatReturnData 65 | WechatResultData 66 | TradeState string `xml:"trade_state"` 67 | TradeStateDesc string `xml:"trade_state_desc"` 68 | } 69 | -------------------------------------------------------------------------------- /common/alipay.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | // AliWebPayResult 支付宝支付结果回调 4 | type AliWebPayResult struct { 5 | NotifyTime string `json:"notify_time"` 6 | NotifyType string `json:"notify_type"` 7 | NotifyID string `json:"notify_id"` 8 | SignType string `json:"sign_type"` 9 | Sign string `json:"sign"` 10 | OutTradeNum string `json:"out_trade_no"` 11 | Subject string `json:"subject"` 12 | PayMentType string `json:"payment_type"` 13 | TradeNum string `json:"trade_no"` 14 | TradeStatus string `json:"trade_status"` 15 | GmtPayMent string `json:"gmt_payment"` 16 | GmtClose string `json:"gmt_close"` 17 | SellerEmail string `json:"seller_email"` 18 | BuyerEmail string `json:"buyer_email"` 19 | SellerID string `json:"seller_id"` 20 | BuyerID string `json:"buyer_id"` 21 | Price string `json:"price"` 22 | TotalFee string `json:"total_fee"` 23 | Quantity string `json:"quantity"` 24 | Body string `json:"body"` 25 | Discount string `json:"discount"` 26 | IsTotalFeeAdjust string `json:"is_total_fee_adjust"` 27 | UseCoupon string `json:"use_coupon"` 28 | RefundStatus string `json:"refund_status"` 29 | GmtRefund string `json:"gmt_refund"` 30 | } 31 | 32 | type AliQueryResult struct { 33 | TradeNo string `json:"trade_no"` 34 | OutTradeNo string `json:"out_trade_no"` 35 | OpenID string `json:"open_id"` 36 | BuyerLogonID string `json:"buyer_logon_id"` 37 | TradeStatus string `json:"trade_status"` 38 | TotalAmount string `json:"total_amount"` 39 | ReceiptAmount string `json:"receipt_amount"` 40 | BuyerPayAmount string `json:"BuyerPayAmount"` 41 | PointAmount string `json:"point_amount"` 42 | InvoiceAmount string `json:"invoice_amount"` 43 | SendPayDate string `json:"send_pay_date"` 44 | AlipayStoreID string `json:"alipay_store_id"` 45 | StoreID string `json:"store_id"` 46 | TerminalID string `json:"terminal_id"` 47 | FundBillList []struct { 48 | FundChannel string `json:"fund_channel"` 49 | Amount string `json:"amount"` 50 | } `json:"fund_bill_list"` 51 | StoreName string `json:"store_name"` 52 | BuyerUserID string `json:"buyer_user_id"` 53 | DiscountGoodsDetail string `json:"discount_goods_detail"` 54 | IndustrySepcDetail string `json:"industry_sepc_detail"` 55 | } 56 | -------------------------------------------------------------------------------- /client/aliapp.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/sha1" 8 | "encoding/base64" 9 | "fmt" 10 | "../common" 11 | "log" 12 | "net/url" 13 | "sort" 14 | "strings" 15 | ) 16 | 17 | var defaultAliAppClient *AliAppClient 18 | 19 | type AliAppClient struct { 20 | PartnerID string //合作者ID 21 | SellerID string 22 | AppID string // 应用ID 23 | PrivateKey *rsa.PrivateKey 24 | PublicKey *rsa.PublicKey 25 | } 26 | 27 | func InitAliAppClient(c *AliAppClient) { 28 | defaultAliAppClient = c 29 | } 30 | 31 | // DefaultAliAppClient 得到默认支付宝app客户端 32 | func DefaultAliAppClient() *AliAppClient { 33 | return defaultAliAppClient 34 | } 35 | 36 | func (aa *AliAppClient) Pay(charge *common.Charge) (string, error) { 37 | data := make(map[string]string) 38 | data["service"] = "mobile.securitypay.pay" 39 | data["partner"] = aa.PartnerID 40 | data["_input_charset"] = "utf-8" 41 | data["notify_url"] = charge.CallbackURL 42 | 43 | data["out_trade_no"] = charge.TradeNum 44 | data["subject"] = charge.Describe 45 | data["payment_type"] = "1" 46 | data["seller_id"] = aa.SellerID 47 | data["total_fee"] = fmt.Sprintf("%.2f", float64(charge.MoneyFee)/float64(100)) 48 | data["body"] = charge.Describe 49 | 50 | sign, err := aa.GenSign(data) 51 | if err != nil { 52 | return "", err 53 | } 54 | data["sign"] = sign 55 | data["sign_type"] = "RSA" 56 | 57 | var re []string 58 | for k, v := range data { 59 | re = append(re, fmt.Sprintf("%s=%s", k, v)) 60 | } 61 | return strings.Join(re, "&"), nil 62 | } 63 | 64 | // GenSign 产生签名 65 | func (aa *AliAppClient) GenSign(m map[string]string) (string, error) { 66 | delete(m, "sign_type") 67 | delete(m, "sign") 68 | var data []string 69 | for k, v := range m { 70 | if v == "" { 71 | continue 72 | } 73 | data = append(data, fmt.Sprintf("%s=%s", k, v)) 74 | } 75 | sort.Strings(data) 76 | signData := strings.Join(data, "&") 77 | s := sha1.New() 78 | _, err := s.Write([]byte(signData)) 79 | if err != nil { 80 | log.Println(err) 81 | } 82 | hashByte := s.Sum(nil) 83 | signByte, err := aa.PrivateKey.Sign(rand.Reader, hashByte, crypto.SHA1) 84 | if err != nil { 85 | return "", err 86 | } 87 | return url.QueryEscape(base64.StdEncoding.EncodeToString(signByte)), nil 88 | } 89 | 90 | // CheckSign 检测签名 91 | func (aa *AliAppClient) CheckSign(data string, sign string) error { 92 | signByte, err := base64.StdEncoding.DecodeString(sign) 93 | if err != nil { 94 | return err 95 | } 96 | s := sha1.New() 97 | _, err = s.Write([]byte(data)) 98 | if err != nil { 99 | return err 100 | } 101 | hash := s.Sum(nil) 102 | return rsa.VerifyPKCS1v15(aa.PublicKey, crypto.SHA1, hash[:], signByte) 103 | } 104 | -------------------------------------------------------------------------------- /client/wechatapp.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "encoding/json" 7 | "encoding/xml" 8 | "errors" 9 | "fmt" 10 | "../common" 11 | "../util" 12 | "sort" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | var defaultWechatAppClient *WechatAppClient 18 | 19 | // DefaultWechatAppClient 默认微信app客户端 20 | func DefaultWechatAppClient() *WechatAppClient { 21 | return defaultWechatAppClient 22 | } 23 | 24 | // WechatAppClient 微信app支付 25 | type WechatAppClient struct { 26 | AppID string // AppID 27 | MchID string // 商户号ID 28 | callbackURL string // 回调地址 29 | Key string // 密钥 30 | PayURL string // 支付地址 31 | } 32 | 33 | func InitWechatClient(c *WechatAppClient) { 34 | defaultWechatAppClient = c 35 | } 36 | 37 | // Pay 支付 38 | func (wechat *WechatAppClient) Pay(charge *common.Charge) (string, error) { 39 | var m = make(map[string]string) 40 | m["appid"] = wechat.AppID 41 | m["mch_id"] = wechat.MchID 42 | m["nonce_str"] = util.RandomStr() 43 | m["body"] = charge.Describe 44 | m["out_trade_no"] = charge.TradeNum 45 | m["total_fee"] = fmt.Sprintf("%d", charge.MoneyFee) 46 | m["spbill_create_ip"] = util.LocalIP() 47 | m["notify_url"] = wechat.callbackURL 48 | m["trade_type"] = "APP" 49 | 50 | sign, err := wechat.GenSign(m) 51 | if err != nil { 52 | return "", err 53 | } 54 | m["sign"] = sign 55 | // 转出xml结构 56 | buf := bytes.NewBufferString("") 57 | for k, v := range m { 58 | buf.WriteString(fmt.Sprintf("<%s>", k, v, k)) 59 | } 60 | xmlStr := fmt.Sprintf("%s", buf.String()) 61 | 62 | re, err := HTTPSC.PostData(wechat.PayURL, "text/xml:charset=UTF-8", xmlStr) 63 | if err != nil { 64 | return "", err 65 | } 66 | 67 | var xmlRe common.WeChatReResult 68 | err = xml.Unmarshal(re, &xmlRe) 69 | if err != nil { 70 | return "", err 71 | } 72 | 73 | if xmlRe.ReturnCode != "SUCCESS" { 74 | // 通信失败 75 | return "", errors.New(xmlRe.ReturnMsg) 76 | } 77 | 78 | if xmlRe.ResultCode != "SUCCESS" { 79 | // 支付失败 80 | return "", errors.New(xmlRe.ErrCodeDes) 81 | } 82 | 83 | var c = make(map[string]string) 84 | c["appid"] = wechat.AppID 85 | c["partnerid"] = wechat.MchID 86 | c["prepayid"] = xmlRe.PrepayID 87 | c["package"] = "Sign=WXPay" 88 | c["noncestr"] = util.RandomStr() 89 | c["timestamp"] = fmt.Sprintf("%d", time.Now().Unix()) 90 | 91 | sign2, err := wechat.GenSign(c) 92 | if err != nil { 93 | return "", err 94 | } 95 | //c["signType"] = "MD5" 96 | c["paySign"] = strings.ToUpper(sign2) 97 | 98 | jsonC, err := json.Marshal(c) 99 | if err != nil { 100 | return "", err 101 | } 102 | 103 | return string(jsonC), nil 104 | } 105 | 106 | // GenSign 产生签名 107 | func (wechat *WechatAppClient) GenSign(m map[string]string) (string, error) { 108 | delete(m, "sign") 109 | delete(m, "key") 110 | var signData []string 111 | for k, v := range m { 112 | if v != "" { 113 | signData = append(signData, fmt.Sprintf("%s=%s", k, v)) 114 | } 115 | } 116 | 117 | sort.Strings(signData) 118 | signStr := strings.Join(signData, "&") 119 | signStr = signStr + "&key=" + wechat.Key 120 | c := md5.New() 121 | _, err := c.Write([]byte(signStr)) 122 | if err != nil { 123 | return "", err 124 | } 125 | signByte := c.Sum(nil) 126 | if err != nil { 127 | return "", err 128 | } 129 | return fmt.Sprintf("%x", signByte), nil 130 | } 131 | 132 | // CheckSign 检查签名 133 | func (wechat *WechatAppClient) CheckSign(data string, sign string) error { 134 | signData := data + "&key=" + wechat.Key 135 | c := md5.New() 136 | _, err := c.Write([]byte(signData)) 137 | if err != nil { 138 | return err 139 | } 140 | signOut := fmt.Sprintf("%x", c.Sum(nil)) 141 | if strings.ToUpper(sign) == strings.ToUpper(signOut) { 142 | return nil 143 | } 144 | return errors.New("签名交易错误") 145 | } 146 | -------------------------------------------------------------------------------- /client/aliweb.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/rand" 7 | "crypto/rsa" 8 | "crypto/sha1" 9 | "encoding/base64" 10 | "fmt" 11 | "../common" 12 | "log" 13 | "net/url" 14 | "sort" 15 | "strings" 16 | ) 17 | 18 | var aliWebClient *AliWebClient 19 | 20 | // AliWebClient 支付宝网页支付 21 | type AliWebClient struct { 22 | PartnerID string // 支付宝合作身份ID 23 | SellerID string // 卖家支付宝用户号 24 | AppID string // 支付宝分配给开发者的应用ID ps: 查询订单用 25 | CallbackURL string // 回调接口 26 | PrivateKey *rsa.PrivateKey // 私钥 27 | PublicKey *rsa.PublicKey // 公钥 28 | PayURL string // 支付网管地址 29 | } 30 | 31 | func InitAliWebClient(c *AliWebClient) { 32 | aliWebClient = c 33 | } 34 | 35 | // DefaultAliWebClient 默认支付宝网页支付客户端 36 | func DefaultAliWebClient() *AliWebClient { 37 | return aliWebClient 38 | } 39 | 40 | // Pay 实现支付接口 41 | func (ac *AliWebClient) Pay(charge *common.Charge) (string, error) { 42 | var m = make(map[string]string) 43 | m["service"] = "alipay.wap.create.direct.pay.by.user" 44 | m["partner"] = ac.PartnerID 45 | m["_input_charset"] = "utf-8" 46 | m["notify_url"] = ac.CallbackURL 47 | m["return_url"] = charge.ReturnURL 48 | m["out_trade_no"] = charge.TradeNum 49 | m["subject"] = charge.Describe 50 | m["total_fee"] = fmt.Sprintf("%.2f", float64(charge.MoneyFee)/float64(100)) 51 | m["seller_id"] = ac.SellerID 52 | m["payment_type"] = "1" 53 | m["show_url"] = charge.ShowURL 54 | sign, err := ac.GenSign(m) 55 | if err != nil { 56 | return "", err 57 | } 58 | m["sign"] = sign // 59 | fmt.Println("sign:", sign) 60 | m["sign_type"] = "RSA" 61 | 62 | return ac.ToHTML(m), nil 63 | } 64 | 65 | // GenSign 产生签名 66 | func (ac *AliWebClient) GenSign(m map[string]string) (string, error) { 67 | delete(m, "sign_type") 68 | delete(m, "sign") 69 | var data []string 70 | for k, v := range m { 71 | if v == "" { 72 | continue 73 | } 74 | data = append(data, fmt.Sprintf(`%s=%s`, k, v)) 75 | } 76 | sort.Strings(data) 77 | signData := strings.Join(data, "&") 78 | fmt.Println(signData) 79 | s := sha1.New() 80 | _, err := s.Write([]byte(signData)) 81 | if err != nil { 82 | log.Println(err) 83 | } 84 | hashByte := s.Sum(nil) 85 | signByte, err := ac.PrivateKey.Sign(rand.Reader, hashByte, crypto.SHA1) 86 | if err != nil { 87 | return "", err 88 | } 89 | return url.QueryEscape(base64.StdEncoding.EncodeToString(signByte)), nil 90 | } 91 | 92 | // ToHTML 转换form表单 93 | func (ac *AliWebClient) ToHTML(m map[string]string) string { 94 | buf := bytes.NewBufferString("") 95 | for k, v := range m { 96 | buf.WriteString(fmt.Sprintf(``, k, v)) 97 | } 98 | formatStr := 99 | ` 100 | 101 | 102 |
103 | %s 104 | 105 |
106 | 111 | 112 | ` 113 | return fmt.Sprintf(formatStr, ac.PayURL, buf.String()) 114 | } 115 | 116 | // ToURL 117 | func (ac *AliWebClient) ToURL(m map[string]string) string { 118 | var buf []string 119 | for k, v := range m { 120 | buf = append(buf, fmt.Sprintf("%s=%s", k, v)) 121 | } 122 | return fmt.Sprintf("%s?%s", ac.PayURL, strings.Join(buf, "&")) 123 | } 124 | 125 | // CheckSign 检测签名 126 | func (ac *AliWebClient) CheckSign(signData, sign string) error { 127 | signByte, err := base64.StdEncoding.DecodeString(sign) 128 | if err != nil { 129 | return err 130 | } 131 | s := sha1.New() 132 | _, err = s.Write([]byte(signData)) 133 | if err != nil { 134 | return err 135 | } 136 | hash := s.Sum(nil) 137 | return rsa.VerifyPKCS1v15(ac.PublicKey, crypto.SHA1, hash, signByte) 138 | } 139 | -------------------------------------------------------------------------------- /callback.go: -------------------------------------------------------------------------------- 1 | package pays_with_go 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "./client" 7 | "./common" 8 | "./util" 9 | "net/http" 10 | "sort" 11 | "strings" 12 | "io/ioutil" 13 | "encoding/xml" 14 | ) 15 | 16 | // WeChatCallback 微信app支付 17 | func WeChatAppCallback(w http.ResponseWriter, r *http.Request) (*common.WeChatPayResult, error) { 18 | var returnCode = "FAIL" 19 | var returnMsg = "" 20 | defer func() { 21 | formatStr := ` 22 | ![CDATA[%s]]` 23 | returnBody := fmt.Sprintf(formatStr, returnCode, returnMsg) 24 | w.Write([]byte(returnBody)) 25 | }() 26 | var reXML common.WeChatPayResult 27 | //body := cb.Ctx.Input.RequestBody 28 | body, err := ioutil.ReadAll(r.Body) 29 | if err != nil { 30 | //log.Error(string(body)) 31 | returnCode = "FAIL" 32 | returnMsg = "Bodyerror" 33 | return nil, err 34 | } 35 | err = xml.Unmarshal(body, &reXML) 36 | if err != nil { 37 | //log.Error(err, string(body)) 38 | returnMsg = "参数错误" 39 | returnCode = "FAIL" 40 | return nil, err 41 | } 42 | 43 | if reXML.ReturnCode != "SUCCESS" { 44 | //log.Error(reXML) 45 | returnCode = "FAIL" 46 | return &reXML, errors.New(reXML.ReturnCode) 47 | } 48 | m, err := util.XmlToMap(body) 49 | if err != nil { 50 | //log.Error(err, body) 51 | returnMsg = "参数错误" 52 | returnCode = "FAIL" 53 | return nil, err 54 | } 55 | //log.Info(m) 56 | var signData []string 57 | for k, v := range m { 58 | if k == "sign" { 59 | continue 60 | } 61 | signData = append(signData, fmt.Sprintf("%v=%v", k, v)) 62 | } 63 | sort.Strings(signData) 64 | signData2 := strings.Join(signData, "&") 65 | err = client.DefaultWechatAppClient().CheckSign(signData2, m["sign"]) 66 | if err != nil { 67 | returnCode = "FAIL" 68 | return nil, err 69 | } 70 | 71 | returnCode = "SUCCESS" 72 | return &reXML, nil 73 | } 74 | 75 | //支付宝web端回调 76 | func AliWebCallback(w http.ResponseWriter, r *http.Request) (*common.AliWebPayResult, error) { 77 | var m = make(map[string]string) 78 | var signSlice []string 79 | r.ParseForm() 80 | for k, v := range r.Form { 81 | // k不会有多个值的情况 82 | m[k] = v[0] 83 | if k == "sign" || k == "sign_type" { 84 | continue 85 | } 86 | signSlice = append(signSlice, fmt.Sprintf("%s=%s", k, v[0])) 87 | } 88 | 89 | sort.Strings(signSlice) 90 | signData := strings.Join(signSlice, "&") 91 | if m["sign_type"] != "RSA" { 92 | //错误日志 93 | return nil, errors.New("签名类型未知") 94 | } 95 | 96 | err := client.DefaultAliWebClient().CheckSign(signData, m["sign"]) 97 | if err != nil { 98 | //log.Error("签名验证失败:", err, signData, m) 99 | return nil, err 100 | } 101 | 102 | var aliPay common.AliWebPayResult 103 | err = util.MapStringToStruct(m, &aliPay) 104 | if err != nil { 105 | //log.Error(err) 106 | w.Write([]byte("error")) 107 | return nil, err 108 | } 109 | 110 | // err = biz.AliWebCallBack(&aliPay) 111 | // if err != nil { 112 | // //log.Error(err) 113 | // w.Write([]byte("error")) 114 | // return nil, err 115 | // } 116 | w.Write([]byte("success")) 117 | return &aliPay, nil 118 | } 119 | 120 | // 支付宝app支付回调 121 | func AliAppCallback(w http.ResponseWriter, r *http.Request) (*common.AliWebPayResult, error) { 122 | var m = make(map[string]string) 123 | var signSlice []string 124 | r.ParseForm() 125 | for k, v := range r.Form { 126 | m[k] = v[0] 127 | if k == "sign" || k == "sign_type" { 128 | continue 129 | } 130 | signSlice = append(signSlice, fmt.Sprintf("%s=%s", k, v[0])) 131 | } 132 | sort.Strings(signSlice) 133 | signData := strings.Join(signSlice, "&") 134 | if m["sign_type"] != "RSA" { 135 | //log.Error(m) 136 | return nil, errors.New("签名类型未知") 137 | } 138 | 139 | err := client.DefaultAliAppClient().CheckSign(signData, m["sign"]) 140 | if err != nil { 141 | //log.Error(err, m, signData) 142 | w.Write([]byte("error")) 143 | return nil, err 144 | } 145 | 146 | var aliPay common.AliWebPayResult 147 | err = util.MapStringToStruct(m, &aliPay) 148 | if err != nil { 149 | //log.Error(err) 150 | w.Write([]byte("error")) 151 | return nil, err 152 | } 153 | 154 | // err = biz.AliAppCallBack(&aliPay) 155 | // if err != nil { 156 | // //log.Error(err) 157 | // w.Write([]byte("error")) 158 | // } 159 | 160 | w.Write([]byte("success")) 161 | return &aliPay, nil 162 | } 163 | 164 | -------------------------------------------------------------------------------- /client/wechatweb.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "encoding/json" 7 | "encoding/xml" 8 | "errors" 9 | "fmt" 10 | "github.com/guidao/gopay/common" 11 | "github.com/guidao/gopay/util" 12 | "sort" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | var defaultWechatWebClient *WechatWebClient 18 | 19 | func DefaultWechatWebClient() *WechatWebClient { 20 | return defaultWechatWebClient 21 | } 22 | 23 | // WechatWebClient 微信公众号支付 24 | type WechatWebClient struct { 25 | AppID string // 公众账号ID 26 | MchID string // 商户号ID 27 | CallbackURL string // 回调地址 28 | Key string // 密钥 29 | PayURL string // 支付地址 30 | QueryURL string // 查询地址 31 | } 32 | 33 | // Pay 支付 34 | func (wechat *WechatWebClient) Pay(charge *common.Charge) (string, error) { 35 | var m = make(map[string]string) 36 | m["appid"] = wechat.AppID 37 | m["mch_id"] = wechat.MchID 38 | m["nonce_str"] = util.RandomStr() 39 | m["body"] = charge.Describe 40 | m["out_trade_no"] = charge.TradeNum 41 | m["total_fee"] = fmt.Sprintf("%d", charge.MoneyFee) 42 | m["spbill_create_ip"] = util.LocalIP() 43 | m["notify_url"] = wechat.CallbackURL 44 | m["trade_type"] = "JSAPI" 45 | m["openid"] = charge.OpenID 46 | 47 | sign, err := wechat.GenSign(m) 48 | if err != nil { 49 | return "", err 50 | } 51 | m["sign"] = sign 52 | // 转出xml结构 53 | buf := bytes.NewBufferString("") 54 | for k, v := range m { 55 | buf.WriteString(fmt.Sprintf("<%s>", k, v, k)) 56 | } 57 | xmlStr := fmt.Sprintf("%s", buf.String()) 58 | 59 | re, err := HTTPSC.PostData(wechat.PayURL, "text/xml:charset=UTF-8", xmlStr) 60 | if err != nil { 61 | return "", err 62 | } 63 | var xmlRe common.WeChatReResult 64 | err = xml.Unmarshal(re, &xmlRe) 65 | if err != nil { 66 | return "", err 67 | } 68 | 69 | if xmlRe.ReturnCode != "SUCCESS" { 70 | // 通信失败 71 | return "", errors.New(xmlRe.ReturnMsg) 72 | } 73 | 74 | if xmlRe.ResultCode != "SUCCESS" { 75 | // 支付失败 76 | return "", errors.New(xmlRe.ErrCodeDes) 77 | } 78 | 79 | var c = make(map[string]string) 80 | c["appId"] = wechat.AppID 81 | c["timeStamp"] = fmt.Sprintf("%d", time.Now().Unix()) 82 | c["nonceStr"] = util.RandomStr() 83 | c["package"] = fmt.Sprintf("prepay_id=%s", xmlRe.PrepayID) 84 | c["signType"] = "MD5" 85 | 86 | sign2, err := wechat.GenSign(c) 87 | if err != nil { 88 | return "", err 89 | } 90 | c["paySign"] = sign2 91 | 92 | jsonC, err := json.Marshal(c) 93 | if err != nil { 94 | return "", err 95 | } 96 | 97 | return string(jsonC), nil 98 | } 99 | 100 | // GenSign 产生签名 101 | func (wechat *WechatWebClient) GenSign(m map[string]string) (string, error) { 102 | delete(m, "sign") 103 | delete(m, "Key") 104 | var signData []string 105 | for k, v := range m { 106 | if v != "" { 107 | signData = append(signData, fmt.Sprintf("%s=%s", k, v)) 108 | } 109 | } 110 | 111 | sort.Strings(signData) 112 | signStr := strings.Join(signData, "&") 113 | signStr = signStr + "&Key=" + wechat.Key 114 | c := md5.New() 115 | _, err := c.Write([]byte(signStr)) 116 | if err != nil { 117 | return "", err 118 | } 119 | signByte := c.Sum(nil) 120 | if err != nil { 121 | return "", err 122 | } 123 | return strings.ToUpper(fmt.Sprintf("%x", signByte)), nil 124 | } 125 | 126 | // CheckSign 检查签名 127 | func (wechat *WechatWebClient) CheckSign(data string, sign string) error { 128 | signData := data + "&Key=" + wechat.Key 129 | c := md5.New() 130 | _, err := c.Write([]byte(signData)) 131 | if err != nil { 132 | return err 133 | } 134 | signOut := fmt.Sprintf("%x", c.Sum(nil)) 135 | if strings.ToUpper(sign) == strings.ToUpper(signOut) { 136 | return nil 137 | } 138 | return errors.New("签名交易错误") 139 | } 140 | 141 | // QueryOrder 查询订单 142 | func (wechat *WechatWebClient) QueryOrder(tradeNum string) (*common.WeChatQueryResult, error) { 143 | var m = make(map[string]string) 144 | m["appid"] = wechat.AppID 145 | m["mch_id"] = wechat.MchID 146 | m["out_trade_no"] = tradeNum 147 | m["nonce_str"] = util.RandomStr() 148 | 149 | sign, err := wechat.GenSign(m) 150 | if err != nil { 151 | return nil, err 152 | } 153 | m["sign"] = sign 154 | 155 | buf := bytes.NewBufferString("") 156 | for k, v := range m { 157 | buf.WriteString(fmt.Sprintf("<%s>", k, v, k)) 158 | } 159 | xmlStr := fmt.Sprintf("%s", buf.String()) 160 | 161 | result, err := HTTPSC.PostData(wechat.QueryURL, "text/xml:charset=UTF-8", xmlStr) 162 | if err != nil { 163 | return nil, err 164 | } 165 | 166 | var queryResult common.WeChatQueryResult 167 | err = xml.Unmarshal(result, &queryResult) 168 | return &queryResult, err 169 | } 170 | --------------------------------------------------------------------------------