pay.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. package wx_mp
  2. import (
  3. // "encoding/json"
  4. "encoding/xml"
  5. "fmt"
  6. "fohow.com/apps/models/order_model"
  7. // "io/ioutil"
  8. // "net/http"
  9. "reflect"
  10. "strings"
  11. "time"
  12. "github.com/astaxie/beego"
  13. "github.com/uuid"
  14. // "github.com/astaxie/beego/context"
  15. "github.com/chanxuehong/wechat/mch"
  16. // "github.com/chanxuehong/wechat/mch/mmpaymkttransfers"
  17. "github.com/chanxuehong/wechat/mch/mmpaymkttransfers/promotion"
  18. "github.com/chanxuehong/wechat/mch/pay"
  19. // "github.com/chanxuehong/wechat/mp/user"
  20. // "go.xikego.com/apps/models/shop_model"
  21. // "go.xikego.com/cache"
  22. )
  23. const (
  24. //玖玖好购
  25. mchId = "1604238155"
  26. apiKey = "w7gb9ur5hb9g5326vy3sr0q4cobmrpgq"
  27. //乐福优选
  28. lehuMchId = "1581904571"
  29. lehuApiKey = "r1sbdza2mgs2zo3x2u6w8plgh91z0gqq"
  30. //凤凰欧标
  31. oubiaoMchId = "1601075858"
  32. oubiaoApiKey = "7592dac80b33214829b3484740162ac4"
  33. //丰和
  34. fengheMchId = "1616910264"
  35. fengheApiKey = "9WVsj03BL10ych7UlwmDmHhrLfoFqZqq"
  36. //微信公众号使用小程序的商户号和api加密
  37. gzhMchId = ""
  38. gzhApiKey = ""
  39. PAY_SUCCESS = "SUCCESS"
  40. PAY_FAIL = "FAIL"
  41. // NO_CHECK:不校验真实姓名
  42. // FORCE_CHECK:强校验真实姓名(未实名认证的用户会校验失败,无法转账)
  43. // OPTION_CHECK:针对已实名认证的用户才校验真实姓名(未实名认证用户不校验,可以转账成功)
  44. PAY_FORCE_CHECK = "FORCE_CHECK"
  45. PAY_NO_CHECK = "NO_CHECK"
  46. PAY_OPTION_CHECK = "OPTION_CHECK"
  47. )
  48. // 小程序端微信支付client
  49. func getMchProxyInitClient(payCode, source string) *mch.Proxy {
  50. mechant := GetMechantInfo(payCode)
  51. appId := ""
  52. switch source {
  53. case order_model.SOURCE_APP:
  54. appId = beego.AppConfig.String("WxFohowAndroAppAppId") //获取小程序渠道appId
  55. default:
  56. appId = beego.AppConfig.String("WxFohowXcxAppId") //获取小程序渠道appId
  57. }
  58. mchProxy := mch.NewProxy(appId, mechant.MchId, mechant.ApiKey, nil)
  59. return mchProxy
  60. }
  61. func getMchTLSProxyInitClient(payCode string) *mch.Proxy {
  62. appId := beego.AppConfig.String("WxFohowXcxAppId") //获取小程序渠道appId
  63. mechant := GetMechantInfo(payCode)
  64. var tlsHttpCilent, _ = mch.NewTLSHttpClient(
  65. mechant.MchCertFile,
  66. mechant.MchKeyFile)
  67. mchTLSProxy := mch.NewProxy(appId, mechant.MchId, mechant.ApiKey, tlsHttpCilent)
  68. return mchTLSProxy
  69. }
  70. // 商户渠道信息
  71. type MechantPayInfo struct {
  72. MchId string //商户号
  73. ApiKey string //api密钥
  74. MchKeyFile string //私钥路径
  75. MchCertFile string //证书路径
  76. }
  77. // 支付结果通知参数
  78. type PayResult struct {
  79. XMLName xml.Name `xml:"xml"`
  80. ReturnCode string `xml:"return_code"` //返回状态码
  81. ReturnMsg string `xml:"return_msg"` //返回信息
  82. AppId string `xml:"appid"` //公众账号ID
  83. MchId string `xml:"mch_id"` //商户号
  84. NonceStr string `xml:"nonce_str"` //随机字符串
  85. Sign string `xml:"sign"` //签名
  86. SignType string `xml:"sign_type"` //签名类型
  87. ResultCode string `xml:"result_code"` //业务结果
  88. ErrCode string `xml:"err_code"` //错误代码
  89. ErrCodeDesc string `xml:"err_code_des"` //错误代码描述
  90. DeviceInfo string `xml:"device_info"` //设备号
  91. OpenId string `xml:"openid"` //用户标识
  92. IsSubscribe string `xml:"is_subscribe"` //是否关注公众账号 Y/N
  93. TradeType string `xml:"trade_type"` //交易类型 JSAPI、NATIVE、APP
  94. BankType string `xml:"bank_type"` //付款银行
  95. TotalFee string `xml:"total_fee"` //总金额
  96. SettlementTotalFee string `xml:"settlement_total_fee"` //应结订单金额
  97. FeeType string `xml:"fee_type"` //货币种类
  98. CashFee string `xml:"cash_fee"` //cash_fee
  99. CashFeeType string `xml:"cash_fee_type"` //现金支付货币类型
  100. CouponFee string `xml:"coupon_fee"` //提货券或立减优惠金额
  101. CouponCount string `xml:"coupon_count"` //提货券或立减优惠使用数量
  102. TransactionId string `xml:"transaction_id"` //微信支付订单号
  103. OutTradeNO string `xml:"out_trade_no"` //商户订单号
  104. Attach string `xml:"attach"` // 商家数据包
  105. TimeEnd string `xml:"time_end"` // 支付完成时间 yyyyMMddHHmmss
  106. //coupon_id_$n
  107. //coupon_fee_$na
  108. //新增 coupon_fee
  109. CouponFee0 string `xml:"coupon_fee_0"` //单个提货券支付金额,$n为下标,从0开始编号
  110. CouponId0 string `xml:"coupon_id_0"` //提货券ID,$n为下标,从0开始编号
  111. CouponFee1 string `xml:"coupon_fee_1"` //单个提货券支付金额,$n为下标,从0开始编号
  112. CouponId1 string `xml:"coupon_id_1"` //提货券ID,$n为下标,从0开始编号
  113. CouponFee2 string `xml:"coupon_fee_2"` //单个提货券支付金额,$n为下标,从0开始编号
  114. CouponId2 string `xml:"coupon_id_2"` //提货券ID,$n为下标,从0开始编号
  115. CouponFee3 string `xml:"coupon_fee_3"` //单个提货券支付金额,$n为下标,从0开始编号
  116. CouponId3 string `xml:"coupon_id_3"` //提货券ID,$n为下标,从0开始编号
  117. }
  118. // 获取jsSDK微信支付需要的数据
  119. func GetMechantInfo(mechantCode string) (ret MechantPayInfo) {
  120. switch mechantCode {
  121. case "fohow":
  122. ret.MchId = mchId
  123. ret.ApiKey = apiKey
  124. ret.MchKeyFile = beego.AppConfig.String("WxMchKeyFile")
  125. ret.MchCertFile = beego.AppConfig.String("MchCertFile")
  126. case "app":
  127. ret.MchId = mchId
  128. ret.ApiKey = apiKey
  129. ret.MchKeyFile = beego.AppConfig.String("WxMchKeyFile")
  130. ret.MchCertFile = beego.AppConfig.String("MchCertFile")
  131. case "lehu":
  132. ret.MchId = lehuMchId
  133. ret.ApiKey = lehuApiKey
  134. ret.MchKeyFile = beego.AppConfig.String("LehuMchKeyFile")
  135. ret.MchCertFile = beego.AppConfig.String("LehuMchCertFile")
  136. case "oubiao":
  137. ret.MchId = oubiaoMchId
  138. ret.ApiKey = oubiaoApiKey
  139. ret.MchKeyFile = beego.AppConfig.String("OuBiaoMchKeyFile")
  140. ret.MchCertFile = beego.AppConfig.String("OuBiaoMchCertFile")
  141. case "fenghe":
  142. ret.MchId = fengheMchId
  143. ret.ApiKey = fengheApiKey
  144. ret.MchKeyFile = beego.AppConfig.String("FengHeMchKeyFile")
  145. ret.MchCertFile = beego.AppConfig.String("FengHeMchCertFile")
  146. default:
  147. //ret.MchId = mchId
  148. //ret.ApiKey = apiKey
  149. //ret.MchKeyFile = beego.AppConfig.String("WxMchKeyFile")
  150. //ret.MchCertFile = beego.AppConfig.String("MchCertFile")
  151. ret.MchId = oubiaoMchId
  152. ret.ApiKey = oubiaoApiKey
  153. ret.MchKeyFile = beego.AppConfig.String("OuBiaoMchKeyFile")
  154. ret.MchCertFile = beego.AppConfig.String("OuBiaoMchCertFile")
  155. }
  156. return ret
  157. }
  158. // 获取APP微信支付需要的数据
  159. func GetAppPayData(outTradeNo string, totalPrice int64, body, notifyUrl, remoteIp, payCode string) (ret map[string]string) {
  160. appId := beego.AppConfig.String("WxFohowAndroAppAppId") //获取App appId
  161. mechantInfo := GetMechantInfo(payCode)
  162. outTradeNo = fmt.Sprintf("%s_%d", outTradeNo, time.Now().Unix())
  163. prepayId := getAppPayPrepayId(outTradeNo, totalPrice, body, notifyUrl, remoteIp, payCode, true)
  164. u := uuid.NewV4().String()
  165. us := strings.Split(u, "-")
  166. nonce_str := strings.Join(us, "")
  167. ret = map[string]string{
  168. "appid": appId,
  169. "partnerid": mechantInfo.MchId,
  170. "prepayid": prepayId,
  171. "package": "Sign=WXPay",
  172. "timestamp": fmt.Sprintf("%d", time.Now().Unix()),
  173. "noncestr": nonce_str}
  174. ret["pasignySign"] = mch.Sign(ret, mechantInfo.ApiKey, nil)
  175. beego.BeeLogger.Warn("ret--%v", ret)
  176. return ret
  177. }
  178. // 获取支付预授权码
  179. //
  180. // [pay.go:76] [E] GetPrepayId err[return_code: "FAIL", return_msg: "不识别的参数wxappid"]
  181. //
  182. // 2016/07/07 11:07:02 [pay_controller.go:303] [W] 生成微信支付订单号:DS20160707503D648C,传的参数:openid=o7lR2txmVPh1pSPjIvto2LO_XVYU,
  183. // 价格:9.90, 名称:希客购, 客户端IP=119.132.31.240;生成的参数:%!v(MISSING)
  184. func getAppPayPrepayId(outTradeNo string, totalPrice int64, body, notifyUrl, remoteIp, payCode string, limit bool) (prepayId string) {
  185. appId := beego.AppConfig.String("WxFohowAndroAppAppId") //获取App渠道appId
  186. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  187. mchProxy := getMchProxyInitClient(payCode, order_model.SOURCE_APP)
  188. u := uuid.NewV4().String()
  189. us := strings.Split(u, "-")
  190. nonce_str := strings.Join(us, "")
  191. req := map[string]string{
  192. "mch_id": mechantInfo.MchId,
  193. "appid": appId,
  194. "nonce_str": nonce_str, //fmt.Sprintf("%d", time.Now().Unix()),
  195. "body": body,
  196. "out_trade_no": outTradeNo,
  197. "total_fee": fmt.Sprintf("%d", totalPrice), //分为单位
  198. "spbill_create_ip": remoteIp,
  199. "notify_url": notifyUrl,
  200. // "product_id": outTradeNo,
  201. "trade_type": "APP",
  202. }
  203. //beego.BeeLogger.Warn("mechantInfo err[%s]", mechantInfo.MchId)
  204. if limit {
  205. req["limit_pay"] = "no_credit"
  206. }
  207. sign := mch.Sign(req, mechantInfo.ApiKey, nil)
  208. req["sign"] = sign
  209. beego.BeeLogger.Warn("%v", req)
  210. ret, err := pay.UnifiedOrder(mchProxy, req)
  211. if err != nil {
  212. beego.BeeLogger.Error("getAppPayPrepayId err[%s]", err)
  213. //fmt.Println(err)
  214. return ""
  215. }
  216. return ret["prepay_id"]
  217. }
  218. // 获取jsSDK微信支付需要的数据
  219. func GetPayData(openid, outTradeNo string, totalPrice int64, body, notifyUrl, remoteIp, payCode string) (ret map[string]string) {
  220. appId := beego.AppConfig.String("WxFohowXcxAppId") //获取小程序渠道appId
  221. mechantInfo := GetMechantInfo(payCode)
  222. prepayId := getPayPrepayId(openid, outTradeNo, totalPrice, body, notifyUrl, remoteIp, payCode, true)
  223. u := uuid.NewV4().String()
  224. us := strings.Split(u, "-")
  225. nonce_str := strings.Join(us, "")
  226. ret = map[string]string{
  227. "appId": appId,
  228. "timeStamp": fmt.Sprintf("%d", time.Now().Unix()),
  229. "package": fmt.Sprintf("prepay_id=%s", prepayId),
  230. "signType": "MD5",
  231. "nonceStr": nonce_str}
  232. ret["paySign"] = mch.Sign(ret, mechantInfo.ApiKey, nil)
  233. return ret
  234. }
  235. // 获取支付预授权码
  236. //
  237. // [pay.go:76] [E] GetPrepayId err[return_code: "FAIL", return_msg: "不识别的参数wxappid"]
  238. //
  239. // 2016/07/07 11:07:02 [pay_controller.go:303] [W] 生成微信支付订单号:DS20160707503D648C,传的参数:openid=o7lR2txmVPh1pSPjIvto2LO_XVYU,
  240. // 价格:9.90, 名称:希客购, 客户端IP=119.132.31.240;生成的参数:%!v(MISSING)
  241. func getPayPrepayId(openid, outTradeNo string, totalPrice int64, body, notifyUrl, remoteIp, payCode string, limit bool) (prepayId string) {
  242. appId := beego.AppConfig.String("WxFohowXcxAppId") //获取小程序渠道appId
  243. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  244. mchProxy := getMchProxyInitClient(payCode, order_model.SOURCE_XCX)
  245. outTradeNo = fmt.Sprintf("%s_%d", outTradeNo, time.Now().Unix())
  246. u := uuid.NewV4().String()
  247. us := strings.Split(u, "-")
  248. nonce_str := strings.Join(us, "")
  249. req := map[string]string{
  250. "mch_id": mechantInfo.MchId,
  251. "appid": appId,
  252. "nonce_str": nonce_str, //fmt.Sprintf("%d", time.Now().Unix()),
  253. "body": body,
  254. "out_trade_no": outTradeNo,
  255. "total_fee": fmt.Sprintf("%d", totalPrice), //分为单位
  256. "spbill_create_ip": remoteIp,
  257. "notify_url": notifyUrl,
  258. // "product_id": outTradeNo,
  259. "trade_type": "JSAPI",
  260. // "limit_pay": "no_credit",
  261. //"用户在商户 appid 下的唯一标识,trade_type 为 JSAPI时,此参数必传,获取方式见表头说明。",
  262. "openid": openid}
  263. if limit {
  264. req["limit_pay"] = "no_credit"
  265. }
  266. sign := mch.Sign(req, mechantInfo.ApiKey, nil)
  267. req["sign"] = sign
  268. //beego.BeeLogger.Warn("%v", req)
  269. ret, err := pay.UnifiedOrder(mchProxy, req)
  270. if err != nil {
  271. beego.BeeLogger.Error("GetPrepayId err[%s]", err)
  272. //fmt.Println(err)
  273. return ""
  274. }
  275. return ret["prepay_id"]
  276. }
  277. // 订单退款接口
  278. func GetRefundDataPay(outTradeNo, outRefundNo string, totalPrice int64, transactionId, remark, payCode string) (ret map[string]string) {
  279. outTradeNo = fmt.Sprintf("%s_%d", outTradeNo, time.Now().Unix())
  280. appId := beego.AppConfig.String("WxFohowXcxAppId") //获取小程序渠道appId
  281. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  282. mchTLSProxy := getMchTLSProxyInitClient(payCode)
  283. u := uuid.NewV4().String()
  284. us := strings.Split(u, "-")
  285. nonce_str := strings.Join(us, "")
  286. req := map[string]string{
  287. "mch_id": mechantInfo.MchId,
  288. "appid": appId,
  289. "nonce_str": nonce_str, //fmt.Sprintf("%d", time.Now().Unix()),
  290. "transaction_id": transactionId,
  291. //"out_trade_no": outTradeNo,
  292. "out_refund_no": outRefundNo,
  293. "total_fee": fmt.Sprintf("%d", totalPrice), //分为单位,订单总金额
  294. "refund_fee": fmt.Sprintf("%d", totalPrice), //分为单位,退款总金额
  295. //"notify_url": notifyUrl,
  296. "refund_desc": remark,
  297. }
  298. sign := mch.Sign(req, mechantInfo.ApiKey, nil)
  299. req["sign"] = sign
  300. beego.BeeLogger.Warn("%v", req)
  301. ret, err := pay.Refund(mchTLSProxy, req)
  302. if err != nil {
  303. beego.BeeLogger.Error("getGetRefundDataPay err[%s]", err)
  304. //fmt.Println(err)
  305. return nil
  306. }
  307. return ret
  308. }
  309. func getTradeNo(prefix string) string {
  310. n := time.Now().Format("20060102")
  311. u := uuid.NewV4().String()
  312. c := strings.Split(u, "-")
  313. s := strings.ToUpper(fmt.Sprintf("%s%s%s", prefix, n, c[0]))
  314. return s
  315. }
  316. // 企业付款
  317. //
  318. // NO_CHECK:不校验真实姓名
  319. // FORCE_CHECK:强校验真实姓名(未实名认证的用户会校验失败,无法转账)
  320. // OPTION_CHECK:针对已实名认证的用户才校验真实姓名(未实名认证用户不校验,可以转账成功)
  321. // 如果check_name设置为FORCE_CHECK或OPTION_CHECK,则必填用户真实姓名
  322. func Transfers(openid string, rmb int64, tradeNo, check, realName, payCode, desc string) map[string]string {
  323. // tradNo := getTradeNo("EP")
  324. appId := beego.AppConfig.String("WxFohowXcxAppId") //获取小程序渠道appId
  325. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  326. mchTLSProxy := getMchTLSProxyInitClient(payCode)
  327. req := map[string]string{
  328. "mch_appid": appId,
  329. "mchid": mechantInfo.MchId,
  330. "nonce_str": fmt.Sprintf("%d", time.Now().Unix()),
  331. "partner_trade_no": tradeNo,
  332. "openid": openid,
  333. "check_name": check,
  334. "re_user_name": realName,
  335. "amount": fmt.Sprintf("%d", rmb),
  336. "desc": desc,
  337. "spbill_create_ip": "8.129.187.89"}
  338. sign := mch.Sign(req, mechantInfo.ApiKey, nil)
  339. req["sign"] = sign
  340. ret, _ := promotion.Transfers(mchTLSProxy, req)
  341. //替换为原fohow微信支付
  342. //ret, _ := promotion.Transfers(takeMchTLSProxy, req)
  343. // if err != nil {
  344. // beego.BeeLogger.Error("Transfers(%s) err[%s],ret[%v]", openid, err, ret)
  345. // // return nil
  346. // }
  347. if ret["return_code"] != "SUCCESS" {
  348. return nil
  349. }
  350. return ret
  351. // if ret["result_code"] == "SUCCESS" {
  352. // return ret
  353. // } else {
  354. // beego.BeeLogger.Error("Transfers(%s) err[%s],ret[%v]", openid, err, ret)
  355. // return nil
  356. // }
  357. }
  358. // 校验支付结果回调参数
  359. func VerifyWxPayResult(payResult *PayResult, payCode string) bool {
  360. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  361. sign := payResult.Sign
  362. params, err := XmlStructToMap(payResult)
  363. if err != nil {
  364. beego.BeeLogger.Error("transfer xmlstruct to map err=[%s]", err)
  365. return false
  366. }
  367. // beego.BeeLogger.Warn("*****params: %v", params)
  368. // beego.BeeLogger.Warn("*****sign: %s", sign)
  369. mchSign := mch.Sign(params, mechantInfo.ApiKey, nil)
  370. // beego.BeeLogger.Warn("*****mch sign: %s", mchSign)
  371. return sign == mchSign
  372. }
  373. // 解析支付结果参数
  374. func ParsePayResult(data []byte) (*PayResult, error) {
  375. tt := data
  376. beego.BeeLogger.Warn("wx_mp.ParsePayResult() %s", string(tt[:]))
  377. var result PayResult
  378. err := xml.Unmarshal(data, &result)
  379. if err != nil {
  380. return nil, err
  381. }
  382. return &result, nil
  383. }
  384. // 校验支付结果回调参数
  385. func VerifyGzhPayResult(payResult *PayResult, payCode string) bool {
  386. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  387. sign := payResult.Sign
  388. params, err := XmlStructToMap(payResult)
  389. if err != nil {
  390. beego.BeeLogger.Error("transfer xmlstruct to map err=[%s]", err)
  391. return false
  392. }
  393. // beego.BeeLogger.Warn("*****params: %v", params)
  394. // beego.BeeLogger.Warn("*****sign: %s", sign)
  395. mchSign := mch.Sign(params, mechantInfo.ApiKey, nil)
  396. // beego.BeeLogger.Warn("*****mch sign: %s", mchSign)
  397. return sign == mchSign
  398. }
  399. // 校验支付结果回调参数
  400. func VerifyPayResult(payResult *PayResult, payCode string) bool {
  401. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  402. sign := payResult.Sign
  403. params, err := XmlStructToMap(payResult)
  404. if err != nil {
  405. beego.BeeLogger.Error("transfer xmlstruct to map err=[%s]", err)
  406. return false
  407. }
  408. //beego.BeeLogger.Warn("*****params: %v", params)
  409. //beego.BeeLogger.Warn("*****sign: %s", sign)
  410. mchSign := mch.Sign(params, mechantInfo.ApiKey, nil)
  411. // beego.BeeLogger.Warn("*****mch sign: %s", mchSign)
  412. return sign == mchSign
  413. }
  414. // map换为xml字符串
  415. func MapToXmlString(in map[string]string) string {
  416. xml := "<xml>"
  417. for k, v := range in {
  418. xml = xml + fmt.Sprintf("<%s>%s</%s>", k, v, k)
  419. }
  420. xml = xml + "</xml>"
  421. return xml
  422. }
  423. // 将xml结构体换为map
  424. func XmlStructToMap(in interface{}) (map[string]string, error) {
  425. out := make(map[string]string, 0)
  426. v := reflect.ValueOf(in)
  427. if v.Kind() == reflect.Ptr {
  428. v = v.Elem()
  429. }
  430. if v.Kind() != reflect.Struct {
  431. return nil, fmt.Errorf("ToMap only accepts structs; got %T", v)
  432. }
  433. typ := v.Type()
  434. for i := 0; i < v.NumField(); i++ {
  435. fi := typ.Field(i)
  436. if tagv := fi.Tag.Get("xml"); tagv != "" && tagv != "xml" && tagv != "sign" {
  437. out[tagv] = v.Field(i).String()
  438. }
  439. }
  440. return out, nil
  441. }