pay.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. package shanghu
  2. import (
  3. "duoduo/apis/common"
  4. "duoduo/apis/shanghu/models"
  5. "duoduo/models/shanghu"
  6. "duoduo/tools"
  7. "duoduo/tools/app"
  8. "encoding/xml"
  9. "errors"
  10. "fmt"
  11. "github.com/gin-gonic/gin"
  12. "github.com/go-pay/gopay"
  13. "github.com/go-pay/gopay/wechat"
  14. wechatV3 "github.com/go-pay/gopay/wechat/v3"
  15. "github.com/shopspring/decimal"
  16. "net/http"
  17. "reflect"
  18. "strconv"
  19. "time"
  20. )
  21. type wechatCallbackResp struct {
  22. XMLName xml.Name `xml:"xml"`
  23. ReturnCode Cdata `xml:"return_code"`
  24. ReturnMsg Cdata `xml:"return_msg"`
  25. }
  26. type Cdata struct {
  27. Value string `xml:",cdata"`
  28. }
  29. var GlobalCashOutMap = make(map[string]int)
  30. var (
  31. successResp = &wechatCallbackResp{ReturnCode: Cdata{Value: "SUCCESS"}, ReturnMsg: Cdata{Value: "OK"}}
  32. failResp = &wechatCallbackResp{ReturnCode: Cdata{Value: "FAIL"}, ReturnMsg: Cdata{Value: "数据处理异常"}}
  33. )
  34. func UnifiedOrder(c *gin.Context) {
  35. var inData models.UnifiedOrderRequest
  36. var sqlData shanghu.ClientPayTrans
  37. var outData models.UnifiedOrderReply
  38. err := c.ShouldBindJSON(&inData)
  39. if err != nil {
  40. app.Error(c, 400, err, err.Error())
  41. return
  42. }
  43. //校验 防止同一笔记录存在
  44. sqlData.RequestID = inData.RequestId
  45. if sqlData.GetRequestNum() > 0 {
  46. app.Error(c, 400, errors.New("交易已存在"), "交易已存在")
  47. return
  48. }
  49. //校验金额
  50. // 检查微信相关参数
  51. if !inData.Amount.GreaterThan(decimal.NewFromInt(0)) {
  52. app.Error(c, 400, errors.New("amount:金额必须大于0"), "amount:金额必须大于0")
  53. return
  54. }
  55. if inData.Amount.Round(2).String() != inData.Amount.String() {
  56. app.Error(c, 400, errors.New("total_fee:金额最多只能保留两位小数"), "total_fee:金额最多只能保留两位小数")
  57. return
  58. }
  59. //创建支付记录
  60. sqlData.ClientOpenID = inData.ClientOpenId
  61. sqlData.RequestID = inData.RequestId
  62. sqlData.CreatedAt = time.Now()
  63. sqlData.UpdatedAt = time.Now()
  64. sqlData.Amount = inData.Amount
  65. sqlData.OutTradeNo = strconv.FormatInt(inData.MerchantCardId, 10) + strconv.FormatInt(time.Now().UnixNano(), 10)
  66. sqlData.Status = 1 //未支付
  67. sqlData.MerchantCardID = inData.MerchantCardId
  68. sqlData.InvitationCode = inData.InvitationCode
  69. _, err = sqlData.Create()
  70. if err != nil {
  71. app.Error(c, 400, err, "创建支付失败")
  72. return
  73. }
  74. fmt.Println(sqlData.OutTradeNo)
  75. bm := make(gopay.BodyMap)
  76. bm.Set("nonce_str", common.GetRandomString(32))
  77. bm.Set("body", "商户卡")
  78. bm.Set("out_trade_no", sqlData.OutTradeNo)
  79. bm.Set("total_fee", inData.Amount.Mul(decimal.NewFromInt(100)).IntPart())
  80. bm.Set("spbill_create_ip", "127.0.0.1")
  81. bm.Set("notify_url", "https://tao1024.com/v1/client/pay/callback")
  82. bm.Set("device_info", "WEB")
  83. bm.Set("trade_type", "JSAPI")
  84. bm.Set("sign_type", wechat.SignType_MD5)
  85. bm.Set("openid", inData.ClientOpenId)
  86. client := NewWechatService()
  87. //请求支付下单,成功后得到结果
  88. wxResp, err := client.UnifiedOrder(c, bm)
  89. if err != nil {
  90. app.Error(c, 400, err, "下单失败")
  91. return
  92. }
  93. if wxResp.ReturnCode != "SUCCESS" {
  94. app.Error(c, 400, errors.New(wxResp.ReturnMsg), "下单失败")
  95. return
  96. }
  97. if wxResp.ResultCode != "SUCCESS" {
  98. app.Error(c, 400, errors.New(wxResp.ErrCode+"--"+wxResp.ErrCodeDes), "下单失败")
  99. return
  100. }
  101. timestamp := strconv.FormatInt(time.Now().Unix(), 10)
  102. pac := "prepay_id=" + wxResp.PrepayId
  103. paySign := wechat.GetMiniPaySign("wx8595c589dd736486", wxResp.NonceStr, pac, wechat.SignType_MD5, timestamp, "33c424fAa69942086f82A003e283E9C8")
  104. outData.Timestamp = timestamp
  105. outData.NonceStr = wxResp.NonceStr
  106. outData.Package = pac
  107. outData.PaySign = paySign
  108. outData.SignType = wechat.SignType_MD5
  109. //merchant, count, err := sqlData.GetOpenIdList(pageSize, pageIndex)
  110. //if err != nil {
  111. // app.Error(c, 500, err, err.Error())
  112. // return
  113. //}
  114. app.OK(c, outData, app.Success)
  115. }
  116. func PayCashOut(c *gin.Context) {
  117. var inData models.PayCashOutRequest
  118. var merchantAccountSql shanghu.MerchantAccount
  119. var clientAccountSql shanghu.MerchantClientAccount
  120. var cashOut shanghu.CashOut
  121. var trans []models.TransferDetailList
  122. var transDetail models.TransferDetailList
  123. err := c.ShouldBindJSON(&inData)
  124. if err != nil {
  125. app.Error(c, 400, err, err.Error())
  126. return
  127. }
  128. _, ok := GlobalCashOutMap[inData.OpenId]
  129. if ok {
  130. app.OK(c, nil, "正在提现中...")
  131. return
  132. }
  133. fmt.Println("tixan")
  134. time.Sleep(100 * time.Second)
  135. GlobalCashOutMap[inData.OpenId] = 1
  136. defer func() {
  137. delete(GlobalCashOutMap, inData.OpenId)
  138. }()
  139. if inData.Appid == "" { //appid 不能为空
  140. app.Error(c, 400, errors.New("Appid不能为空"), "Appid不能为空")
  141. return
  142. }
  143. if inData.Amount.Cmp(decimal.NewFromInt(500)) > 0 {
  144. app.Error(c, 400, errors.New("单笔金额不能大于500"), "单笔金额不能大于500")
  145. return
  146. }
  147. if inData.Amount.Cmp(decimal.NewFromInt(1)) < 0 {
  148. app.Error(c, 400, errors.New("单笔金额不能小于1"), "单笔金额不能大于1")
  149. return
  150. }
  151. //
  152. cashOut.OpenID = inData.OpenId
  153. cashOut.AppID = inData.Appid
  154. cashOut.Status = 1 //提现中
  155. status := []int{1, 3} //提现中、金额待扣减
  156. cashNum := cashOut.GetCashOutByStatusNum(status)
  157. if cashNum > 0 {
  158. app.Error(c, 400, errors.New("有一笔交易正在提现中"), "有一笔交易正在提现中")
  159. return
  160. }
  161. //校验金额是否够
  162. if inData.AccountType == "client" {
  163. clientAccountSql.ClientOpenID = inData.OpenId
  164. clientAccountInfo, err := clientAccountSql.GetClientAccount()
  165. if err != nil {
  166. app.Error(c, 400, err, err.Error())
  167. return
  168. }
  169. if inData.Amount.Cmp(clientAccountInfo.Amount) > 0 {
  170. app.Error(c, 400, errors.New("账号余额不够"), "账号余额不够")
  171. return
  172. }
  173. var client shanghu.MerchantClientUser
  174. client.Code = "7jb6"
  175. clientInfo, _ := client.GetUserInfoByCode()
  176. if clientInfo.ClientOpenID == inData.OpenId {
  177. cashOut.Fee = decimal.NewFromInt(0)
  178. } else {
  179. cashOut.Fee = inData.Amount.Mul(decimal.NewFromFloat32(0.05))
  180. }
  181. } else if inData.AccountType == "merchant" {
  182. merchantAccountSql.MerchantOpenID = inData.OpenId
  183. merchantAccountInfo, err := merchantAccountSql.GetMerchantAccount()
  184. if err != nil {
  185. app.Error(c, 400, err, err.Error())
  186. return
  187. }
  188. if inData.Amount.Cmp(merchantAccountInfo.Amount) > 0 {
  189. app.Error(c, 400, errors.New("账号余额不够"), "账号余额不够")
  190. return
  191. }
  192. cashOut.Fee = decimal.NewFromInt(0)
  193. } else {
  194. app.Error(c, 400, errors.New("账户类型错误"), "账户类型错误")
  195. return
  196. }
  197. clientV3, err := NewWechatServiceV3(inData.Appid)
  198. if err != nil {
  199. app.Error(c, 400, err, err.Error())
  200. return
  201. }
  202. userName, err := clientV3.V3EncryptText(inData.UserName)
  203. if err != nil {
  204. app.Error(c, 400, err, err.Error())
  205. return
  206. }
  207. transDetail.OutDetailNo = common.GetRandomString(32)
  208. transDetail.TransferAmount = inData.Amount.Sub(cashOut.Fee).Mul(decimal.NewFromInt(100)).IntPart()
  209. transDetail.UserName = userName
  210. transDetail.Openid = inData.OpenId
  211. transDetail.TransferRemark = "提现"
  212. trans = append(trans, transDetail)
  213. partnerTradeNo := common.GetRandomString(32)
  214. var cashOutCreate shanghu.CashOut
  215. //创建提现记录
  216. cashOutCreate.AppID = inData.Appid
  217. cashOutCreate.Status = 1 //提现中
  218. cashOutCreate.OpenID = inData.OpenId
  219. cashOutCreate.Amount = inData.Amount
  220. cashOutCreate.CreatedAt = time.Now()
  221. cashOutCreate.UpdatedAt = time.Now()
  222. cashOutCreate.PartnerTradeNo = partnerTradeNo
  223. cashOutCreate.Fee = cashOut.Fee
  224. cashOutInfo, err := cashOutCreate.Create()
  225. if err != nil {
  226. app.Error(c, 400, err, err.Error())
  227. return
  228. }
  229. var bMap []gopay.BodyMap
  230. bm := make(gopay.BodyMap)
  231. bm.Set("appid", inData.Appid)
  232. bm.Set("out_batch_no", partnerTradeNo)
  233. bm.Set("batch_name", "提现")
  234. bm.Set("batch_remark", "提现")
  235. bm.Set("total_amount", inData.Amount.Sub(cashOut.Fee).Mul(decimal.NewFromInt(100)).IntPart())
  236. bm.Set("total_num", 1)
  237. bMap = append(bMap, structToMap(&transDetail))
  238. bm.Set("transfer_detail_list", bMap)
  239. bm.Set("transfer_scene_id", "1001")
  240. fmt.Println(bm.JsonBody())
  241. reply, err := clientV3.V3Transfer(c, bm)
  242. if err != nil {
  243. cashOut.ID = cashOutInfo.ID
  244. cashOut.FailRes = err.Error()
  245. cashOut.Status = 2 //提现失败
  246. cashOut.UpdateMerchantStatus()
  247. app.Error(c, 500, err, err.Error())
  248. return
  249. }
  250. if reply.Code != 0 {
  251. cashOut.ID = cashOutInfo.ID
  252. cashOut.FailRes = reply.Error
  253. cashOut.Status = 2 //提现失败
  254. cashOut.UpdateMerchantStatus()
  255. app.Error(c, 500, errors.New(reply.Error), reply.Error)
  256. return
  257. }
  258. cashOut.WxPartnerTradeNo = reply.Response.BatchId
  259. cashOut.PartnerTradeNo = reply.Response.OutBatchNo
  260. cashOut.UpdateCashOutWxBachNo()
  261. app.OK(c, nil, app.Success)
  262. }
  263. func PayCallBack(c *gin.Context) {
  264. var payLog shanghu.PayCallbackLog
  265. var payTrans shanghu.ClientPayTrans
  266. wxNotify, err := wechat.ParseNotifyToBodyMap(c.Request)
  267. if err != nil {
  268. c.XML(http.StatusOK, failResp)
  269. return
  270. }
  271. //通知回调log
  272. payLog.CallBackLog = wxNotify.JsonBody()
  273. payLog.ThirdTradeNo = wxNotify.Get("transaction_id")
  274. payLog.OutTradeNo = wxNotify.Get("out_trade_no")
  275. payLog.CreatedAt = time.Now()
  276. payLog.UpdatedAt = time.Now()
  277. _, err = payLog.Create()
  278. if err != nil {
  279. c.XML(http.StatusOK, failResp)
  280. return
  281. }
  282. if wxNotify.Get("return_code") != "SUCCESS" || wxNotify.Get("result_code") != "SUCCESS" {
  283. payLog.ErrLog = "微信返回错误:" + wxNotify.Get("return_code") + "--" + wxNotify.Get("result_code")
  284. payLog.UpdateMerchant()
  285. c.XML(http.StatusOK, failResp)
  286. return
  287. }
  288. //校验金额
  289. payTrans.OutTradeNo = wxNotify.Get("out_trade_no")
  290. payTransInfo, err := payTrans.GetPayTransByTradeNo()
  291. if err != nil {
  292. payLog.ErrLog = "查询交易信息错误:" + " err=" + err.Error()
  293. payLog.UpdateMerchant()
  294. c.XML(http.StatusOK, failResp)
  295. return
  296. }
  297. // 判断金额与支付流水是否一致
  298. totalFee, err := decimal.NewFromString(wxNotify.Get("total_fee"))
  299. if err != nil {
  300. payLog.ErrLog = "解析总金额报错:" + "err=" + err.Error()
  301. payLog.UpdateMerchant()
  302. c.XML(http.StatusOK, failResp)
  303. return
  304. }
  305. if !totalFee.Equal(payTransInfo.Amount.Mul(decimal.NewFromInt(100))) {
  306. payLog.ErrLog = "验证金额报错:total_fee=" + wxNotify.Get("total_fee") + " amount=" + payTransInfo.Amount.String()
  307. payLog.UpdateMerchant()
  308. c.XML(http.StatusOK, failResp)
  309. return
  310. }
  311. // 解析支付时间
  312. timeEnd, err := time.ParseInLocation("20060102150405", wxNotify.Get("time_end"), tools.TimeLocation)
  313. if err != nil {
  314. payLog.ErrLog = "付款时间解析出错:err=" + err.Error()
  315. payLog.UpdateMerchant()
  316. c.XML(http.StatusOK, failResp)
  317. return
  318. }
  319. payTrans.ThirdTradeNo = wxNotify.Get("transaction_id")
  320. payTrans.Status = 2 //支付成功
  321. payTrans.PayTime = timeEnd
  322. payTrans.AccountStatus = 1 //未分账
  323. err = payTrans.UpdatePayTransByTradeNo()
  324. if err != nil {
  325. payLog.ErrLog = "更新支付状态失败:err=" + err.Error()
  326. payLog.UpdateMerchant()
  327. c.XML(http.StatusOK, failResp)
  328. return
  329. }
  330. c.XML(http.StatusOK, successResp)
  331. }
  332. func NewWechatService() *wechat.Client {
  333. client := wechat.NewClient("wx8595c589dd736486", "1670841410", "33c424fAa69942086f82A003e283E9C8", true)
  334. //设置国家
  335. client.SetCountry(wechat.China)
  336. return client
  337. }
  338. func NewWechatServiceAppid(appid string) *wechat.Client {
  339. client := wechat.NewClient(appid, "1501641641", "1RKRJBVH4vaRrF0XPW9GX2M3ZSImukIz", true)
  340. //设置国家
  341. client.SetCountry(wechat.China)
  342. return client
  343. }
  344. func NewWechatServiceV3(appid string) (*wechatV3.ClientV3, error) {
  345. key := `-----BEGIN PRIVATE KEY-----
  346. MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7Q/aQNEHIZC6p
  347. BmUPx9dgt56vC9l1aDT0MG/PovJ2NkihIxccsEkKvxtUxBGQ5ysNRXWG22qgJE38
  348. 4NYb7nmxsAl9XNWxix5HsjAnWtdCiMp4yRxM3xeDQp0sp25zK5OU+GXPhJ7shBoB
  349. dlJd9BtIMK5URKVRU7JyUhn2+/FFPEVQ9pbL+cnpDesC5L+uQVqKyYK9hW0x7fy/
  350. RKwi8T8d37CdfIybywqlUFUzXP3BUYuR1L0dVikD6is3Aq8/+DNs68f5wLOvjdow
  351. kBTuegtDd7Km2y3Tqnf3gwHxAYW1KoSuKijV8SWRuQnfTf84nWfy9lryUMn+dZHt
  352. 2SX8LyQRAgMBAAECggEAQViM8HMbxWaYFalCmMgecwSAHgsffeW4HMHOMoFk5DU7
  353. EOeiyAMH6fUX/3NPweW40y+6vC1SvsEMacK3VBXaZ1PLa/B6LTMjhNc8EG+VkAUV
  354. yiI7euOaW9Zh7FQcqZm6LRCkzk4z+sp6HKqCQYDOCFnca1Fs8r0nFtdpchMmdQbi
  355. cl6uVj41hCVV0xWdPMb5LXTnhAuckVDW8M2oHavjdbKzL9H//WEjYqDZ76eLwvg3
  356. /89OamrXjUyd+lquSYuuG+3/HzI3a2QXeKwBysskmAwzEZrAQe9O3V2qDks7jFYP
  357. RcqGnwpZSRkEpzUiEDrWVamVHjWwOQy6J7h8jEVpxQKBgQDy980Tyhuhnp6SIxmV
  358. FnAnQHm06abq+gLQvEYwTcE/oYHVbd2cOrn/tQ5QN60JSGGaHvio1Fh1x4oM/iZ3
  359. Pw0nGZTBpwiPDnk0rJSliqOAYejua/nITpaXE5v/yu7SeBrvaP3pRJKtm2sROs79
  360. GvFiA4F0o7utqSG45yf32hLzWwKBgQDFT09RXKWHVjDFnv8o6NahSnH1Mroy9AKW
  361. WW3PlYPOtMW6eIPt9Pw2hSYyEVTc+FPRbtZploTki3Aq3FU1qVXMwhJb464pg6gW
  362. OKmLXdPXIkBQ+g6REZpseWrTlNM99jebURXx4o+S6am14LwGV98m5Yz7FfEn2uWu
  363. 9A7LUyb+AwKBgQC42CNSEenRnH5hCYkV7nsmsZumMjhF/iyUJxhy+USzJylCeZIm
  364. oFsPGeyoxKLFNCbk/PkKYHpoKIVzewtCn1pfS8vrRCtzwnwdwcpY4s+fBV4TvVvL
  365. s1ZH26hMA1SMFMnRKBw8EmpQ37Ol6Qq+bngDrE0ZstM+vDSmml3C50qRjwKBgFAV
  366. aLQ1Tlon+ZO8fQQ1vSep96b19+1GbOZpVdCzdtQnOzn8QFAM53GZiW846aDmid6v
  367. hgdFOJsqnVRIKa2mFVUOUDVLrBzdexPJ28bdRmZDWKeFVvQ6mNr+TQWjmjnD/b3k
  368. o2uR8YRHosJXfPl2IPTApwAiX8c1aZQhKwALt//RAoGBAIYu48CyWdlwpFdvqBJc
  369. KF8wna5Xm20ETxNl5hN9ffWRfIB0DWUnW+HTr3fD0KOxUuA8lIjpi9QwxaQVva6C
  370. nBV1PRxCDZ6Wk7G9XvT7rEr4cOFWsR8TEkI9IY4qbK7QXZworCrM9EhKdrt36n+q
  371. KRm8Wc217XJMOFZj7zErPmNF
  372. -----END PRIVATE KEY-----`
  373. client, err := wechatV3.NewClientV3("1670841410", "38B1AD453A0DD1025DE4DAC6C4494CD1A134EE15", "33c424fAa69942086f82A003e283E9C8", key)
  374. if err != nil {
  375. return nil, err
  376. }
  377. // 启用自动同步返回验签,并定时更新微信平台API证书(开启自动验签时,无需单独设置微信平台API证书和序列号)
  378. err = client.AutoVerifySign()
  379. if err != nil {
  380. return nil, err
  381. }
  382. // 打开Debug开关,输出日志,默认是关闭的
  383. //client.DebugSwitch = gopay.DebugOn
  384. return client, nil
  385. }
  386. func structToMap(obj interface{}) gopay.BodyMap {
  387. result := make(gopay.BodyMap)
  388. value := reflect.ValueOf(obj).Elem() // 获取指针的值
  389. typ := value.Type() // 获取类型信息
  390. for i := 0; i < typ.NumField(); i++ {
  391. field := typ.Field(i) // 获取字段信息
  392. tag := field.Tag.Get("json") // 获取标签(如果有)
  393. if tag != "" && !field.Anonymous { // 只处理非匿名字段且有标签的情况
  394. key := field.Name // 默认使用字段名作为Key
  395. if tag != "-" { // 若标签不等于-则使用标签作为Key
  396. key = tag
  397. }
  398. result[key] = value.Field(i).Interface() // 存入Map
  399. }
  400. }
  401. return result
  402. }