diff --git a/common.go b/common.go new file mode 100644 index 0000000..75fc2dd --- /dev/null +++ b/common.go @@ -0,0 +1,26 @@ +package wechat + +import ( + "crypto/md5" + "encoding/hex" + "strings" +) + +//密码加密 +func Md5Str(str ...string) string { + + var build strings.Builder + if len(str) > 0 { + for _, v := range str { + build.WriteString(v) + } + } else { + return "" + } + + h := md5.New() + h.Write([]byte(build.String())) // 需要加密的字符串 + cipher2Str := h.Sum(nil) + sMd5 := hex.EncodeToString(cipher2Str) // 输出加密结果 + return sMd5 +} diff --git a/micropay.go b/micropay.go new file mode 100644 index 0000000..eb577a4 --- /dev/null +++ b/micropay.go @@ -0,0 +1,337 @@ +package wechat + +import ( + "encoding/xml" + "fmt" + "log" + "reflect" + "strings" + "time" + + "git.tetele.net/tgo/network" + + "git.tetele.net/tgo/crypter" + + "git.tetele.net/tgo/helper" +) + +//请求支付 +type PayXml struct { + XMLName xml.Name `xml:"xml"` + Appid string `xml:"appid"` + MchId string `xml:"mch_id"` + DeviceInfo string `xml:"device_info"` + NonceStr string `xml:"nonce_str"` + Sign string `xml:"sign"` + Body string `xml:"body"` + OutTradeNo string `xml:"out_trade_no"` + TotalFee int `xml:"total_fee"` + SpbillCreateIp string `xml:"spbill_create_ip"` + AuthCode string `xml:"auth_code"` +} + +//支付结果 +type PayReturnXml struct { + ReturnCode string `xml:"return_code"` + ReturnMsg string `xml:"return_msg"` + Appid string `xml:"appid"` + MchId string `xml:"mch_id"` + DeviceInfo string `xml:"device_info"` + NonceStr string `xml:"nonce_str"` + Sign string `xml:"sign"` + ResultCode string `xml:"result_code"` + ErrCode string `xml:"err_code"` + ErrCodeDes string `xml:"err_code_des"` + Openid string `xml:"openid"` + IsSubscribe string `xml:"is_subscribe"` + TradeType string `xml:"trade_type"` + BankType string `xml:"bank_type"` + FeeType string `xml:"fee_type"` + TotalFee string `xml:"total_fee"` + SettlementTotalFee string `xml:"settlement_total_fee"` + CouponFee string `xml:"coupon_fee"` + CashFeeType string `xml:"cash_fee_type"` + CashFee string `xml:"cash_fee"` + TransactionId string `xml:"transaction_id"` + OutTradeNo string `xml:"out_trade_no"` + Attach string `xml:"attach"` + TimeEnd string `xml:"time_end"` + PromotionDetail string `xml:"promotion_detail"` +} + +//查询支付 +type QueryXml struct { + XMLName xml.Name `xml:"xml"` + Appid string `xml:"appid"` + MchId string `xml:"mch_id"` + OutTradeNo string `xml:"out_trade_no"` + NonceStr string `xml:"nonce_str"` + Sign string `xml:"sign"` +} + +//查询结果 +type QueryReturnXml 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"` + ErrCode string `xml:"err_code"` + ErrCodeDes string `xml:"err_code_des"` + DeviceInfo string `xml:"device_info"` + Openid string `xml:"openid"` + IsSubscribe string `xml:"is_subscribe"` + TradeType string `xml:"trade_type"` + TradeState string `xml:"trade_state"` + BankType string `xml:"bank_type"` + TotalFee string `xml:"total_fee"` + SettlementTotalFee string `xml:"settlement_total_fee"` + FeeType string `xml:"fee_type"` + CashFee string `xml:"cash_fee"` + CashFeeType string `xml:"cash_fee_type"` + CouponFee string `xml:"coupon_fee"` + CouponCount string `xml:"coupon_count"` + TransactionId string `xml:"transaction_id"` + OutTradeNo string `xml:"out_trade_no"` + Attach string `xml:"attach"` + TimeEnd string `xml:"time_end"` + TradeStateDesc string `xml:"trade_state_desc"` +} + +// 撤消 +type ReverseXml struct { + XMLName xml.Name `xml:"xml"` + Appid string `xml:"appid"` + MchId string `xml:"mch_id"` + OutTradeNo string `xml:"out_trade_no"` + NonceStr string `xml:"nonce_str"` + Sign string `xml:"sign"` +} + +//撤消结果 +type ReverseReturnXml 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"` + ErrCode string `xml:"err_code"` + ErrCodeDes string `xml:"err_code_des"` + Recall string `xml:"recall"` +} + +/** + * 微信二维码支付 + * 2022/02/07 + * @param device_info 设备号 + * @param body 商品描述 + * @param out_trade_no 商户订单号 + * @param total_fee 订单金额 + * @param auth_code 付款码 + */ + +func MicroPay(appid, mchid, mch_key, device_info, body, out_trade_no, auth_code, total_fee string) (bool, string, PayReturnXml) { + + log.Println("wechat micropay") + + var requestxml PayXml + + var retXml PayReturnXml + + requestxml.Appid = appid + requestxml.MchId = mchid + requestxml.DeviceInfo = device_info + requestxml.NonceStr = helper.GetRandomString(30) + requestxml.Body = body + requestxml.OutTradeNo = out_trade_no + requestxml.TotalFee = helper.ToInt(helper.FloatMul(total_fee, 100)) + requestxml.SpbillCreateIp = network.GetLocalIp() + requestxml.AuthCode = auth_code + + signStr := helper.StringJoin("appid=", requestxml.Appid, "&auth_code=", requestxml.AuthCode, + "&body=", requestxml.Body, "&device_info=", requestxml.DeviceInfo, + "&mch_id=", requestxml.MchId, "&nonce_str=", requestxml.NonceStr, + "&out_trade_no=", requestxml.OutTradeNo, "&spbill_create_ip=", requestxml.SpbillCreateIp, + "&total_fee=", helper.ToString(requestxml.TotalFee), "&key=", mch_key) + + signMd5 := Md5Str(signStr) + + requestxml.Sign = strings.ToUpper(signMd5) + + reqXmlJson, err := xml.Marshal(requestxml) + + log.Println(requestxml, string(reqXmlJson), err) + + url := "https://api.mch.weixin.qq.com/pay/micropay" + ret, err := network.SendXml("POST", url, requestxml) + + log.Println("wechat micropay pay result:", string(ret), err) + + if err != nil { + log.Println("Err:", err) + return false, "微信支付失败,请检查网络", retXml + } + + err = xml.Unmarshal(ret, &retXml) + if err != nil { + log.Println("xml Unmarshal Err:", retXml, err) + return false, "未知的支付结果", retXml + } + + if retXml.ResultCode == "SUCCESS" { + + retMap := XmlStructToMap(retXml) + + //验证sign + signStr = helper.HttpBuildQuery(retMap) + "&key=" + mch_key + + sign := strings.ToUpper(Md5Str(signStr)) + + if sign != retXml.Sign { + log.Println("Err:签名错误", string(ret), sign) + return false, "支付结果签名错误", retXml + } + } else { + return false, retXml.ReturnMsg, retXml + } + + return true, "", retXml +} + +func MicroPayQuery(appid, mchid, out_trade_no, mch_key string) (bool, string, QueryReturnXml) { + + log.Println("wechat micropay query", out_trade_no) + + var requestxml QueryXml + + var retXml QueryReturnXml + + requestxml.Appid = appid + requestxml.MchId = mchid + requestxml.NonceStr = helper.GetRandomString(30) + requestxml.OutTradeNo = out_trade_no + + signStr := helper.StringJoin("appid=", requestxml.Appid, + "&mch_id=", requestxml.MchId, "&nonce_str=", requestxml.NonceStr, + "&out_trade_no=", requestxml.OutTradeNo, "&key=", mch_key) + + signMd5 := Md5Str(signStr) + + requestxml.Sign = strings.ToUpper(signMd5) + + url := "https://api.mch.weixin.qq.com/pay/orderquery" + ret, err := network.SendXml("POST", url, requestxml) + + if err != nil { + log.Println("Err:", err) + return false, "微信支付失败,请检查网络", retXml + } + + err = xml.Unmarshal(ret, &retXml) + if err != nil { + log.Println("xml Unmarshal Err:", retXml, err) + return false, "未知的支付结果", retXml + } + + if retXml.ReturnCode == "SUCCESS" { + + retMap := XmlStructToMap(retXml) + + //验证sign + signStr = helper.HttpBuildQuery(retMap) + "&key=" + mch_key + + sign := strings.ToUpper(Md5Str(signStr)) + + if sign != retXml.Sign { + log.Println("Err:签名错误", string(ret), sign) + return false, "查询支付结果签名错误", retXml + } + } else { + return false, retXml.ReturnMsg, retXml + } + return true, "", retXml +} + +/** + * 撤消订单 + * 2023/06/13 + */ +func MicroPayReverse(appid, mchid, out_trade_no, mch_key string) (bool, string, QueryReturnXml) { + + log.Println("wechat micropay reverse", out_trade_no) + + var requestxml ReverseXml + + var retXml ReverseReturnXml + + requestxml.Appid = appid + requestxml.MchId = mchid + requestxml.NonceStr = helper.GetRandomString(30) + requestxml.OutTradeNo = out_trade_no + + signStr := helper.StringJoin("appid=", requestxml.Appid, + "&mch_id=", requestxml.MchId, "&nonce_str=", requestxml.NonceStr, + "&out_trade_no=", requestxml.OutTradeNo, "&key=", mch_key) + + signMd5 := Md5Str(signStr) + + requestxml.Sign = strings.ToUpper(signMd5) + + url := "https://api.mch.weixin.qq.com/secapi/pay/reverse" + ret, err := network.SendXml("POST", url, requestxml) + + if err != nil { + log.Println("Err:", err) + return false, "撤消微信支付失败,请检查网络", retXml + } + + err = xml.Unmarshal(ret, &retXml) + if err != nil { + log.Println("xml Unmarshal Err:", retXml, err) + return false, "未知的撤消结果", retXml + } + + if retXml.ReturnCode == "SUCCESS" { + + retMap := XmlStructToMap(retXml) + + //验证sign + signStr = helper.HttpBuildQuery(retMap) + "&key=" + mch_key + + sign := strings.ToUpper(Md5Str(signStr)) + + if sign != retXml.Sign { + log.Println("Err:签名错误", string(ret), sign) + return false, "查询支付结果签名错误", retXml + } + } else { + return false, retXml.ReturnMsg, retXml + } + return true, "", retXml +} + +func XmlStructToMap(retXml interface{}) map[string]string { + + t := reflect.TypeOf(retXml) + v := reflect.ValueOf(retXml) + + //转为map,验签 + retMap := make(map[string]string) + var retValue string + var tag string + for k := 0; k < t.NumField(); k++ { + if t.Field(k).Name != "Sign" { + retValue = helper.ToString(v.Field(k).Interface()) + tag = fmt.Sprintf("%s", t.Field(k).Tag) + tag = strings.ReplaceAll(tag, "xml:", "") + tag = strings.ReplaceAll(tag, "\"", "") + retMap[tag] = retValue + } + } + + return retMap +}