package exchange import ( "crypto/md5" "encoding/hex" "encoding/json" "fmt" "github.com/demdxx/gocast" "reflect" "sort" "strings" "time" ) // doc: https://dsgxajqzhgq3.sg.larksuite.com/wiki/Flk1wnbUPiSucBk47TClBnC4g7m // 获取充值入口 func GetTransferUri(customParams string, args ...string) string { uri := fmt.Sprintf("/zh-CN/application-center/transfer?appid=%s&custom_param=%s&redirect_uri=%s", conf().Appid, customParams, conf().RedirectUrl) if len(args) > 0 { uri = fmt.Sprintf("/zh-CN/application-center/transfer?appid=%s&custom_param=%s&redirect_uri=%s", conf().Appid, customParams, strings.Join(args, "")) } return uri } // /v1/act/public/application/third/transfer // /v1/act/public/application/third/transfer?accessToken={ACCESSTOKEN}&openId={OPENID} // accessToken openId 至少一个不为空,两者都不为空时,accesToken优先有效。 // 转出 type TransferOutReq struct { Currency string `json:"currency,omitempty"` // 必填 Amount string `json:"amount,omitempty"` // 必填 OrderId string `json:"orderId,omitempty"` // 必填 AppId string `json:"appId,omitempty"` Timestamp string `json:"timestamp,omitempty"` Sign string `json:"sign,omitempty"` } type TransferOutResp struct { Code int `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` } func TransferOut(openId string, req *TransferOutReq) (*TransferOutResp, error) { uri := fmt.Sprintf("/v1/act/public/application/third/transfer-apply?openId=%s", openId) url := ToPath(conf().RootUrl, uri) req.Timestamp = gocast.ToString(time.Now().UnixMilli()) req.AppId = conf().Appid reqMap, err := structToJSONMap(req) if err != nil { return nil, err } sign := signMd5(reqMap) req.Sign = sign data, _ := json.Marshal(&req) return post[TransferOutResp](url, data) } // 回调处理(充值 提现) const ( TransferUserToApp = "user_to_application" TransferAppToUser = "application_to_user" ) const ( ExchangeRwStatusRechargeSuccess = 0 ExchangeRwStatusWithdrawSuccess = 1 ExchangeRwStatusWithdrawFail = 2 ) type TransferCallbackReq struct { Currency string `json:"currency,omitempty"` Amount string `json:"amount,omitempty"` OrderId string `json:"orderId,omitempty"` OpenId string `json:"openId,omitempty"` CreateTime int64 `json:"createTime,omitempty"` Type string `json:"type,omitempty"` // user_to_application application_to_user AppId string `json:"appId,omitempty"` CustomParam string `json:"customParam"` Status int `json:"status,omitempty"` Sign string `json:"sign,omitempty"` } type CheckTransferCallbackReq struct { Currency string `json:"currency,omitempty"` Amount string `json:"amount,omitempty"` OrderId string `json:"orderId,omitempty"` OpenId string `json:"openId,omitempty"` CreateTime string `json:"createTime"` Type string `json:"type"` // user_to_application application_to_user AppId string `json:"appId,omitempty"` CustomParam string `json:"customParam"` Status int `json:"status,omitempty"` Sign string `json:"sign,omitempty"` } // 加密检查 func SignCheck(req *TransferCallbackReq) bool { sign := req.Sign checkReq := &CheckTransferCallbackReq{ Currency: req.Currency, Amount: req.Amount, OrderId: req.OrderId, OpenId: req.OpenId, CreateTime: fmt.Sprintf("%d", req.CreateTime), Type: req.Type, AppId: req.AppId, Status: req.Status, CustomParam: req.CustomParam, } reqMap, err := structToJSONMap(checkReq) if err != nil { return false } return sign == signMd5(reqMap) } func SignCheckMap(req map[string]interface{}, sign string) bool { return sign == signMd5(req) } // 将结构体转换为 map func beanToMap(req interface{}) map[string]interface{} { result := make(map[string]interface{}) value := reflect.ValueOf(req) for i := 0; i < value.NumField(); i++ { field := value.Type().Field(i) result[field.Name] = value.Field(i).Interface() } return result } // 计算MD5签名 -- 不实用 func CalculateSign(req TransferCallbackReq) string { // 实体转map hashMap := beanToMap(req) // 将HashMap转换为TreeMap(按键排序) treeMap := make(map[string]string) for key, value := range hashMap { if value != nil { treeMap[key] = fmt.Sprintf("%v", value) } } // 排序键 keys := make([]string, 0, len(treeMap)) for key := range treeMap { keys = append(keys, key) } sort.Strings(keys) // 拼接字符串 var stringBuilder strings.Builder for _, key := range keys { value := treeMap[key] if value != "" && key != "sign" { // 过滤空值和 "sign" 字段 stringBuilder.WriteString(key) stringBuilder.WriteString(value) } } // appSecret拼接 stringBuilder.WriteString(conf().Secret) // md5加密 hash := md5.Sum([]byte(stringBuilder.String())) return hex.EncodeToString(hash[:]) }