| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
c3faf5bc5e | 增加发送模板消息跳转小程序 | 1 year ago |
|
|
38520a86e7 | 增加发送模板消息 | 2 years ago |
|
|
2373d13446 | 修改扫码支付方法 | 2 years ago |
|
|
de651b674e | 修改扫码支付方法 | 2 years ago |
|
|
30cc37109b | 增加扫码支付方法 | 2 years ago |
|
|
524c79ec91 | 打印二维码生成结果 | 2 years ago |
|
|
535afb769b | 小程序码路径&字符被转义问题 | 2 years ago |
|
|
4046688a55 | 小程序码问题debug | 2 years ago |
|
|
8e322f45cc | 小程序码问题debug | 2 years ago |
|
|
229de89019 | 小程序码打印错误码 | 2 years ago |
|
|
e99270d9d8 | 字段名称修改 | 3 years ago |
|
|
1d2e6fab6f | 增加微信退款 | 3 years ago |
|
|
a237f130a8 | 修复调用方法 | 3 years ago |
|
|
97e2926e75 | 增加查询支付结果方法 | 3 years ago |
|
|
e384fa6cbb | 修改openid.go | 3 years ago |
|
|
b389876023 | 修改获取公众号openid | 3 years ago |
|
|
6e67c0d63b | 增加获取公众号openid及用户信息 | 3 years ago |
|
|
50a513dee1 | 添加微信公众号授权链接配置 | 3 years ago |
|
|
5a39f9d8c0 | 修改依赖 | 3 years ago |
| @ -0,0 +1,51 @@ | |||
| package wechat | |||
| import ( | |||
| "crypto/md5" | |||
| "encoding/hex" | |||
| "net" | |||
| "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 | |||
| } | |||
| /** | |||
| * 取本地IP | |||
| */ | |||
| func GetLocalIp() string { | |||
| addrs, err := net.InterfaceAddrs() | |||
| if err != nil { | |||
| return "" | |||
| } | |||
| for _, address := range addrs { | |||
| // 检查ip地址判断是否回环地址 | |||
| if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { | |||
| if ipnet.IP.To4() != nil && !ipnet.IP.IsLinkLocalUnicast() { | |||
| return ipnet.IP.String() | |||
| } | |||
| } | |||
| } | |||
| return "" | |||
| } | |||
| @ -0,0 +1,334 @@ | |||
| package wechat | |||
| import ( | |||
| "encoding/xml" | |||
| "errors" | |||
| "fmt" | |||
| "log" | |||
| "reflect" | |||
| "strings" | |||
| "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) (PayReturnXml, error) { | |||
| 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 = 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 := SendXml("POST", url, requestxml) | |||
| log.Println("wechat micropay pay result:", string(ret), err) | |||
| if err != nil { | |||
| log.Println("Err:", err) | |||
| return retXml, errors.New("微信支付失败,请检查网络") | |||
| } | |||
| err = xml.Unmarshal(ret, &retXml) | |||
| if err != nil { | |||
| log.Println("xml Unmarshal Err:", retXml, err) | |||
| return retXml, errors.New("未知的支付结果") | |||
| } | |||
| 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 retXml, errors.New("支付结果签名错误") | |||
| } | |||
| } else { | |||
| return retXml, errors.New(retXml.ReturnMsg) | |||
| } | |||
| return retXml, nil | |||
| } | |||
| func MicroPayQuery(appid, mchid, out_trade_no, mch_key string) (QueryReturnXml, error) { | |||
| 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 := SendXml("POST", url, requestxml) | |||
| if err != nil { | |||
| log.Println("Err:", err) | |||
| return retXml, errors.New("微信支付失败,请检查网络") | |||
| } | |||
| err = xml.Unmarshal(ret, &retXml) | |||
| if err != nil { | |||
| log.Println("xml Unmarshal Err:", retXml, err) | |||
| return retXml, errors.New("未知的支付结果") | |||
| } | |||
| 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 retXml, errors.New("查询支付结果签名错误") | |||
| } | |||
| } else { | |||
| return retXml, errors.New(retXml.ReturnMsg) | |||
| } | |||
| return retXml, nil | |||
| } | |||
| /** | |||
| * 撤消订单 | |||
| * 2023/06/13 | |||
| */ | |||
| func MicroPayReverse(appid, mchid, out_trade_no, mch_key string) (ReverseReturnXml, error) { | |||
| 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 := SendXml("POST", url, requestxml) | |||
| if err != nil { | |||
| log.Println("Err:", err) | |||
| return retXml, errors.New("撤消微信支付失败,请检查网络") | |||
| } | |||
| err = xml.Unmarshal(ret, &retXml) | |||
| if err != nil { | |||
| log.Println("xml Unmarshal Err:", retXml, err) | |||
| return retXml, errors.New("未知的撤消结果") | |||
| } | |||
| 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 retXml, errors.New("查询支付结果签名错误") | |||
| } | |||
| } else { | |||
| return retXml, errors.New(retXml.ReturnMsg) | |||
| } | |||
| return retXml, nil | |||
| } | |||
| 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 | |||
| } | |||
| @ -0,0 +1,109 @@ | |||
| package wechat | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| ) | |||
| type wx_template_msg_res struct { | |||
| Errcode int `json:"errcode"` | |||
| Errmsg string `json:"errmsg"` | |||
| msgid int `json:"msgid"` | |||
| } | |||
| func SendTemplateMessage(access_token string, touser, template_id, url string, data map[string]interface{}, client_msg_id, miniapp_appid, miniapp_pagepath string) (wx_template_msg_res, error) { | |||
| var res wx_template_msg_res | |||
| if touser == "" { | |||
| return res, errors.New("缺少用户openid") | |||
| } | |||
| if template_id == "" { | |||
| return res, errors.New("缺少消息模板") | |||
| } | |||
| if access_token == "" { | |||
| return res, errors.New("缺少access token") | |||
| } | |||
| api_url := fmt.Sprintf(MP_TEMPLATE_MESSAGE_API, access_token) | |||
| msg := map[string]interface{}{ | |||
| "touser": touser, | |||
| "template_id": template_id, | |||
| "url": url, | |||
| "data": data, | |||
| } | |||
| if client_msg_id != "" { | |||
| msg["client_msg_id"] = client_msg_id | |||
| } | |||
| if miniapp_appid != "" { | |||
| msg["miniprogram"] = map[string]string{ | |||
| "appid": miniapp_appid, | |||
| "pagepath": miniapp_pagepath, | |||
| } | |||
| } | |||
| msg_json, err := json.Marshal(msg) | |||
| if err != nil { | |||
| return res, err | |||
| } | |||
| data_byte, err := PostJson(api_url, msg_json) | |||
| if err == nil { | |||
| err = json.Unmarshal(data_byte, &res) | |||
| } | |||
| return res, err | |||
| } | |||
| func SendTemplateMessageApplet(access_token string, touser, template_id string, miniprogram map[string]string, data map[string]interface{}, client_msg_id, miniapp_appid, miniapp_pagepath string) (wx_template_msg_res, error) { | |||
| var res wx_template_msg_res | |||
| if touser == "" { | |||
| return res, errors.New("缺少用户openid") | |||
| } | |||
| if template_id == "" { | |||
| return res, errors.New("缺少消息模板") | |||
| } | |||
| if access_token == "" { | |||
| return res, errors.New("缺少access token") | |||
| } | |||
| api_url := fmt.Sprintf(MP_TEMPLATE_MESSAGE_API, access_token) | |||
| msg := map[string]interface{}{ | |||
| "touser": touser, | |||
| "template_id": template_id, | |||
| "data": data, | |||
| } | |||
| if len(miniprogram) > 0 { | |||
| msg["miniprogram"] = miniprogram | |||
| } | |||
| if client_msg_id != "" { | |||
| msg["client_msg_id"] = client_msg_id | |||
| } | |||
| if miniapp_appid != "" { | |||
| msg["miniprogram"] = map[string]string{ | |||
| "appid": miniapp_appid, | |||
| "pagepath": miniapp_pagepath, | |||
| } | |||
| } | |||
| msg_json, err := json.Marshal(msg) | |||
| if err != nil { | |||
| return res, err | |||
| } | |||
| data_byte, err := PostJson(api_url, msg_json) | |||
| if err == nil { | |||
| err = json.Unmarshal(data_byte, &res) | |||
| } | |||
| return res, err | |||
| } | |||
| @ -0,0 +1,46 @@ | |||
| package wechat | |||
| import ( | |||
| "testing" | |||
| ) | |||
| func Test_SendTemplateMessage(t *testing.T) { | |||
| // appid := "wxedaca3ab498ec6c0" | |||
| // appsecret := "6c7e567181dba2fe23692339d19ee16a" | |||
| touser := "o4AH1jm_rnjDV4-4nsFhGhk4wIEQ" | |||
| template_id := "avBk4QTEoA17c4G72XHet9gpGQtonW0ck2ewQWSsUwU" | |||
| url := "https://shopv2.tetele.net" | |||
| data := map[string]interface{}{ | |||
| "first": map[string]interface{}{ | |||
| "value": "已预约成功", | |||
| }, | |||
| "keyword1": map[string]interface{}{ | |||
| "value": "巧克力", | |||
| }, | |||
| "keyword2": map[string]interface{}{ | |||
| "value": "2014年9月22日", | |||
| }, | |||
| "remark": map[string]interface{}{ | |||
| "value": "请点击详情", | |||
| }, | |||
| } | |||
| client_msg_id := "" | |||
| miniapp_appid := "" | |||
| miniapp_pagepath := "" | |||
| // mpAT, err := GetAccessToken(appid, appsecret) | |||
| // t.Log("access token:", mpAT) | |||
| // if err==nil{ | |||
| // access_token = mpAT..AccessToken | |||
| // } | |||
| // if err != nil { | |||
| // t.Error("access token error:", err) | |||
| // } | |||
| access_token := "71_sgGezbuBtu94tgRuhzSJHiDvgoOI2mS5kpYyeaynnvILmpPPHEclUmhKq74fj0M3HRjZk7hcNfB54tAJvNZPOrZleXQX2dyWZm_8tyu5m1YpAb8qLrwRnUNRdowMJXhAIAVSG" | |||
| ret, err := SendTemplateMessage(access_token, touser, template_id, url, data, client_msg_id, miniapp_appid, miniapp_pagepath) | |||
| t.Log(ret) | |||
| t.Log(err) | |||
| } | |||
| @ -0,0 +1,14 @@ | |||
| package wechat | |||
| import ( | |||
| "testing" | |||
| ) | |||
| func Test_GetMpOpenid(t *testing.T) { | |||
| appid := "wx470b16375ed3c1ae" | |||
| appsecret := "cd567382c953c478626f4b7446c3508e" | |||
| code := "053gTKkl2FAV394okUkl2j4EvB0gTKkZ" | |||
| ret, err := GetMpOpenid(appid, appsecret, code) | |||
| t.Log(ret) | |||
| t.Log(err) | |||
| } | |||
| @ -0,0 +1,111 @@ | |||
| package wechat | |||
| import ( | |||
| "context" | |||
| "crypto/rsa" | |||
| "crypto/x509" | |||
| "errors" | |||
| "io/ioutil" | |||
| "log" | |||
| "git.tetele.net/tgo/helper" | |||
| "github.com/wechatpay-apiv3/wechatpay-go/core" | |||
| "github.com/wechatpay-apiv3/wechatpay-go/core/option" | |||
| "github.com/wechatpay-apiv3/wechatpay-go/utils" | |||
| ) | |||
| /** | |||
| * 查询支付情况 | |||
| * wx_appid 公众号 appid | |||
| * wx_mch_id 商户号 | |||
| * wx_mch_serial_no 支付密钥 | |||
| * wx_mch_apiclient_key 商户私钥 | |||
| * wx_pay_cert 平台证书 | |||
| */ | |||
| func GetPayResult(dbname, order_sn, wx_appid, wx_mch_id, wx_mch_serial_no, wx_mch_apiclient_key, wx_pay_cert string) (bool, map[string]interface{}, error) { | |||
| var ( | |||
| ctx context.Context = context.Background() | |||
| client *core.Client | |||
| ) | |||
| if wx_appid == "" { | |||
| return false, nil, errors.New("没有appid") | |||
| } | |||
| if wx_mch_id == "" { | |||
| return false, nil, errors.New("没有商户号") | |||
| } | |||
| privateKey, err := utils.LoadPrivateKey(wx_mch_apiclient_key) | |||
| if err != nil { | |||
| log.Println("商户私钥有误:", err) | |||
| return false, nil, errors.New("商户私钥不正确") | |||
| } | |||
| wechatPayCertificate, err := utils.LoadCertificate(wx_pay_cert) | |||
| if err != nil { | |||
| log.Println("平台证书有误:", err) | |||
| return false, nil, errors.New("平台证书不正确") | |||
| } | |||
| client, err = CreatePayClient(ctx, wechatPayCertificate, privateKey, wx_mch_id, wx_mch_serial_no) | |||
| if err != nil { | |||
| log.Println("create pay client error:", err) | |||
| return false, nil, errors.New("构造失败") | |||
| } | |||
| url := "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + order_sn + "?mchid=" + wx_mch_id | |||
| ret, err := client.Get(ctx, url) | |||
| body, err := ioutil.ReadAll(ret.Response.Body) | |||
| var resp map[string]interface{} | |||
| err = json.Unmarshal(body, &resp) | |||
| if err != nil { | |||
| log.Println("json unmarshal error:", err) | |||
| return false, nil, err | |||
| } else { | |||
| if _, exists := resp["trade_state"]; exists { | |||
| if helper.ToStr(resp["trade_state"]) == "SUCCESS" { | |||
| return true, resp, nil | |||
| } | |||
| } | |||
| } | |||
| return false, nil, nil | |||
| } | |||
| /** | |||
| * 构造支付请求 | |||
| * privateKey 商户私钥 | |||
| * wechatPayCertificate 平台证书 | |||
| * mchID 商户号 | |||
| * mchSerialNo 支付密钥 | |||
| */ | |||
| func CreatePayClient(ctx context.Context, wechatPayCertificate *x509.Certificate, privateKey *rsa.PrivateKey, mchID, mchSerialNo string) (*core.Client, error) { | |||
| var client *core.Client | |||
| var err error | |||
| if wechatPayCertificate != nil { | |||
| client, err = core.NewClient( | |||
| ctx, option.WithMerchantCredential(mchID, mchSerialNo, privateKey), | |||
| option.WithWechatPayCertificate([]*x509.Certificate{wechatPayCertificate}), | |||
| ) | |||
| if err != nil { | |||
| log.Println("创建 Client 失败:", err) | |||
| return nil, err | |||
| } | |||
| } else { | |||
| client, err = core.NewClient( | |||
| ctx, option.WithMerchantCredential(mchID, mchSerialNo, privateKey), option.WithoutValidator(), | |||
| ) | |||
| if err != nil { | |||
| log.Println("创建 Client 失败:", err) | |||
| return nil, err | |||
| } | |||
| } | |||
| return client, nil | |||
| } | |||
| @ -0,0 +1,126 @@ | |||
| package wechat | |||
| import ( | |||
| "context" | |||
| "crypto/rsa" | |||
| "crypto/x509" | |||
| "errors" | |||
| "git.tetele.net/tgo/helper" | |||
| "github.com/wechatpay-apiv3/wechatpay-go/core" | |||
| "github.com/wechatpay-apiv3/wechatpay-go/core/option" | |||
| "github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic" | |||
| "github.com/wechatpay-apiv3/wechatpay-go/utils" | |||
| "io/ioutil" | |||
| "log" | |||
| ) | |||
| /** | |||
| * 商户订单号,退款单号,总金额,退款金额,微信商户id,微信商户平台API私钥,平台证书,商户平台证书序列号,商品名,商品id,退款原因,回调地址 | |||
| */ | |||
| func CreateRefund(order_sn, out_refund_no, total_amount, refund_amount, wx_mp_mch_id, wx_mch_apiclient_key, | |||
| wx_pay_cert, mch_serial_no, title, uuid, reason, notify_url string) (map[string]interface{}, error) { | |||
| total := helper.ToInt64(helper.FloatMul(total_amount, 100)) //总金额 | |||
| refund := helper.ToInt64(helper.FloatMul(refund_amount, 100)) //退款金额 | |||
| log.Println("refund", order_sn, out_refund_no, refund, total, notify_url) | |||
| var ( | |||
| ctx context.Context = context.Background() | |||
| client *core.Client | |||
| ) | |||
| privateKey, err := utils.LoadPrivateKey(wx_mch_apiclient_key) | |||
| if err != nil { | |||
| log.Println("商户私钥有误:", err) | |||
| return map[string]interface{}{}, errors.New("商户私钥有误") | |||
| } | |||
| wechatPayCertificate, err := utils.LoadCertificate(wx_pay_cert) | |||
| if err != nil { | |||
| log.Println("平台证书有误:", err) | |||
| return map[string]interface{}{}, errors.New("商户平台证书有误") | |||
| } | |||
| client, err = CreateClient(ctx, wechatPayCertificate, privateKey, wx_mp_mch_id, mch_serial_no) | |||
| if err != nil { | |||
| return map[string]interface{}{}, errors.New("请检查支付参数配置") | |||
| } | |||
| // now := time.Now() | |||
| svc := refunddomestic.RefundsApiService{Client: client} | |||
| _, result, err := svc.Create(ctx, | |||
| refunddomestic.CreateRequest{ | |||
| Amount: &refunddomestic.AmountReq{ | |||
| Currency: core.String("CNY"), | |||
| // From: []refunddomestic.FundsFromItem{refunddomestic.FundsFromItem{ | |||
| // Account: refunddomestic.ACCOUNT_AVAILABLE.Ptr(), | |||
| // Amount: core.Int64(444), | |||
| // }}, | |||
| Refund: core.Int64(refund), | |||
| Total: core.Int64(total), | |||
| }, | |||
| // FundsAccount: refunddomestic.REQFUNDSACCOUNT_AVAILABLE.Ptr(), | |||
| GoodsDetail: []refunddomestic.GoodsDetail{refunddomestic.GoodsDetail{ | |||
| GoodsName: core.String(title), | |||
| MerchantGoodsId: core.String(uuid), | |||
| RefundAmount: core.Int64(refund), | |||
| RefundQuantity: core.Int64(1), | |||
| UnitPrice: core.Int64(refund), | |||
| // WechatpayGoodsId: core.String("1001"), | |||
| }}, | |||
| NotifyUrl: core.String(notify_url), | |||
| OutRefundNo: core.String(out_refund_no), | |||
| OutTradeNo: core.String(order_sn), | |||
| Reason: core.String(reason), | |||
| // SubMchid: core.String("1900000109"), | |||
| // TransactionId: core.String("1217752501201407033233368018"), | |||
| }, | |||
| ) | |||
| res := map[string]interface{}{} | |||
| log.Println("Response:", result.Response) | |||
| log.Println("Response Body:", result.Response.Body) | |||
| if err != nil { | |||
| response_body := map[string]interface{}{} | |||
| body, _ := ioutil.ReadAll(result.Response.Body) | |||
| err_body := json.Unmarshal(body, &response_body) | |||
| if err_body != nil { | |||
| res["code"] = "RESPONSE_BODY_ERROR" | |||
| res["message"] = "退款回调参数异常" | |||
| return res, err_body | |||
| } | |||
| log.Println("response_body:", response_body) | |||
| res["code"] = response_body["code"] | |||
| res["message"] = response_body["message"] | |||
| return res, errors.New(helper.ToStr(response_body["message"])) | |||
| } | |||
| res["code"] = "success" | |||
| res["message"] = "退款成功" | |||
| return res, nil | |||
| } | |||
| func CreateClient(ctx context.Context, wechatPayCertificate *x509.Certificate, privateKey *rsa.PrivateKey, mchID, mchSerialNo string) (*core.Client, error) { | |||
| var client *core.Client | |||
| var err error | |||
| if wechatPayCertificate != nil { | |||
| client, err = core.NewClient( | |||
| ctx, option.WithMerchantCredential(mchID, mchSerialNo, privateKey), | |||
| option.WithWechatPayCertificate([]*x509.Certificate{wechatPayCertificate}), | |||
| ) | |||
| if err != nil { | |||
| log.Println("创建 Client 失败:", err) | |||
| return nil, err | |||
| } | |||
| } else { | |||
| client, err = core.NewClient( | |||
| ctx, option.WithMerchantCredential(mchID, mchSerialNo, privateKey), option.WithoutValidator(), | |||
| ) | |||
| if err != nil { | |||
| log.Println("创建 Client 失败:", err) | |||
| return nil, err | |||
| } | |||
| } | |||
| return client, nil | |||
| } | |||
| @ -0,0 +1,27 @@ | |||
| package wechat | |||
| import ( | |||
| "github.com/kayon/iploc" | |||
| "testing" | |||
| ) | |||
| func Test_CreateRefund(t *testing.T) { | |||
| wx_mch_apiclient_key := "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDsea/umgdNz68w\nBdISLMPg+5E5Od2EDblO0ifz60P92kyIIQgFMKnw7sawfYlQNMs7fTOKMUBFoSJH\nzUNdOXUxLTmvPg9Pnh2jBBDQkUqBCMvUSsk5WzqF2VTSAmESy2XnfbGrXz9Q4KhV\nsLwP9O0qFDCrxgqjhKStUew6wNBq03lPnyvOee7QQ3eBZYY2z98XXjHai4nH7Lwi\nzwfYN23P/1QjWcs7a+J53v9KXjlEofNQzoXKy0hJnkPySTF8J9V7vBkbNwRceDVD\ng/uCsZ+ZBYEqwVtu7TkBaFZS06/y8/9iGNyvVLTBOzyX5XTxMAjSctRKuHEkzTII\n4ikNGtxbAgMBAAECggEAG8ez3lDj+MUkLH0Sj6YIoN6M0e81UbUXBuZzYRlLVIAX\nq4pN2eY8oJw8ZTFqNyzsjyGHM33Tshpa35qChmoglVVdyHN+8vh9PNBWdVoMKZpx\n74+VkhJgjqbYaw9yffW9E/PVMm+vDX5G6EIp5an9TosuqI9MYqdLfTz5hnFuSo4x\ngZvJmQZ/NytYjUDUOKejHjMFcfb9oBJpMDD+cOpqbK8DI9t+a4WWFxgX25TsgtzY\n9Dwnx/H4gXoDBU5nayZ5aQ1EXwNcH6s+XbAYQUpneWyjdsmg5mCuIRf/DaC0amAx\ncyGU74YJA1/eapD+TJsrNmboMdXFBUVsdwGrlF1IMQKBgQD/bUiCdYKKMC5DKidB\nrT6YqJC3lDoSbAQw/vhOy6xP1zegJdX3irlFweKCEzhRTlStV7C/vBe7eylz303Q\nUS6T59piEtKLlSJNxiPTWH0Gr+KGdJJgXa/fZPL0OEEDE7ScDrbGtAPrKLCM+Nx1\nXHujy/g2i1hbKuD82wWFidcfwwKBgQDtAYSslD/43qSGngWom2UPCr7b5nlhk9Xh\nihztlrTV+txCE5KUx1dq2kuYU2sywDlDJN7+v0XW+YhQi7PVGI5eWwh4voR87x/4\nSKhHxhkKPG1EFSU/oX8rmWUvYArgYBNFBfC7EdxbjfBGZn6lJrpRwQN1MJivGJlB\nTS4NFXDfiQKBgQD+Iwd/9dcaEvIUlX5VlD+xuBW08eANsDdDYIxCKdeZ4LJRvxNy\npi3MwCnT7bec/ei2R4duXYDnYpVN2qIbYIqNUqQv7DZb9tgYtcmzYFmVK66gxJ9X\nDiAMju5EGyxkGVkb5G/qp/U2JlfzCzCd/JRv1mNyMZaJrtIIAwstYIYP7QKBgD2p\npnYsQBSlAJNRq9E5VOq79fpKlYwQpZX/lBUwn64IjaPXacyxrInNb9ua4Iocvwk8\nH5SR+T68P34NSuWHdQsRjjuz/c5vfJbheH7sCHnETP1orv9GKksSpuPTjiDgfkL4\nD7DsLbPYUwpOAmc/kzxQBnFICoGL2dvGk9Otn2dZAoGBAOsn4AKGN/8PcFAJ2CgA\nZiqlninYOqWZl6dYdLRnhZ8sFF9MlOFPH+0OnRgNWihuF1Mbjjs1vuIP/2bgWAEw\nBr5hb+CyVD17FKICcb9Aa+hZarppRwuPkdNNcrOAOtCcHBV1k3Syp853OyUUqO8a\nxM/YXk8qkTIx0EdnYgemHeWh\n-----END PRIVATE KEY-----" | |||
| wx_pay_cert := "-----BEGIN CERTIFICATE-----\nMIID3DCCAsSgAwIBAgIUWlZytNdS/jkalg7CE2RiKZk0qa8wDQYJKoZIhvcNAQEL\nBQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT\nFFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg\nQ0EwHhcNMjAwODA0MDgwMzI0WhcNMjUwODAzMDgwMzI0WjBuMRgwFgYDVQQDDA9U\nZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRl\nbnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGDAJDTjERMA8GA1UEBwwIU2hlblpo\nZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDU68UowJdEQlwqB1ID\nTpnfmsn6hR9xeTOTMt4db6tuAtExA5yNRU9T8ilBeEPIqjAPxHq/V6mDxwhw7gur\nSbqS80YhaHqqwFTu4lfktlHa3/xi3Itty9+70e75evlem+OY2OQEjpWtKnKjq7Op\nMrHpihkpxXbYxvTQe8yR99cSm3Pa9SR5FXL3rqs3f/M1LBw5faUBG/7ak+LZvqmD\nokdJVXk5VJg3TPtt/QOAwC4QeDCFt1uzPqs8LJWuLbQczHd0hzUvDye+9tIQ5eDM\nDoyME0YTZfspnn4WD3fQSCnf2oL8t2E1Che9w0lydXpNkYEgMgTLuX344jHSn6DY\nDNLVAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DBlBgNVHR8EXjBc\nMFqgWKBWhlRodHRwOi8vZXZjYS5pdHJ1cy5jb20uY24vcHVibGljL2l0cnVzY3Js\nP0NBPTFCRDQyMjBFNTBEQkMwNEIwNkFEMzk3NTQ5ODQ2QzAxQzNFOEVCRDIwDQYJ\nKoZIhvcNAQELBQADggEBAClIek1KQAFhAIoP1UKXb/zFOGdWVuBD4vg/3aT33MlW\nsXmCH+oB6QyuY0tqIaQ5XfnvUgFFuTk+kMQuq2Tayj6XNozu1pteVtQ//3u2V9DF\nGXNxiT588EeN1od9qT+zhLH6nuptw++mlxBorQQ3BJvgX3NIOgFU/ajCiSkGAnpj\nD4QcHWzsM6kwpK6JUuPDRq3E7O/1cCJkt6a20JQj+fbYJ0By28PgdvaphuK8lW3e\nuSlkSNLP/a1F7sp5xHMW4a/E+/OF9D4ETHfoQhVYwOTHlxpnXPLApQtj9zcwr64/\nU9DP6BALDin+8uBDDkxsF29VyCYZ/l6LdsnYD+ihbSo=\n-----END CERTIFICATE-----" | |||
| ret, err := CreateRefund("6006167143914016978", "111223", "0.01", "0.01", "1298704001", wx_mch_apiclient_key, wx_pay_cert, "4642B326DDDDEAC74C677C163FB82BC95DCE43C0", "中餐", "122331", "yuanyin", "https://ttl-mini.tetele.net/admin/user/user/ttt_refund") | |||
| t.Log(ret) | |||
| t.Log(err) | |||
| } | |||
| func Test_Qqwry(t *testing.T) { | |||
| loc, err := iploc.Open("qqwry.dat") | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| detail := loc.Find("219.131.197.178") | |||
| t.Log(detail.String()) | |||
| t.Log(detail.Country, detail.Province, detail.City, detail.County) | |||
| } | |||
| @ -0,0 +1,50 @@ | |||
| package wechat | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| ) | |||
| type MpUserInfo struct { | |||
| Subscribe int `json:"subscribe"` | |||
| Openid string `json:"openid"` | |||
| Language string `json:"language"` | |||
| SubscribeTime int `json:"subscribe_time"` | |||
| Unionid string `json:"unionid"` | |||
| Remark string `json:"remark"` | |||
| Groupid int `json:"groupid"` | |||
| TagidList interface{} `json:"tagid_list"` | |||
| SubscribeScene string `json:"subscribe_scene"` | |||
| QrScene int `json:"qr_scene"` | |||
| QrSceneStr string `json:"qr_scene_str"` | |||
| Errcode int `json:"errcode"` | |||
| Errmsg string `json:"errmsg"` | |||
| } | |||
| /** | |||
| * 公众号获取用户基本信息(UnionID机制) | |||
| */ | |||
| func GetMpUserInfo(access_token, openid string, lang ...string) (MpUserInfo, error) { | |||
| var data MpUserInfo | |||
| var language string = "zh_CN" | |||
| if len(lang) > 0 { | |||
| language = lang[0] | |||
| } | |||
| url := fmt.Sprintf(MP_USERINFO_API, access_token, openid, language) | |||
| data_byte, err := SendHttp("GET", url, nil) | |||
| if err != nil { | |||
| return data, err | |||
| } | |||
| err = json.Unmarshal(data_byte, &data) | |||
| if data.Errcode != 0 { | |||
| return data, errors.New(data.Errmsg) | |||
| } | |||
| return data, err | |||
| } | |||
| @ -0,0 +1,13 @@ | |||
| package wechat | |||
| import ( | |||
| "testing" | |||
| ) | |||
| func Test_GetMpUserInfo(t *testing.T) { | |||
| access_token := "56_V9X_7MOCa4iAxPTycxgtjftFrt_68u_T64EqSBmU-qTCF1wEG9vsfxv-4UbnEsFoTHOdwTGPkt0TCGw8fB6g9CbvipiiddE0iStdHdD_F4fnYbMqQqJ8cP9CmvM73KQJphXExnUk8Li4bmASBAGaAFAJIO" | |||
| openid := "ocUSI5LIiU6mDPt8lFEaP9Y8mIcQ" | |||
| ret, err := GetMpUserInfo(access_token, openid) | |||
| t.Log(ret) | |||
| t.Log(err) | |||
| } | |||