pay.go 16 KB

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