小程序支付的交互图如下:
商户系统和微信支付系统主要交互:
1、小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API】
2、商户server调用支付统一下单,api参见公共api【统一下单API】
3、商户server调用再次签名,api参见公共api【再次签名】
4、商户server接收支付通知,api参见公共api【支付结果通知API】
5、商户server查询支付结果,api参见公共api【查询订单API】
以下是支付结果通知API
type WXPayNotifyReq struct {
Appid string `xml:"appid"`
Bank_type string `xml:"bank_type"`
Cash_fee float64 `xml:"cash_fee"`
Fee_type string `xml:"fee_type"`
Is_subscribe string `xml:"is_subscribe"`
Mch_id string `xml:"mch_id"`
Nonce_str string `xml:"nonce_str"`
Openid string `xml:"openid"`
Out_trade_no string `xml:"out_trade_no"`
Result_code string `xml:"result_code"`
Return_code string `xml:"return_code"`
Sign string `xml:"sign"`
Time_end string `xml:"time_end"`
Total_fee float64 `xml:"total_fee"`
Trade_type string `xml:"trade_type"`
Transaction_id string `xml:"transaction_id"`
}
type WXPayNotifyResp struct {
Return_code string `xml:"return_code"`
Return_msg string `xml:"return_msg"`
}
func WeixinNoticeHandler(rw http.ResponseWriter,req *http.Request) {
body,err := IoUtil.ReadAll(req.Body)
if err != nil {
logger.Error("读取http body失败,原因!",err)
http.Error(rw.(http.ResponseWriter),http.StatusText(http.StatusBadRequest),http.StatusBadRequest)
return
}
defer req.Body.Close()
logger.Info("微信支付异步通知,HTTP Body:",string(body))
var mr WXPayNotifyReq
err = xml.Unmarshal(body,&mr)
if err != nil {
logger.Error("解析HTTP Body格式到xml失败,原因!",http.StatusBadRequest)
return
}
var reqMap map[string]interface{}
reqMap = make(map[string]interface{},0)
reqMap["appid"] = mr.Appid
reqMap["bank_type"] = mr.Bank_type
reqMap["cash_fee"] = mr.Cash_fee
reqMap["fee_type"] = mr.Fee_type
reqMap["is_subscribe"] = mr.Is_subscribe
reqMap["mch_id"] = mr.Mch_id
reqMap["nonce_str"] = mr.Nonce_str
reqMap["openid"] = mr.Openid
reqMap["out_trade_no"] = mr.Out_trade_no
reqMap["result_code"] = mr.Result_code
reqMap["return_code"] = mr.Return_code
reqMap["time_end"] = mr.Time_end
reqMap["total_fee"] = mr.Total_fee
reqMap["trade_type"] = mr.Trade_type
reqMap["transaction_id"] = mr.Transaction_id
var resp WXPayNotifyResp
//进行签名校验
if wxpayVerifySign(reqMap,mr.Sign) {
//transactionId := reqMap["transaction_id"]
orderCode := reqMap["out_trade_no"]
total_fee := reqMap["total_fee"].(float64) //分->元 除以100
rows,err := MysqLDB.Query("SELECT * FROM canyin_order WHERE dno = ?",orderCode)
if err!=nil{
logger.Error("微信查询价格错误",err)
return
}
defer rows.Close()
orders := RowResult(rows)
if len(orders) > 0 {
orderInfo := orders[0].(map[string]interface{})
//orderId := ToStr(orderInfo["id"])
allcost,_ := strconv.ParseFloat(ToStr(orderInfo["allcost"]),64)
logger.Info("价格比对","---",allcost,total_fee)
//商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失
if allcost == total_fee {
logger.Info("订单验证成功")
//以下是业务处理
}
resp.Return_code = "SUCCESS"
resp.Return_msg = "OK"
}else{
resp.Return_code = "FAIL"
resp.Return_msg = "无此订单"
}
}else {
resp.Return_code = "FAIL"
resp.Return_msg = "Failed to verify sign,please retry!"
}
//结果返回,微信要求如果成功需要返回return_code "SUCCESS"
bytes,_err := xml.Marshal(resp) //string(bytes)
strResp := strings.Replace(bytes2str(bytes),"WXPayNotifyResp","xml",-1)
if _err != nil {
logger.Error("xml编码失败,原因:",_err)
http.Error(rw.(http.ResponseWriter),http.StatusBadRequest)
return
}
rw.(http.ResponseWriter).WriteHeader(http.StatusOK)
fmt.Fprint(rw.(http.ResponseWriter),strResp)
}
//微信支付签名验证函数
func wxpayVerifySign(needVerifyM map[string]interface{},sign string) bool {
pc,_,line,_ := runtime.Caller(0)
fc := runtime.FuncForPC(pc)
WECHAT_API_KEY := ""
signCalc := wxpayCalcSign(needVerifyM,WECHAT_API_KEY)
logger.Info(fc.Name(),"计算出来的sign: ",signCalc)
logger.Info(fc.Name(),"微信异步通知sign: ",sign)
if sign == signCalc {
logger.Info(fc.Name(),"签名校验通过!")
return true
}
logger.Error(fc.Name(),"签名校验失败!")
return false
}
//微信支付计算签名的函数
func wxpayCalcSign(mReq map[string]interface{},key string) (sign string) {
//方法名 行数
pc,_,line,_ := runtime.Caller(0)
fc := runtime.FuncForPC(pc)
logger.Info(fc.Name(),"微信支付签名计算,API KEY:",key)
//STEP 1,对key进行升序排序.
sorted_keys := make([]string,0)
for k,_ := range mReq { sorted_keys = append(sorted_keys,k) }
sort.Strings(sorted_keys)
//STEP2,对key=value的键值对用&连接起来,略过空值
var signStrings string
for _,k := range sorted_keys {
logger.Printf("k=%v,v=%v\n",k,mReq[k])
value := fmt.Sprintf("%v",mReq[k])
if value != "" {
signStrings = signStrings + k + "=" + value + "&"
}
}
//STEP3,在键值对的最后加上key=API_KEY
if key != "" {
signStrings = signStrings + "key=" + key
}
//STEP4,进行MD5签名并且将所有字符转为大写.
md5Ctx := md5.New()
md5Ctx.Write(str2bytes(signStrings))
cipherStr := md5Ctx.Sum(nil)
upperSign := strings.ToUpper(hex.EncodeToString(cipherStr))
return upperSign
}