刷卡支付是用户展示钱包内的“刷微信卡条码/二维码”给商户系统,商户扫描后直接完成扣款。
主要应用线下面对面收银的场景,需要有扫描设备的支撑,最常见的莫过于扫码枪,但注意不能是激光扫码枪,因为这种枪因为反光的原因无法扫码手机上的条形码
用户打开微信,进入”我”->“钱包”->“刷卡”,展示条形码的界面
收银台在商户系统操作生成支付订单,用户确认支付金额
收银员使用扫码枪扫码用户展示的条形码/二维码,获得条形码/二维码信息,商户系统使用刷卡支付接口提交支付
如果需要验密,手机端会弹出密码输入框要求用户输入密码;否则直接完成扣款
支付金额>1000的交易需要输入密码
用户账户每天最多允许5笔免密交易,超过需要输入密码
微信支付后台判定用户行为有异常,符合免密规则的也要求输入密码
https://api.mch.weixin.qq.com/pay/micropay ,在package config里面为URL_MICROPAY变量
数据提交方式为post方式
数据格式为xml格式
在上面向接口提交支付的时候,会返回对应的结果,分2种情况,一种是直接扣款,称为免密;一种是微信支付要求用户输入密码,称为验密
刷卡支付实现中用到了package tools
里面的微信支付签名,网络请求,生成随机字符串,请先去实现
签名代码,点击查看
网络请求代码,点击查看
生成随机字符串代码,点击查看
创建文件夹micropay
创建文件 micropayrequest.go
,实现:
Mircopayrequest
结构体:存储被扫支付需要提交的参数func (v *Mircopayrequest) Signmd5()
:对Mircopayrequest
里面的非空字段进行签名,并存储签名func (v *Mircopayrequest) Xml()
:根据Mircopayrequest
里面的非空字段,生成对应的xml格式数据,并存储该数据func (v Mircopayrequest) Dorequest()
:向被扫支付接口以post的请求方式发送生成的xml数据,并返回结果代码实现
package micropay
import (
"encoding/xml"
"wechatpaygolang/config"
"wechatpaygolang/tools"
)
//1.创建Mircopayrequest结构体,存放我们的请求字段,带有 omitempty 表示该字段选填,其他的字段属于必填
type Mircopayrequest struct {
XMLName xml.Name `xml:"xml"` //表示xml数据格式
Appid string `xml:"appid"` //公众账号ID
Attach string `xml:"attach,omitempty"` //附加数据
Auth_code string `xml:"auth_code"` //授权码
Body string `xml:"body"` //商品描述
Device_info string `xml:"device_info,omitempty"` //设备号
Detail string `xml:"detail,omitempty"` //商品详情
Fee_type string `xml:"fee_type,omitempty"` //货币类型
Goods_tag string `xml:"goods_tag,omitempty"` //商品标记
Mch_id string `xml:"mch_id"` //商户号
Nonce_str string `xml:"nonce_str"` //随机字符串
Out_trade_no string `xml:"out_trade_no"` //商户订单号
Sign string `xml:"sign"` //签名
Spbill_create_ip string `xml:"spbill_create_ip"` //终端IP
Time_expite string `xml:"time_expite,omitempty"` //交易失效时间
Time_start string `xml:"time_start,omitempty"` //交易起始时间
Total_fee string `xml:"total_fee"` //总金额
RequestXML string `xml:"-"` //存放最终生成的xml请求数据,不参与签名以及xml的组成
}
//2.对Mircopayrequest里面的字段进行md5签名,存储到Mircopayrequest里面的Sign变量
func (v *Mircopayrequest) Signmd5() bool {
sign := tools.Wechatpay_SignMD5(*v, config.API_KEY)
v.Sign = sign
return true
}
//3.生成Mircopayrequest对应的xml数据,存储到Mircopayrequest里面的RequestXML变量
func (v *Mircopayrequest) Xml() error {
xmlresult, err := tools.XmlEndoestruct(v)
v.RequestXML = xmlresult
return err
}
//4.向刷卡接口发送post的请求,请求的数据为我们的生成存储在RequestXML变量的xml数据
// 并且将得到的数据解析到Mircopayresponse结构体
func (v Mircopayrequest) Dorequest() Mircopayresponse {
//发送post请求,f返回数据为data
data := tools.Post(config.URL_MICROPAY, v.RequestXML)
//解析data到Mircopayresponse结构体
mircopayresponse := Mircopayresponse{}
tools.XmlDecodebytes(data, &mircopayresponse)
mircopayresponse.ReponseXML = string(data)
//返回结果
return mircopayresponse
}
func value2CDATA(v string) CDATAText {
return CDATAText{""}
}
type CDATAText struct {
Text string `xml:",innerxml"`
}
创建文件 micropayresponse.go
,存储发起支付请求后的返回结果,实现代码如下
package micropay
import (
"encoding/xml"
)
//1.创建Mircopayresponse结构体,存放我们的请求字段,带有 omitempty 表示该字段选填,其他的字段属于必填,支付请求的结果会被解析到该结构体
type Mircopayresponse struct {
XMLName xml.Name `xml:"xml"`
Return_code string `xml:"return_code"` //返回状态码
Return_msg string `xml:"return_msg"` //返回信息
Appid string `xml:"appid"` //公众账号ID
Mch_id string `xml:"mch_id"` //商户号
Device_info string `xml:"device_info,omitempty"` //设备号
Nonce_str string `xml:"nonce_str"` //随机字符串
Sign string `xml:"sign"` //签名
Result_code string `xml:"result_code"` //业务结果
Err_code string `xml:"err_code"` //错误代码
Err_code_des string `xml:"err_code_des"` //错误代码描述
Is_subscribe string `xml:"is_subscribe"` //是否关注公众账号
Trade_type string `xml:"trade_type"` //交易类型
Bank_type string `xml:"bank_type"` //付款银行
Fee_type string `xml:"fee_type"` //货币类型
Total_fee string `xml:"total_fee"` //总金额
Cash_fee_type string `xml:"cash_fee_type"` //现金支付货币类型
Cash_fee string `xml:"cash_fee"` //现金支付金额
Coupon_fee string `xml:"coupon_fee"` //代金券或立减优惠金额
Transaction_id string `xml:"transaction_id"` //微信支付订单号
Out_trade_no string `xml:"out_trade_no"` //商户订单号
Attach string `xml:"attach"` //商家数据包
Time_end string `xml:"time_end"` //支付完成时间
ReponseXML string `xml:"-"` //结果xml串
}
创建文件miropay.go
,执行被扫支付操作,实现代码如下
package micropay
import (
"fmt"
"net/http"
"wechatpaygolang/config"
"wechatpaygolang/tools"
)
type Show struct {
Resault string
}
//1.执行刷卡支付功能入口
func Micropay(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
//获得要支付的金额
fee := r.FormValue("fee")
//获取用户的刷卡二维码
authcode := r.FormValue("authcode")
//创建Mircopayrequest请求参数
v := &Mircopayrequest{Appid: config.APP_ID, Mch_id: config.MCH_ID}
v.Auth_code = authcode
if fee != "" {
v.Total_fee = fee
} else {
v.Total_fee = "1"
}
v.Body = "weixin"
v.Nonce_str = tools.Getnoncestr(32)
v.Out_trade_no = tools.Getnoncestr(32)
v.Spbill_create_ip = "127.0.0.1"
// v.Goods_tag = "das"
//对请求参数进行MD5签名,得到签名字串
v.Signmd5()
// v.Sign = "607642655893AF4616B3DC406E659F32"
//把请求参数转换为xml格式的数据
v.Xml()
fmt.Fprintf(w, "请求数据如下\n")
fmt.Fprintf(w, v.RequestXML+"\n\n\n\n")
//发起支付请求,并得到返回结果
response := v.Dorequest()
//输出支付结果信息
fmt.Printf("返回状态:%s \n", response.Return_code)
fmt.Printf("返回信息: %s \n", response.Return_msg)
fmt.Printf("业务结果: %s \n", response.Result_code)
fmt.Printf("错误代码: %s \n", response.Err_code)
fmt.Printf("错误代码描述: %s\n", response.Err_code_des)
fmt.Println("========================================")
//打印支付结果到网页
w.Header().Set("Access-Control-Allow-Origin", "*") //允许访问所有域,解决跨域问题
fmt.Fprintf(w, "支付返回数据如下\n")
fmt.Fprintf(w, response.ReponseXML)
}
在main.go
文件里面
加入头文件"wechatpaygolang/micropay"
在rout
函数里面加入路由
func rout(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
path := r.URL.Path
if path == "/helloworld" {
fmt.Println("被扫支付")
helloworld.HelloWorld(w, r)
} else if path == "/micropay" {
fmt.Println("被扫支付")
micropay.Micropay(w, r)
}
}
这里我们就完成对url地址:/micropay
和micropay.Micropay()
函数的关联
打开浏览器,访问地址:localhost/micropay?fee=1&authcode=AUTHCODE
,得到如下运行结果,这里我的付款码不对,所以不会正确扣款
reponse.Return_code为SUCCESS,response.Result_code为SUCCESS,表示支付成功
reponse.Return_code为SUCCESS,reponse.Result_code为FAIL,reponse.Err_code为USERPAYING 表示正在输入密码
reponse.Err_code
以及对应的处理办法名称 | 描述 | 原因 | 解决方案 |
---|---|---|---|
SYSTEMERROR | 接口返回错误 | 系统超时 | 请立即调用被扫订单结果查询API,查询当前订单状态,并根据订单的状态决定下一步的操作 |
ORDERPAID | 订单已支付复 | 订单号重复 | 请确认该订单号是否重复支付,如果是新单,请使用新订单号提交 |
NOAUTH | 商户无权限 | 商户没有开通被扫支付权限 | 请开通商户号权产品或商务申请 |
AUTHCODEEXPIRE | 二维码已过期,请用户在微信上刷新后再试 | 用户的条码已经过期 | 请收银员提示用户,请用户在微信上刷新条码,然后请收银员重新扫码。 直接将错误展示给收银员 |
NOTENOUGH | 余额不足 | 用户的零钱余额不足 | 请收银员提示用户更换当前支付的卡,然后请收银员重新扫码。建议:商户系统返回给收银台的提示为“用户余额不足.提示用户换卡支付” |
NOTSUPORTCARD | 不支持卡类型 | 用户使用卡种不支持当前支付形式,请用户重新选择卡种 | 建议:商户系统返回给收银台的提示为“该卡不支持当前支付,提示用户换卡支付或绑新卡支付” |
ORDERCLOSED | 订单已关闭 | 该订单已关 | 商户订单号异常,请重新下单支付 |
ORDERREVERSED | 订单已撤销 | 当前订单已经被撤销 | 当前订单状态为“订单已撤销”,请提示用户重新支付 |
BANKERROR | 银行系统异常 | 银行端超时 | 请立即调用被扫订单结果查询API,查询当前订单的不同状态,决定下一步的操作 |
USERPAYING | 用户支付中,需要输入密码 | 该笔交易因为业务规则要求,需要用户输入支付密码 | 等待5秒,然后调用被扫订单结果查询API,查询当前订单的不同状态,决定下一步的操作 |
AUTH_CODE_ERROR | 授权码参数错误 | 请求参数未按指引进行填写 | 每个二维码仅限使用一次,请刷新再试 |
AUTH_CODE_INVALID | 授权码检验错误 | 收银员扫描的不是微信支付的条码 | 请扫描微信支付被扫条码/二维码 |
XML_FORMAT_ERROR | XML格式错误 | XML格式错误 | 请检查XML参数格式是否正确 |
REQUIRE_POST_METHOD | 请使用post方法 | 未使用post传递参数 | 请检查请求参数是否通过post方法提交 |
SIGNERROR | 签名错误 | 参数签名结果不正确 | 请检查签名参数和方法是否都符合签名算法要求 |
LACK_PARAMS | 缺少参数 | 缺少必要的请求参数 | 请检查参数是否齐全 |
NOT_UTF8 | 编码格式错误 | 未使用指定编码格式 | 请使用UTF8编码格式 |
BUYER_MISMATCH | 支付帐号错误 | 暂不支持同一笔订单更换支付方 | 请确认支付方是否相同 |
APPID_NOT_EXIST | APPID不存在 | 参数中缺少APPID | 请检查APPID是否正确 |
MCHID_NOT_EXIST | MCHID不存在 | 参数中缺少MCHID | 请检查MCHID是否正确 |
OUT_TRADE_NO_USED | 商户订单号重复 | 同一笔交易不能多次提交 | 请核实商户订单号是否重复提交 |
APPID_MCHID_NOT_MATCH | appid和mch_id不匹配 | appid和mch_id不匹配 | 请确认appid和mch_id是否匹配 |
第一步:检查本地签名程序是否正确
使用签名检查工具(http://mch.weixin.qq.com/wiki/tools/signverify/),检查签名本地签名程序是否有错误
如何用签名检查工具生成的签名和本地程序生成的签名不一致,说明程序有错误,请先检查本地代码程序。
如果无误,表示程序没有问题,进行下一步API秘钥设置
第二步:API密钥设置
在使用签名检查工具检查没有错误的情况下, 可以确定是API密钥设置出错。
微信支付必须保证本地签名使用的key值和在商户平台设置的API密钥一致。
设置方法:
登陆商户平台(https://pay.weixin.qq.com/index.php/home/login) ,设置位置:账户设置-安全设置-API安全