pay.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  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 (
  30. successResp = &wechatCallbackResp{ReturnCode: Cdata{Value: "SUCCESS"}, ReturnMsg: Cdata{Value: "OK"}}
  31. failResp = &wechatCallbackResp{ReturnCode: Cdata{Value: "FAIL"}, ReturnMsg: Cdata{Value: "数据处理异常"}}
  32. )
  33. func UnifiedOrder(c *gin.Context) {
  34. var inData models.UnifiedOrderRequest
  35. var sqlData shanghu.ClientPayTrans
  36. var outData models.UnifiedOrderReply
  37. err := c.ShouldBindJSON(&inData)
  38. if err != nil {
  39. app.Error(c, 400, err, err.Error())
  40. return
  41. }
  42. //校验 防止同一笔记录存在
  43. sqlData.RequestID = inData.RequestId
  44. if sqlData.GetRequestNum() > 0 {
  45. app.Error(c, 400, errors.New("交易已存在"), "交易已存在")
  46. return
  47. }
  48. //校验金额
  49. // 检查微信相关参数
  50. if !inData.Amount.GreaterThan(decimal.NewFromInt(0)) {
  51. app.Error(c, 400, errors.New("amount:金额必须大于0"), "amount:金额必须大于0")
  52. return
  53. }
  54. if inData.Amount.Round(2).String() != inData.Amount.String() {
  55. app.Error(c, 400, errors.New("total_fee:金额最多只能保留两位小数"), "total_fee:金额最多只能保留两位小数")
  56. return
  57. }
  58. //创建支付记录
  59. sqlData.ClientOpenID = inData.ClientOpenId
  60. sqlData.RequestID = inData.RequestId
  61. sqlData.CreatedAt = time.Now()
  62. sqlData.UpdatedAt = time.Now()
  63. sqlData.Amount = inData.Amount
  64. sqlData.OutTradeNo = strconv.FormatInt(inData.MerchantCardId, 10) + strconv.FormatInt(time.Now().UnixNano(), 10)
  65. sqlData.Status = 1 //未支付
  66. sqlData.MerchantCardID = inData.MerchantCardId
  67. sqlData.InvitationCode = inData.InvitationCode
  68. _, err = sqlData.Create()
  69. if err != nil {
  70. app.Error(c, 400, err, "创建支付失败")
  71. return
  72. }
  73. fmt.Println(sqlData.OutTradeNo)
  74. bm := make(gopay.BodyMap)
  75. bm.Set("nonce_str", common.GetRandomString(32))
  76. bm.Set("body", "商户卡")
  77. bm.Set("out_trade_no", sqlData.OutTradeNo)
  78. bm.Set("total_fee", inData.Amount.Mul(decimal.NewFromInt(100)).IntPart())
  79. bm.Set("spbill_create_ip", "127.0.0.1")
  80. bm.Set("notify_url", "https://shisanmiao.com/v1/client/pay/callback")
  81. bm.Set("device_info", "WEB")
  82. bm.Set("trade_type", "JSAPI")
  83. bm.Set("sign_type", wechat.SignType_MD5)
  84. bm.Set("openid", inData.ClientOpenId)
  85. client := NewWechatService()
  86. //请求支付下单,成功后得到结果
  87. wxResp, err := client.UnifiedOrder(c, bm)
  88. if err != nil {
  89. app.Error(c, 400, err, "下单失败")
  90. return
  91. }
  92. if wxResp.ReturnCode != "SUCCESS" {
  93. app.Error(c, 400, errors.New(wxResp.ReturnMsg), "下单失败")
  94. return
  95. }
  96. if wxResp.ResultCode != "SUCCESS" {
  97. app.Error(c, 400, errors.New(wxResp.ErrCode+"--"+wxResp.ErrCodeDes), "下单失败")
  98. return
  99. }
  100. timestamp := strconv.FormatInt(time.Now().Unix(), 10)
  101. pac := "prepay_id=" + wxResp.PrepayId
  102. paySign := wechat.GetMiniPaySign("wx25357518f710b8ce", wxResp.NonceStr, pac, wechat.SignType_MD5, timestamp, "1RKRJBVH4vaRrF0XPW9GX2M3ZSImukIz")
  103. outData.Timestamp = timestamp
  104. outData.NonceStr = wxResp.NonceStr
  105. outData.Package = pac
  106. outData.PaySign = paySign
  107. outData.SignType = wechat.SignType_MD5
  108. //merchant, count, err := sqlData.GetOpenIdList(pageSize, pageIndex)
  109. //if err != nil {
  110. // app.Error(c, 500, err, err.Error())
  111. // return
  112. //}
  113. app.OK(c, outData, app.Success)
  114. }
  115. func PayCashOut(c *gin.Context) {
  116. var inData models.PayCashOutRequest
  117. var merchantAccountSql shanghu.MerchantAccount
  118. var clientAccountSql shanghu.MerchantClientAccount
  119. var cashOut shanghu.CashOut
  120. var trans []models.TransferDetailList
  121. var transDetail models.TransferDetailList
  122. err := c.ShouldBindJSON(&inData)
  123. if err != nil {
  124. app.Error(c, 400, err, err.Error())
  125. return
  126. }
  127. if inData.Appid == "" { //appid 不能为空
  128. app.Error(c, 400, errors.New("Appid不能为空"), "Appid不能为空")
  129. return
  130. }
  131. if inData.Amount.Cmp(decimal.NewFromInt(500)) > 0 {
  132. app.Error(c, 400, errors.New("单笔金额不能大于500"), "单笔金额不能大于500")
  133. return
  134. }
  135. if inData.Amount.Cmp(decimal.NewFromInt(1)) < 0 {
  136. app.Error(c, 400, errors.New("单笔金额不能小于1"), "单笔金额不能大于1")
  137. return
  138. }
  139. //
  140. cashOut.OpenID = inData.OpenId
  141. cashOut.AppID = inData.Appid
  142. cashOut.Status = 1 //提现中
  143. status := []int{1, 3} //提现中、金额待扣减
  144. cashNum := cashOut.GetCashOutByStatusNum(status)
  145. if cashNum > 0 {
  146. app.Error(c, 400, errors.New("有一笔交易正在提现中"), "有一笔交易正在提现中")
  147. return
  148. }
  149. //校验金额是否够
  150. if inData.AccountType == "client" {
  151. clientAccountSql.ClientOpenID = inData.OpenId
  152. clientAccountInfo, err := clientAccountSql.GetClientAccount()
  153. if err != nil {
  154. app.Error(c, 400, err, err.Error())
  155. return
  156. }
  157. if inData.Amount.Cmp(clientAccountInfo.Amount) > 0 {
  158. app.Error(c, 400, errors.New("账号余额不够"), "账号余额不够")
  159. return
  160. }
  161. } else if inData.AccountType == "merchant" {
  162. merchantAccountSql.MerchantOpenID = inData.OpenId
  163. merchantAccountInfo, err := merchantAccountSql.GetMerchantAccount()
  164. if err != nil {
  165. app.Error(c, 400, err, err.Error())
  166. return
  167. }
  168. if inData.Amount.Cmp(merchantAccountInfo.Amount) > 0 {
  169. app.Error(c, 400, errors.New("账号余额不够"), "账号余额不够")
  170. return
  171. }
  172. } else {
  173. app.Error(c, 400, errors.New("账户类型错误"), "账户类型错误")
  174. return
  175. }
  176. clientV3, err := NewWechatServiceV3(inData.Appid)
  177. if err != nil {
  178. app.Error(c, 400, err, err.Error())
  179. return
  180. }
  181. //clientV3.WxPublicKeyMap()
  182. userName, err := clientV3.V3EncryptText(inData.UserName)
  183. if err != nil {
  184. app.Error(c, 400, err, err.Error())
  185. return
  186. }
  187. transDetail.OutDetailNo = common.GetRandomString(32)
  188. transDetail.TransferAmount = inData.Amount.Sub(cashOut.Fee).Mul(decimal.NewFromInt(100)).IntPart()
  189. transDetail.UserName = userName
  190. transDetail.Openid = inData.OpenId
  191. transDetail.TransferRemark = "报销"
  192. trans = append(trans, transDetail)
  193. //transBody, err := json.Marshal(transDetail)
  194. //if err != nil {
  195. // app.Error(c, 500, err, err.Error())
  196. // return
  197. //}
  198. partnerTradeNo := common.GetRandomString(32)
  199. var cashOutCreate shanghu.CashOut
  200. //创建提现记录
  201. cashOutCreate.AppID = inData.Appid
  202. cashOutCreate.Status = 1 //提现中
  203. cashOutCreate.OpenID = inData.OpenId
  204. cashOutCreate.Amount = inData.Amount
  205. cashOutCreate.CreatedAt = time.Now()
  206. cashOutCreate.UpdatedAt = time.Now()
  207. cashOutCreate.PartnerTradeNo = partnerTradeNo
  208. cashOutCreate.Fee = inData.Amount.Mul(decimal.NewFromFloat32(0.1))
  209. cashOutInfo, err := cashOutCreate.Create()
  210. if err != nil {
  211. app.Error(c, 400, err, err.Error())
  212. return
  213. }
  214. //client := NewWechatServiceAppid(inData.Appid)
  215. var bMap []gopay.BodyMap
  216. bm := make(gopay.BodyMap)
  217. bm.Set("appid", inData.Appid)
  218. bm.Set("out_batch_no", partnerTradeNo)
  219. bm.Set("batch_name", "报销")
  220. bm.Set("batch_remark", "报销")
  221. bm.Set("total_amount", inData.Amount.Sub(cashOutCreate.Fee).Mul(decimal.NewFromInt(100)).IntPart())
  222. bm.Set("total_num", 1)
  223. bMap = append(bMap, structToMap(&transDetail))
  224. bm.Set("transfer_detail_list", bMap)
  225. bm.Set("transfer_scene_id", "1001")
  226. fmt.Println(bm.JsonBody())
  227. //{
  228. // "appid": "wxf636efh567hg4356",
  229. // "out_batch_no": "plfk2020042013",
  230. // "batch_name": "2019年1月深圳分部报销单",
  231. // "batch_remark": "2019年1月深圳分部报销单",
  232. // "total_amount": 4000000,
  233. // "total_num": 200,
  234. // "transfer_detail_list": [
  235. //{
  236. //"out_detail_no": "x23zy545Bd5436",
  237. //"transfer_amount": 200000,
  238. //"transfer_remark": "2020年4月报销",
  239. //"openid": "o-MYE42l80oelYMDE34nYD456Xoy",
  240. //"user_name": "757b340b45ebef5467rter35gf464344v3542sdf4t6re4tb4f54ty45t4yyry45"
  241. //}
  242. //]
  243. //}
  244. reply, err := clientV3.V3Transfer(c, bm)
  245. if err != nil {
  246. cashOut.ID = cashOutInfo.ID
  247. cashOut.FailRes = err.Error()
  248. cashOut.Status = 2 //提现失败
  249. cashOut.UpdateMerchantStatus()
  250. app.Error(c, 500, err, err.Error())
  251. return
  252. }
  253. if reply.Code != 0 {
  254. cashOut.ID = cashOutInfo.ID
  255. cashOut.FailRes = reply.Error
  256. cashOut.Status = 2 //提现失败
  257. cashOut.UpdateMerchantStatus()
  258. app.Error(c, 500, errors.New(reply.Error), reply.Error)
  259. return
  260. }
  261. cashOut.WxPartnerTradeNo = reply.Response.BatchId
  262. cashOut.PartnerTradeNo = reply.Response.OutBatchNo
  263. cashOut.UpdateCashOutWxBachNo()
  264. //wxResp, err := client.Transfer(c, bm)
  265. //if err != nil {
  266. // cashOut.ID = cashOutInfo.ID
  267. // cashOut.FailRes = err.Error()
  268. // cashOut.Status = 2 //提现失败
  269. // cashOut.UpdateMerchantStatus()
  270. //
  271. // app.Error(c, 400, err, "提现失败")
  272. // return
  273. //}
  274. //
  275. //if wxResp.ReturnCode != "SUCCESS" {
  276. // cashOut.ID = cashOutInfo.ID
  277. // cashOut.FailRes = wxResp.ReturnMsg
  278. // cashOut.Status = 2 //提现失败
  279. // cashOut.UpdateMerchantStatus()
  280. // app.Error(c, 400, errors.New(wxResp.ReturnMsg), "提现")
  281. // return
  282. //}
  283. //if wxResp.ResultCode != "SUCCESS" {
  284. // cashOut.ID = cashOutInfo.ID
  285. // cashOut.FailRes = wxResp.ErrCode + "--" + wxResp.ErrCodeDes
  286. // cashOut.Status = 2 //提现失败
  287. // cashOut.UpdateMerchantStatus()
  288. // app.Error(c, 400, errors.New(wxResp.ErrCode+"--"+wxResp.ErrCodeDes), "提现")
  289. // return
  290. //}
  291. app.OK(c, nil, app.Success)
  292. }
  293. func PayCallBack(c *gin.Context) {
  294. var payLog shanghu.PayCallbackLog
  295. var payTrans shanghu.ClientPayTrans
  296. wxNotify, err := wechat.ParseNotifyToBodyMap(c.Request)
  297. if err != nil {
  298. c.XML(http.StatusOK, failResp)
  299. return
  300. }
  301. //通知回调log
  302. payLog.CallBackLog = wxNotify.JsonBody()
  303. payLog.ThirdTradeNo = wxNotify.Get("transaction_id")
  304. payLog.OutTradeNo = wxNotify.Get("out_trade_no")
  305. payLog.CreatedAt = time.Now()
  306. payLog.UpdatedAt = time.Now()
  307. _, err = payLog.Create()
  308. if err != nil {
  309. c.XML(http.StatusOK, failResp)
  310. return
  311. }
  312. if wxNotify.Get("return_code") != "SUCCESS" || wxNotify.Get("result_code") != "SUCCESS" {
  313. payLog.ErrLog = "微信返回错误:" + wxNotify.Get("return_code") + "--" + wxNotify.Get("result_code")
  314. payLog.UpdateMerchant()
  315. c.XML(http.StatusOK, failResp)
  316. return
  317. }
  318. //校验金额
  319. payTrans.OutTradeNo = wxNotify.Get("out_trade_no")
  320. payTransInfo, err := payTrans.GetPayTransByTradeNo()
  321. if err != nil {
  322. payLog.ErrLog = "查询交易信息错误:" + " err=" + err.Error()
  323. payLog.UpdateMerchant()
  324. c.XML(http.StatusOK, failResp)
  325. return
  326. }
  327. // 判断金额与支付流水是否一致
  328. totalFee, err := decimal.NewFromString(wxNotify.Get("total_fee"))
  329. if err != nil {
  330. payLog.ErrLog = "解析总金额报错:" + "err=" + err.Error()
  331. payLog.UpdateMerchant()
  332. c.XML(http.StatusOK, failResp)
  333. return
  334. }
  335. if !totalFee.Equal(payTransInfo.Amount.Mul(decimal.NewFromInt(100))) {
  336. payLog.ErrLog = "验证金额报错:total_fee=" + wxNotify.Get("total_fee") + " amount=" + payTransInfo.Amount.String()
  337. payLog.UpdateMerchant()
  338. c.XML(http.StatusOK, failResp)
  339. return
  340. }
  341. // 解析支付时间
  342. timeEnd, err := time.ParseInLocation("20060102150405", wxNotify.Get("time_end"), tools.TimeLocation)
  343. if err != nil {
  344. payLog.ErrLog = "付款时间解析出错:err=" + err.Error()
  345. payLog.UpdateMerchant()
  346. c.XML(http.StatusOK, failResp)
  347. return
  348. }
  349. payTrans.ThirdTradeNo = wxNotify.Get("transaction_id")
  350. payTrans.Status = 2 //支付成功
  351. payTrans.PayTime = timeEnd
  352. payTrans.AccountStatus = 1 //未分账
  353. err = payTrans.UpdatePayTransByTradeNo()
  354. if err != nil {
  355. payLog.ErrLog = "更新支付状态失败:err=" + err.Error()
  356. payLog.UpdateMerchant()
  357. c.XML(http.StatusOK, failResp)
  358. return
  359. }
  360. c.XML(http.StatusOK, successResp)
  361. }
  362. func NewWechatService() *wechat.Client {
  363. client := wechat.NewClient("wx25357518f710b8ce", "1501641641", "1RKRJBVH4vaRrF0XPW9GX2M3ZSImukIz", true)
  364. //设置国家
  365. client.SetCountry(wechat.China)
  366. return client
  367. }
  368. func NewWechatServiceAppid(appid string) *wechat.Client {
  369. client := wechat.NewClient(appid, "1501641641", "1RKRJBVH4vaRrF0XPW9GX2M3ZSImukIz", true)
  370. //设置国家
  371. client.SetCountry(wechat.China)
  372. return client
  373. }
  374. func NewWechatServiceV3(appid string) (*wechatV3.ClientV3, error) {
  375. key := `-----BEGIN PRIVATE KEY-----
  376. MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD/RTrS1Tycrskr
  377. SkuzdLNmOic4UklFtoyKtKDIeklbQFvynJBC1wYaitoOqFkdGWagkcuqRqJOcgEQ
  378. T8DRDD/rnVKTrnntevUSW3vZYwMscomK3dgjRRui5GLY5wYHuKhp02bldLrhPKNy
  379. wT5IYhoDwQLmf5IrURbtT8bKpdC+NyEEfw+d/mHMK56w80YKfzFtSmjoB9SkWDLg
  380. YFBlw91C78E73uqhCKnArN7p2iJe+tsp7EGpDVcdowqcMKjY2YiNPY6ABZaI9YJx
  381. nRQs5Sjm6YSsJujMR5rQVZ3HCfLlzxLpPyHfqffbJ+vviend1PJ/ugJeM2hehn1d
  382. LrpKuWyHAgMBAAECggEAIbow6nhYGM+TLtATLnPF3ETkt7FPkxFqgk0ZTUOy+4aG
  383. X4jGGr60RL+Bzhv6Ijkf8SkyQp4whbLUZyZScIxwyZ2wsmiEHZd4V+OUeoV1fuLn
  384. P1zOWOKhoyUP1l630j9YqRrQZpLEuku7wMa9huzHSSWwT2odkvGU2OgIeO/to6P7
  385. gIE4Q+3pNmTdaXJweUlVoz6sdqsSWAZ729SxzY34zcRyXI+feBQb9n/C1hr9+Ge4
  386. KbBsvrECy0eUNiAT9f5dPwgwjyRpR9gJ8xxaezIYfxdpTTW3EB1Gf+xPX4X7ml2B
  387. zObPN2HEwLHhrwbOXLFP8F/62TK4fIGe2yXPIKIe8QKBgQD/t4Xy9pTFrmNhDLMe
  388. ZMHdj9XMoM/izcquI6rq4pGWLtnyxIcFuXiqvPszbJL+urCc1+HqfPAVScryEg5W
  389. b6AvLENVhC5E3GcZA50ciAr+PUrB1UXH0vbbc7HP/Mofsz61kxEW2SvKaPoKhW8M
  390. IavoVWDJkcLHnjM20Gt7tioEXwKBgQD/jZR7FDSB8VCRp687QcH4s0HyAWaRVhmC
  391. qId2xVbPFVlbm4BWcQVWPdsUei29sPPtcvzREFStKwTmu2FSUfoDv3uM4Nl2+h/Y
  392. pbhfuYfHd79UZa0OtT9njJHZcYPwKRmZQIVBVHSJFSZy2HSGLe9AdI7gAsNMx/Mm
  393. eqZDU79I2QKBgQCBSNoSIpTI9QgNkwwkO7DAQe5IDK3N71mffSz2oCIXGgza7n2N
  394. aV4WhIFEWIpg+yY7xfHUSeJgAPT4OiTBkqIb93b7j16NNhlxzh/qwuU78OUQ5rDm
  395. /EQOY4nsq9PM/ySfTIGBWb8IENcJ5rhkG8n8Jt5OSsF9hwBBoFIXM9w+ZQKBgQD1
  396. rArw43Sy8uTskZKA2e96ggHEgBo1X9s4Y4GO6ZlRjQmRaoVPFGn4BZEGN4qfkGx/
  397. egqXhSaSLwgQNFUUCWDbl4pT3ZjRqxVQdcgwpjBkzratkO10dUOV7WoM6vbWuvwz
  398. +vXf3ywE2MNUpsgmciROB3+O1LkhqBsVg9UwZmM+yQKBgQD6yBvxOaHXBSZncz+m
  399. 3SpdbcRjK82i3IUJ3sl18J7YEPer0FclsUcQOluqBbHTOAr8letWPrKnjZGuyFfg
  400. gAjwa8uLSyfEEcWb9WcObvud2GNxS/LiI0GnW9ittvT29JvOhmUUtnty5/TiWIsi
  401. slm2kO53RSw9brymV8PAX2+SXg==
  402. -----END PRIVATE KEY-----`
  403. client, err := wechatV3.NewClientV3("1501641641", "219B3AF3B5F17D4C2F145EE318188708318DD7BD", "9x9ydkdk0nzsa4mr2ucq75grlvt9n8l3", key)
  404. if err != nil {
  405. return nil, err
  406. }
  407. // 启用自动同步返回验签,并定时更新微信平台API证书(开启自动验签时,无需单独设置微信平台API证书和序列号)
  408. err = client.AutoVerifySign()
  409. if err != nil {
  410. return nil, err
  411. }
  412. // 打开Debug开关,输出日志,默认是关闭的
  413. //client.DebugSwitch = gopay.DebugOn
  414. return client, nil
  415. }
  416. func structToMap(obj interface{}) gopay.BodyMap {
  417. result := make(gopay.BodyMap)
  418. value := reflect.ValueOf(obj).Elem() // 获取指针的值
  419. typ := value.Type() // 获取类型信息
  420. for i := 0; i < typ.NumField(); i++ {
  421. field := typ.Field(i) // 获取字段信息
  422. tag := field.Tag.Get("json") // 获取标签(如果有)
  423. if tag != "" && !field.Anonymous { // 只处理非匿名字段且有标签的情况
  424. key := field.Name // 默认使用字段名作为Key
  425. if tag != "-" { // 若标签不等于-则使用标签作为Key
  426. key = tag
  427. }
  428. result[key] = value.Field(i).Interface() // 存入Map
  429. }
  430. }
  431. return result
  432. }