pay.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  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. // 代金券相关字段
  107. CouponFee0 string `xml:"coupon_fee_0"` //单个代金券支付金额,$n为下标,从0开始编号
  108. CouponId0 string `xml:"coupon_id_0"` //代金券ID,$n为下标,从0开始编号
  109. CouponFee1 string `xml:"coupon_fee_1"` //单个代金券支付金额,$n为下标,从0开始编号
  110. CouponId1 string `xml:"coupon_id_1"` //代金券ID,$n为下标,从0开始编号
  111. CouponFee2 string `xml:"coupon_fee_2"` //单个代金券支付金额,$n为下标,从0开始编号
  112. CouponId2 string `xml:"coupon_id_2"` //代金券ID,$n为下标,从0开始编号
  113. CouponFee3 string `xml:"coupon_fee_3"` //单个代金券支付金额,$n为下标,从0开始编号
  114. CouponId3 string `xml:"coupon_id_3"` //代金券ID,$n为下标,从0开始编号
  115. CouponFee4 string `xml:"coupon_fee_4"` //单个代金券支付金额,$n为下标,从0开始编号
  116. CouponId4 string `xml:"coupon_id_4"` //代金券ID,$n为下标,从0开始编号
  117. CouponFee5 string `xml:"coupon_fee_5"` //单个代金券支付金额,$n为下标,从0开始编号
  118. CouponId5 string `xml:"coupon_id_5"` //代金券ID,$n为下标,从0开始编号
  119. }
  120. // 获取jsSDK微信支付需要的数据
  121. func GetMechantInfo(mechantCode string) (ret MechantPayInfo) {
  122. switch mechantCode {
  123. case "fohow":
  124. ret.MchId = mchId
  125. ret.ApiKey = apiKey
  126. ret.MchKeyFile = beego.AppConfig.String("WxMchKeyFile")
  127. ret.MchCertFile = beego.AppConfig.String("MchCertFile")
  128. case "app":
  129. ret.MchId = mchId
  130. ret.ApiKey = apiKey
  131. ret.MchKeyFile = beego.AppConfig.String("WxMchKeyFile")
  132. ret.MchCertFile = beego.AppConfig.String("MchCertFile")
  133. case "lehu":
  134. ret.MchId = lehuMchId
  135. ret.ApiKey = lehuApiKey
  136. ret.MchKeyFile = beego.AppConfig.String("LehuMchKeyFile")
  137. ret.MchCertFile = beego.AppConfig.String("LehuMchCertFile")
  138. case "oubiao":
  139. ret.MchId = oubiaoMchId
  140. ret.ApiKey = oubiaoApiKey
  141. ret.MchKeyFile = beego.AppConfig.String("OuBiaoMchKeyFile")
  142. ret.MchCertFile = beego.AppConfig.String("OuBiaoMchCertFile")
  143. case "fenghe":
  144. ret.MchId = fengheMchId
  145. ret.ApiKey = fengheApiKey
  146. ret.MchKeyFile = beego.AppConfig.String("FengHeMchKeyFile")
  147. ret.MchCertFile = beego.AppConfig.String("FengHeMchCertFile")
  148. default:
  149. //ret.MchId = mchId
  150. //ret.ApiKey = apiKey
  151. //ret.MchKeyFile = beego.AppConfig.String("WxMchKeyFile")
  152. //ret.MchCertFile = beego.AppConfig.String("MchCertFile")
  153. ret.MchId = oubiaoMchId
  154. ret.ApiKey = oubiaoApiKey
  155. ret.MchKeyFile = beego.AppConfig.String("OuBiaoMchKeyFile")
  156. ret.MchCertFile = beego.AppConfig.String("OuBiaoMchCertFile")
  157. }
  158. return ret
  159. }
  160. // 获取APP微信支付需要的数据
  161. func GetAppPayData(outTradeNo string, totalPrice int64, body, notifyUrl, remoteIp, payCode string) (ret map[string]string) {
  162. appId := beego.AppConfig.String("WxFohowAndroAppAppId") //获取App appId
  163. mechantInfo := GetMechantInfo(payCode)
  164. outTradeNo = fmt.Sprintf("%s_%d", outTradeNo, time.Now().Unix())
  165. prepayId := getAppPayPrepayId(outTradeNo, totalPrice, body, notifyUrl, remoteIp, payCode, true)
  166. u := uuid.NewV4().String()
  167. us := strings.Split(u, "-")
  168. nonce_str := strings.Join(us, "")
  169. ret = map[string]string{
  170. "appid": appId,
  171. "partnerid": mechantInfo.MchId,
  172. "prepayid": prepayId,
  173. "package": "Sign=WXPay",
  174. "timestamp": fmt.Sprintf("%d", time.Now().Unix()),
  175. "noncestr": nonce_str}
  176. ret["pasignySign"] = mch.Sign(ret, mechantInfo.ApiKey, nil)
  177. beego.BeeLogger.Warn("ret--%v", ret)
  178. return ret
  179. }
  180. // 获取支付预授权码
  181. //
  182. // [pay.go:76] [E] GetPrepayId err[return_code: "FAIL", return_msg: "不识别的参数wxappid"]
  183. //
  184. // 2016/07/07 11:07:02 [pay_controller.go:303] [W] 生成微信支付订单号:DS20160707503D648C,传的参数:openid=o7lR2txmVPh1pSPjIvto2LO_XVYU,
  185. // 价格:9.90, 名称:希客购, 客户端IP=119.132.31.240;生成的参数:%!v(MISSING)
  186. func getAppPayPrepayId(outTradeNo string, totalPrice int64, body, notifyUrl, remoteIp, payCode string, limit bool) (prepayId string) {
  187. appId := beego.AppConfig.String("WxFohowAndroAppAppId") //获取App渠道appId
  188. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  189. mchProxy := getMchProxyInitClient(payCode, order_model.SOURCE_APP)
  190. u := uuid.NewV4().String()
  191. us := strings.Split(u, "-")
  192. nonce_str := strings.Join(us, "")
  193. req := map[string]string{
  194. "mch_id": mechantInfo.MchId,
  195. "appid": appId,
  196. "nonce_str": nonce_str, //fmt.Sprintf("%d", time.Now().Unix()),
  197. "body": body,
  198. "out_trade_no": outTradeNo,
  199. "total_fee": fmt.Sprintf("%d", totalPrice), //分为单位
  200. "spbill_create_ip": remoteIp,
  201. "notify_url": notifyUrl,
  202. // "product_id": outTradeNo,
  203. "trade_type": "APP",
  204. }
  205. //beego.BeeLogger.Warn("mechantInfo err[%s]", mechantInfo.MchId)
  206. if limit {
  207. req["limit_pay"] = "no_credit"
  208. }
  209. sign := mch.Sign(req, mechantInfo.ApiKey, nil)
  210. req["sign"] = sign
  211. beego.BeeLogger.Warn("%v", req)
  212. ret, err := pay.UnifiedOrder(mchProxy, req)
  213. if err != nil {
  214. beego.BeeLogger.Error("getAppPayPrepayId err[%s]", err)
  215. //fmt.Println(err)
  216. return ""
  217. }
  218. return ret["prepay_id"]
  219. }
  220. // 获取jsSDK微信支付需要的数据
  221. func GetPayData(openid, outTradeNo string, totalPrice int64, body, notifyUrl, remoteIp, payCode string) (ret map[string]string) {
  222. appId := beego.AppConfig.String("WxFohowXcxAppId") //获取小程序渠道appId
  223. mechantInfo := GetMechantInfo(payCode)
  224. prepayId := getPayPrepayId(openid, outTradeNo, totalPrice, body, notifyUrl, remoteIp, payCode, false)
  225. u := uuid.NewV4().String()
  226. us := strings.Split(u, "-")
  227. nonce_str := strings.Join(us, "")
  228. ret = map[string]string{
  229. "appId": appId,
  230. "timeStamp": fmt.Sprintf("%d", time.Now().Unix()),
  231. "package": fmt.Sprintf("prepay_id=%s", prepayId),
  232. "signType": "MD5",
  233. "nonceStr": nonce_str}
  234. ret["paySign"] = mch.Sign(ret, mechantInfo.ApiKey, nil)
  235. return ret
  236. }
  237. // 获取支付预授权码
  238. //
  239. // [pay.go:76] [E] GetPrepayId err[return_code: "FAIL", return_msg: "不识别的参数wxappid"]
  240. //
  241. // 2016/07/07 11:07:02 [pay_controller.go:303] [W] 生成微信支付订单号:DS20160707503D648C,传的参数:openid=o7lR2txmVPh1pSPjIvto2LO_XVYU,
  242. // 价格:9.90, 名称:希客购, 客户端IP=119.132.31.240;生成的参数:%!v(MISSING)
  243. func getPayPrepayId(openid, outTradeNo string, totalPrice int64, body, notifyUrl, remoteIp, payCode string, limit bool) (prepayId string) {
  244. appId := beego.AppConfig.String("WxFohowXcxAppId") //获取小程序渠道appId
  245. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  246. mchProxy := getMchProxyInitClient(payCode, order_model.SOURCE_XCX)
  247. outTradeNo = fmt.Sprintf("%s_%d", outTradeNo, time.Now().Unix())
  248. u := uuid.NewV4().String()
  249. us := strings.Split(u, "-")
  250. nonce_str := strings.Join(us, "")
  251. req := map[string]string{
  252. "mch_id": mechantInfo.MchId,
  253. "appid": appId,
  254. "nonce_str": nonce_str, //fmt.Sprintf("%d", time.Now().Unix()),
  255. "body": body,
  256. "out_trade_no": outTradeNo,
  257. "total_fee": fmt.Sprintf("%d", totalPrice), //分为单位
  258. "spbill_create_ip": remoteIp,
  259. "notify_url": notifyUrl,
  260. // "product_id": outTradeNo,
  261. "trade_type": "JSAPI",
  262. // "limit_pay": "no_credit",
  263. //"用户在商户 appid 下的唯一标识,trade_type 为 JSAPI时,此参数必传,获取方式见表头说明。",
  264. "openid": openid}
  265. if limit {
  266. req["limit_pay"] = "no_credit"
  267. }
  268. sign := mch.Sign(req, mechantInfo.ApiKey, nil)
  269. req["sign"] = sign
  270. //beego.BeeLogger.Warn("%v", req)
  271. ret, err := pay.UnifiedOrder(mchProxy, req)
  272. if err != nil {
  273. beego.BeeLogger.Error("GetPrepayId err[%s]", err)
  274. //fmt.Println(err)
  275. return ""
  276. }
  277. return ret["prepay_id"]
  278. }
  279. // 订单退款接口
  280. func GetRefundDataPay(outTradeNo, outRefundNo string, refundFee, totalPrice int64, transactionId, remark, payCode string) (ret map[string]string) {
  281. outTradeNo = fmt.Sprintf("%s_%d", outTradeNo, time.Now().Unix())
  282. appId := beego.AppConfig.String("WxFohowXcxAppId") //获取小程序渠道appId
  283. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  284. mchTLSProxy := getMchTLSProxyInitClient(payCode)
  285. u := uuid.NewV4().String()
  286. us := strings.Split(u, "-")
  287. nonce_str := strings.Join(us, "")
  288. req := map[string]string{
  289. "mch_id": mechantInfo.MchId,
  290. "appid": appId,
  291. "nonce_str": nonce_str, //fmt.Sprintf("%d", time.Now().Unix()),
  292. "transaction_id": transactionId,
  293. //"out_trade_no": outTradeNo,
  294. "out_refund_no": outRefundNo,
  295. "total_fee": fmt.Sprintf("%d", totalPrice), //分为单位,订单总金额
  296. "refund_fee": fmt.Sprintf("%d", refundFee), //分为单位,退款总金额
  297. //"notify_url": notifyUrl,
  298. "refund_desc": remark,
  299. }
  300. sign := mch.Sign(req, mechantInfo.ApiKey, nil)
  301. req["sign"] = sign
  302. beego.BeeLogger.Warn("%v", req)
  303. ret, err := pay.Refund(mchTLSProxy, req)
  304. if err != nil {
  305. beego.BeeLogger.Error("getGetRefundDataPay err[%s]", err)
  306. //fmt.Println(err)
  307. return nil
  308. }
  309. beego.BeeLogger.Warn("refund result----%v", ret)
  310. return ret
  311. }
  312. func getTradeNo(prefix string) string {
  313. n := time.Now().Format("20060102")
  314. u := uuid.NewV4().String()
  315. c := strings.Split(u, "-")
  316. s := strings.ToUpper(fmt.Sprintf("%s%s%s", prefix, n, c[0]))
  317. return s
  318. }
  319. // 企业付款
  320. //
  321. // NO_CHECK:不校验真实姓名
  322. // FORCE_CHECK:强校验真实姓名(未实名认证的用户会校验失败,无法转账)
  323. // OPTION_CHECK:针对已实名认证的用户才校验真实姓名(未实名认证用户不校验,可以转账成功)
  324. // 如果check_name设置为FORCE_CHECK或OPTION_CHECK,则必填用户真实姓名
  325. func Transfers(openid string, rmb int64, tradeNo, check, realName, payCode, desc string) map[string]string {
  326. // tradNo := getTradeNo("EP")
  327. appId := beego.AppConfig.String("WxFohowXcxAppId") //获取小程序渠道appId
  328. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  329. mchTLSProxy := getMchTLSProxyInitClient(payCode)
  330. req := map[string]string{
  331. "mch_appid": appId,
  332. "mchid": mechantInfo.MchId,
  333. "nonce_str": fmt.Sprintf("%d", time.Now().Unix()),
  334. "partner_trade_no": tradeNo,
  335. "openid": openid,
  336. "check_name": check,
  337. "re_user_name": realName,
  338. "amount": fmt.Sprintf("%d", rmb),
  339. "desc": desc,
  340. "spbill_create_ip": "8.129.187.89"}
  341. sign := mch.Sign(req, mechantInfo.ApiKey, nil)
  342. req["sign"] = sign
  343. ret, _ := promotion.Transfers(mchTLSProxy, req)
  344. //替换为原fohow微信支付
  345. //ret, _ := promotion.Transfers(takeMchTLSProxy, req)
  346. // if err != nil {
  347. // beego.BeeLogger.Error("Transfers(%s) err[%s],ret[%v]", openid, err, ret)
  348. // // return nil
  349. // }
  350. if ret["return_code"] != "SUCCESS" {
  351. return nil
  352. }
  353. return ret
  354. // if ret["result_code"] == "SUCCESS" {
  355. // return ret
  356. // } else {
  357. // beego.BeeLogger.Error("Transfers(%s) err[%s],ret[%v]", openid, err, ret)
  358. // return nil
  359. // }
  360. }
  361. // 校验支付结果回调参数
  362. func VerifyWxPayResult(payResult *PayResult, payCode string) bool {
  363. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  364. sign := payResult.Sign
  365. params, err := XmlStructToMap(payResult)
  366. if err != nil {
  367. beego.BeeLogger.Error("transfer xmlstruct to map err=[%s]", err)
  368. return false
  369. }
  370. // beego.BeeLogger.Warn("*****params: %v", params)
  371. // beego.BeeLogger.Warn("*****sign: %s", sign)
  372. mchSign := mch.Sign(params, mechantInfo.ApiKey, nil)
  373. // beego.BeeLogger.Warn("*****mch sign: %s", mchSign)
  374. return sign == mchSign
  375. }
  376. // 解析支付结果参数
  377. func ParsePayResult(data []byte) (*PayResult, error) {
  378. tt := data
  379. beego.BeeLogger.Warn("wx_mp.ParsePayResult() %s", string(tt[:]))
  380. var result PayResult
  381. err := xml.Unmarshal(data, &result)
  382. if err != nil {
  383. return nil, err
  384. }
  385. return &result, nil
  386. }
  387. // 校验支付结果回调参数
  388. func VerifyGzhPayResult(payResult *PayResult, payCode string) bool {
  389. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  390. sign := payResult.Sign
  391. params, err := XmlStructToMap(payResult)
  392. if err != nil {
  393. beego.BeeLogger.Error("transfer xmlstruct to map err=[%s]", err)
  394. return false
  395. }
  396. // beego.BeeLogger.Warn("*****params: %v", params)
  397. // beego.BeeLogger.Warn("*****sign: %s", sign)
  398. mchSign := mch.Sign(params, mechantInfo.ApiKey, nil)
  399. // beego.BeeLogger.Warn("*****mch sign: %s", mchSign)
  400. return sign == mchSign
  401. }
  402. // 校验支付结果回调参数
  403. func VerifyPayResult(payResult *PayResult, payCode string) bool {
  404. mechantInfo := GetMechantInfo(payCode) //商户号配置信息
  405. sign := payResult.Sign
  406. params, err := XmlStructToMap(payResult)
  407. if err != nil {
  408. beego.BeeLogger.Error("transfer xmlstruct to map err=[%s]", err)
  409. return false
  410. }
  411. //beego.BeeLogger.Warn("*****params: %v", params)
  412. //beego.BeeLogger.Warn("*****sign: %s", sign)
  413. mchSign := mch.Sign(params, mechantInfo.ApiKey, nil)
  414. // beego.BeeLogger.Warn("*****mch sign: %s", mchSign)
  415. return sign == mchSign
  416. }
  417. // map换为xml字符串
  418. func MapToXmlString(in map[string]string) string {
  419. xml := "<xml>"
  420. for k, v := range in {
  421. xml = xml + fmt.Sprintf("<%s>%s</%s>", k, v, k)
  422. }
  423. xml = xml + "</xml>"
  424. return xml
  425. }
  426. // 将xml结构体换为map
  427. func XmlStructToMap(in interface{}) (map[string]string, error) {
  428. out := make(map[string]string, 0)
  429. v := reflect.ValueOf(in)
  430. if v.Kind() == reflect.Ptr {
  431. v = v.Elem()
  432. }
  433. if v.Kind() != reflect.Struct {
  434. return nil, fmt.Errorf("ToMap only accepts structs; got %T", v)
  435. }
  436. typ := v.Type()
  437. for i := 0; i < v.NumField(); i++ {
  438. fi := typ.Field(i)
  439. if tagv := fi.Tag.Get("xml"); tagv != "" && tagv != "xml" && tagv != "sign" {
  440. out[tagv] = v.Field(i).String()
  441. }
  442. }
  443. return out, nil
  444. }