ex_transfer.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package exchange
  2. import (
  3. "crypto/md5"
  4. "encoding/hex"
  5. "encoding/json"
  6. "fmt"
  7. "github.com/demdxx/gocast"
  8. "reflect"
  9. "sort"
  10. "strings"
  11. "time"
  12. )
  13. // doc: https://dsgxajqzhgq3.sg.larksuite.com/wiki/Flk1wnbUPiSucBk47TClBnC4g7m
  14. // 获取充值入口
  15. func GetTransferUri(customParams string, args ...string) string {
  16. uri := fmt.Sprintf("/zh-CN/application-center/transfer?appid=%s&custom_param=%s&redirect_uri=%s",
  17. conf().Appid, customParams, conf().RedirectUrl)
  18. if len(args) > 0 {
  19. uri = fmt.Sprintf("/zh-CN/application-center/transfer?appid=%s&custom_param=%s&redirect_uri=%s",
  20. conf().Appid, customParams, strings.Join(args, ""))
  21. }
  22. return uri
  23. }
  24. // /v1/act/public/application/third/transfer
  25. // /v1/act/public/application/third/transfer?accessToken={ACCESSTOKEN}&openId={OPENID}
  26. // accessToken openId 至少一个不为空,两者都不为空时,accesToken优先有效。
  27. // 转出
  28. type TransferOutReq struct {
  29. Currency string `json:"currency,omitempty"` // 必填
  30. Amount string `json:"amount,omitempty"` // 必填
  31. OrderId string `json:"orderId,omitempty"` // 必填
  32. AppId string `json:"appId,omitempty"`
  33. Timestamp string `json:"timestamp,omitempty"`
  34. Sign string `json:"sign,omitempty"`
  35. }
  36. type TransferOutResp struct {
  37. Code int `json:"code"`
  38. Msg string `json:"msg"`
  39. Data interface{} `json:"data"`
  40. }
  41. func TransferOut(openId string, req *TransferOutReq) (*TransferOutResp, error) {
  42. uri := fmt.Sprintf("/v1/act/public/application/third/transfer-apply?openId=%s",
  43. openId)
  44. url := ToPath(conf().RootUrl, uri)
  45. req.Timestamp = gocast.ToString(time.Now().UnixMilli())
  46. req.AppId = conf().Appid
  47. reqMap, err := structToJSONMap(req)
  48. if err != nil {
  49. return nil, err
  50. }
  51. sign := signMd5(reqMap)
  52. req.Sign = sign
  53. data, _ := json.Marshal(&req)
  54. return post[TransferOutResp](url, data)
  55. }
  56. // 回调处理(充值 提现)
  57. const (
  58. TransferUserToApp = "user_to_application"
  59. TransferAppToUser = "application_to_user"
  60. )
  61. const (
  62. ExchangeRwStatusRechargeSuccess = 0
  63. ExchangeRwStatusWithdrawSuccess = 1
  64. ExchangeRwStatusWithdrawFail = 2
  65. )
  66. type TransferCallbackReq struct {
  67. Currency string `json:"currency,omitempty"`
  68. Amount string `json:"amount,omitempty"`
  69. OrderId string `json:"orderId,omitempty"`
  70. OpenId string `json:"openId,omitempty"`
  71. CreateTime int64 `json:"createTime,omitempty"`
  72. Type string `json:"type,omitempty"` // user_to_application application_to_user
  73. AppId string `json:"appId,omitempty"`
  74. CustomParam string `json:"customParam"`
  75. Status int `json:"status,omitempty"`
  76. Sign string `json:"sign,omitempty"`
  77. }
  78. type CheckTransferCallbackReq struct {
  79. Currency string `json:"currency,omitempty"`
  80. Amount string `json:"amount,omitempty"`
  81. OrderId string `json:"orderId,omitempty"`
  82. OpenId string `json:"openId,omitempty"`
  83. CreateTime string `json:"createTime"`
  84. Type string `json:"type"` // user_to_application application_to_user
  85. AppId string `json:"appId,omitempty"`
  86. CustomParam string `json:"customParam"`
  87. Status int `json:"status,omitempty"`
  88. Sign string `json:"sign,omitempty"`
  89. }
  90. // 加密检查
  91. func SignCheck(req *TransferCallbackReq) bool {
  92. sign := req.Sign
  93. checkReq := &CheckTransferCallbackReq{
  94. Currency: req.Currency,
  95. Amount: req.Amount,
  96. OrderId: req.OrderId,
  97. OpenId: req.OpenId,
  98. CreateTime: fmt.Sprintf("%d", req.CreateTime),
  99. Type: req.Type,
  100. AppId: req.AppId,
  101. Status: req.Status,
  102. CustomParam: req.CustomParam,
  103. }
  104. reqMap, err := structToJSONMap(checkReq)
  105. if err != nil {
  106. return false
  107. }
  108. return sign == signMd5(reqMap)
  109. }
  110. func SignCheckMap(req map[string]interface{}, sign string) bool {
  111. return sign == signMd5(req)
  112. }
  113. // 将结构体转换为 map
  114. func beanToMap(req interface{}) map[string]interface{} {
  115. result := make(map[string]interface{})
  116. value := reflect.ValueOf(req)
  117. for i := 0; i < value.NumField(); i++ {
  118. field := value.Type().Field(i)
  119. result[field.Name] = value.Field(i).Interface()
  120. }
  121. return result
  122. }
  123. // 计算MD5签名 -- 不实用
  124. func CalculateSign(req TransferCallbackReq) string {
  125. // 实体转map
  126. hashMap := beanToMap(req)
  127. // 将HashMap转换为TreeMap(按键排序)
  128. treeMap := make(map[string]string)
  129. for key, value := range hashMap {
  130. if value != nil {
  131. treeMap[key] = fmt.Sprintf("%v", value)
  132. }
  133. }
  134. // 排序键
  135. keys := make([]string, 0, len(treeMap))
  136. for key := range treeMap {
  137. keys = append(keys, key)
  138. }
  139. sort.Strings(keys)
  140. // 拼接字符串
  141. var stringBuilder strings.Builder
  142. for _, key := range keys {
  143. value := treeMap[key]
  144. if value != "" && key != "sign" { // 过滤空值和 "sign" 字段
  145. stringBuilder.WriteString(key)
  146. stringBuilder.WriteString(value)
  147. }
  148. }
  149. // appSecret拼接
  150. stringBuilder.WriteString(conf().Secret)
  151. // md5加密
  152. hash := md5.Sum([]byte(stringBuilder.String()))
  153. return hex.EncodeToString(hash[:])
  154. }