package pay import ( "crypto/md5" "encoding/hex" "encoding/xml" "fmt" "math/rand" "sort" "strings" "time" "github.com/cloudprint/cloudprint-backend/config" "github.com/iGoogle-ink/gopay" "github.com/iGoogle-ink/gopay/wechat" ) // RandomString generates a random string with given length func RandomString(length int) string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, length) for i := range b { b[i] = charset[rand.Intn(len(charset))] } return string(b) } type WeChatPay struct { appID string mchID string apiKey string } type UnifiedOrderResponse struct { ReturnCode string `xml:"return_code"` ReturnMsg string `xml:"return_msg"` ResultCode string `xml:"result_code"` PrepayID string `xml:"prepay_id,omitempty"` CodeURL string `xml:"code_url,omitempty"` MWebURL string `xml:"mweb_url,omitempty"` ErrCode string `xml:"err_code,omitempty"` ErrMsg string `xml:"err_msg,omitempty"` TransactionID string `xml:"transaction_id,omitempty"` TradeState string `xml:"trade_state,omitempty"` TotalFee int `xml:"total_fee,omitempty"` } type PayCallback struct { ReturnCode string `xml:"return_code"` ReturnMsg string `xml:"return_msg"` AppID string `xml:"appid"` MchID string `xml:"mch_id"` NonceStr string `xml:"nonce_str"` Sign string `xml:"sign"` ResultCode string `xml:"result_code"` TransactionID string `xml:"transaction_id"` OutTradeNo string `xml:"out_trade_no"` TotalFee int `xml:"total_fee"` TimeEnd string `xml:"time_end"` } func NewWeChatPay(cfg *config.WeChatConfig) *WeChatPay { return &WeChatPay{ appID: cfg.AppID, mchID: cfg.MchID, apiKey: cfg.ApiKey, } } func (p *WeChatPay) createSign(params map[string]string) string { keys := make([]string, 0, len(params)) for k := range params { keys = append(keys, k) } sort.Strings(keys) var signParts []string for _, k := range keys { if params[k] != "" && k != "sign" { signParts = append(signParts, fmt.Sprintf("%s=%s", k, params[k])) } } signStr := strings.Join(signParts, "&") signStr += "&key=" + p.apiKey md5Hash := md5.New() md5Hash.Write([]byte(signStr)) return strings.ToUpper(hex.EncodeToString(md5Hash.Sum(nil))) } func (p *WeChatPay) UnifiedOrder(orderNo string, amount float64, description, openID, clientIP string) (*UnifiedOrderResponse, error) { nonceStr := RandomString(32) totalFee := int(amount * 100) // Convert to fen // Build request body using gopay.BodyMap body := gopay.BodyMap{} body.Set("appid", p.appID) body.Set("mch_id", p.mchID) body.Set("nonce_str", nonceStr) body.Set("body", description) body.Set("out_trade_no", orderNo) body.Set("total_fee", totalFee) body.Set("spbill_create_ip", clientIP) body.Set("trade_type", "JSAPI") body.Set("openid", openID) // Use go-pay client to call unified order client := wechat.NewClient(p.mchID, p.apiKey, p.appID, false) resp, err := client.UnifiedOrder(body) if err != nil { return nil, fmt.Errorf("unified order failed: %w", err) } result := &UnifiedOrderResponse{ ReturnCode: resp.ReturnCode, ReturnMsg: resp.ReturnMsg, ResultCode: resp.ResultCode, PrepayID: resp.PrepayId, CodeURL: resp.CodeUrl, MWebURL: resp.MwebUrl, ErrCode: resp.ErrCode, } return result, nil } func (p *WeChatPay) QueryOrder(orderNo string) (string, string, error) { nonceStr := RandomString(32) body := gopay.BodyMap{} body.Set("appid", p.appID) body.Set("mch_id", p.mchID) body.Set("nonce_str", nonceStr) body.Set("out_trade_no", orderNo) client := wechat.NewClient(p.mchID, p.apiKey, p.appID, false) resp, _, err := client.QueryOrder(body) if err != nil { return "", "", err } return resp.TradeState, resp.TotalFee, nil } func (p *WeChatPay) ParseCallback(body []byte) (*PayCallback, error) { var callback PayCallback if err := xml.Unmarshal(body, &callback); err != nil { return nil, fmt.Errorf("failed to parse callback: %w", err) } return &callback, nil } func (p *WeChatPay) ValidateCallback(callback *PayCallback) bool { params := map[string]string{ "return_code": callback.ReturnCode, "return_msg": callback.ReturnMsg, "appid": callback.AppID, "mch_id": callback.MchID, "nonce_str": callback.NonceStr, "result_code": callback.ResultCode, "transaction_id": callback.TransactionID, "out_trade_no": callback.OutTradeNo, "total_fee": fmt.Sprintf("%d", callback.TotalFee), "time_end": callback.TimeEnd, } expectedSign := p.createSign(params) return callback.Sign == expectedSign } func (p *WeChatPay) GenerateJSAPIPayParams(prepayID string) (map[string]string, error) { nonceStr := RandomString(32) timestamp := fmt.Sprintf("%d", time.Now().Unix()) params := map[string]string{ "appId": p.appID, "timeStamp": timestamp, "nonceStr": nonceStr, "package": "prepay_id=" + prepayID, "signType": "MD5", } paySign := p.createSign(map[string]string{ "appId": p.appID, "timeStamp": timestamp, "nonceStr": nonceStr, "package": "prepay_id=" + prepayID, "signType": "MD5", }) params["paySign"] = paySign return params, nil }