1.支付前准备
1.1首先两个平台接入账户。
- 商户平台:https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F
- 公众平台:https://mp.weixin.qq.com/
1.2两个平台的作用
3 商户平台:支付收款方,通俗点将就是网站用户支付的钱给谁。里面有商户的一些信息以及秘钥支付时要用。
4.公众号平台:在这里需要它提供网页授权的一些信息。
2.微信公众号支付流程
关于支付流程,官方给了一张很详细的流程图如下:
当然这是所有支付的流程图,在这里博主结合自己的实现方式也画了一张只有授权和公众号支付流程的图。大家可以参考有画的不对的地方还请路过的大牛多多指教。
3. 微信公众号支付的那些坑
1:获取授权的时候,访问授权接口不要用ajax提交。如果用ajax提交方式提交请求,微信服务端会报跨域请求不允许的错误。
2:获取授权openid的时候appid,secret一定要正确,这里的secret获取方式为下图,如果忘记,可以重置:
3:如果用的springmvc的框架,在统一支付接口记得用注解@ResponseBody,因为统一支付接口返回的数据一般是一个map,这个map中的数据前台页面要解析,所以需要这个注解。
4:调用统一支付接口时,请求报文是xml格式的,所以要把需要的参数转变为xml形式。
5:异步回调方法是支付成功后微信通知商户后台时调用,所以测试时需要在外网测试
7:授权时,只授一次就行,所以在授权之前判断是否已经授权。
8:微信支付以分为单位,支付金额需要注意转换单位。
9:如果签名失败,一定要仔细检查参数是否都拼接完毕,拼接正确,一般签名失败最可能的原因就是secret错误,应该去微信公众平台重新查找并且核对。
10:退款时,注意双向证书的路径。一般退款回调失败最可能的原因就是证书出错。
4. 微信公众号开发需要的一些官网网站
商户平台开发者文档网址
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
用户授权需要查看的网址,重点查看用户管理相关说明
http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html
商户平台微信支付网址
https://pay.weixin.qq.com/wiki/doc/api/index.html
商户平台登录网址
https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2Findex.php%2Fcore%2Faccount
微信公众平台登录入口
https://mp.weixin.qq.com/
5.公众号支付代码
前端网页js代码
var pay_orderno; //订单编号
//判断是否是授权回来的outh
var outh = $.getUrlParam("outh");
if (!empty(outh)){
//说明outh不为空,即为授权回来的,则直接微信支付
wxzhifu();
}
function getRequest() {
var url = location.search; //获取url中"?"符后的字串
var theRequest = new Object();
if (url.indexOf("?") != -1) {
var str = url.substr(1);
strs = str.split("&");
for (var i = 0; i < strs.length; i++) {
theRequest[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1]);
}
}
return theRequest;
}
//保存订单信息
function saveOrder(){
var mode = "";
if($("#AliPay")[0].checked) {
mode = "支付宝";
}else if($("#bankpay")[0].checked) {
mode = "银联支付";
}else if($("#wxpay")[0].checked) {
mode = "微信支付";
}
var dishjson = data.dishJson; //订单表dishJson内容
var dishJson = (JSON.parse(dishjson))[0];
var oldcanpin = dishJson.canpin;
var newcanpin = oldcanpin + ",支付方式:"+ mode; //拼接支付方式以后的菜品信息
//alert(newcanpin);
dishJson["canpin"] = newcanpin;
data["dishJson"] = JSON.stringify(dishJson);
var url="/cyDishorder/saveCyDcOrders";
var successfull= function (datas) {
var bizData=datas.bizData;
pay_orderno = bizData.orderNum;
//alert(pay_orderno);
//var paymoney = $("#ydmoney").text().substring(1);
if(datas.rtnCode == "0000000"){
pay(); //跳转支付方法
//alert("==============")
}
}
ajaxPostFun(url, data, successfull,null, "保存餐厅预定订单");
}
//跳转到支付页面
function pay() {
if($("#AliPay")[0].checked) {
location.href = server+"/AliPayDingCan/" + pay_orderno;
}else if($("#bankpay")[0].checked) {
location.href = server+"/CYChinapay/" + pay_orderno;
}else if($("#wxpay")[0].checked){
//查询该用户是否微信授权
var url = server+"/Member/FindById/"+userid;
var successFun = function (data) {
if(empty(data.bizData.openid)){
//alert("微信未授权");
//如果该用户没有授权,则进行授权操作
wxshouquan();
}else{
//alert("微信已授权");
//用户已经授权,直接微信支付
wxzhifu();
}
}
ajaxPostFun(url, {}, successFun, null, "查询用户是否授权");
}else {
layer.msg("请选择支付方式");
return false;
}
}
//微信授权
function wxshouquan() {
location.href = server + "/CyWechatPay/outh?userid=" + userid;
}
//微信支付
function wxzhifu(){
var url = server+"/CyWechatPay/unifiedorder";
ajaxPostFun(url, {userid:userid,orderno:pay_orderno}, function(res) {
var _data = res.bizData;
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":_data.appId, //公众号名称,由商户传入
"timeStamp":_data.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr":_data.nonceStr, //随机串
"package" :_data.package,
"signType":_data.signType, //微信签名方式:
"paySign": _data.paySign //微信签名
},
function(ress){
alert( JSON.stringify(ress));
if(ress.err_msg == "get_brand_wcpay_request:ok" ) {
layer.msg("支付成功!",{time:2000});
//window.location.href = "buyGoodsChenggong.html?orderid="+orderid;
}else if(ress.err_msg == "get_brand_wcpay_request:cancel"){
layer.msg("支付取消!",{time:2000});
}else {
layer.msg("未知错误!",{time:2000});
} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
}
);
}
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
}, null, "微信支付获取package包");
}
支付,退款,controller代码
@Controller
@RequestMapping(value = "/CyWechatPay")
public class CyWechatController {
@Autowired
private IMemberService memberService;
@Autowired
private IMobileCyDishorderService mobileCyDishorderService;
@Autowired
private IMobileMemberService mobileMemberService;
@Autowired
private IMobileServiceStyleService mobileServiceStyleService;
private static Logger logger = Logger.getLogger(CyWechatController.class);
private static String url_snsapi_base = "https://open.weixin.qq.com/connect/oauth2/authorize";
private static String url_access_token = "https://api.weixin.qq.com/sns/oauth2/access_token";
private static String url_unifiedorder = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/**
* 统一下单
* @return prepare_id 预付款id
* @throws IOException
* @throws JDOMException
*/
@ResponseBody
@RequestMapping("/unifiedorder")
public Map<String, Object> unifiedorder(@RequestParam(value = "orderno", required = true)String orderno, String userid,HttpServletRequest request) throws Exception {
//根据订单号查询订单时间
CyDishorderEntity orderEntity = (CyDishorderEntity) mobileCyDishorderService.findOne("orderNum", orderno);
//获得随机数
String nonce_str = WxTool.getRandomCode(32);
//根据用户id查询用户信息
Map<String,Object> memberEntity =memberService.findByid("id",userid);
String openid = memberEntity.get("openid").toString();
//生成xml,参数说明(说明,标识码,订单号,IP,价格,openid用户在微信端的唯一标示)
String xml = this.getXmlData(orderEntity.getRemarks(), nonce_str,
orderno, WxTool.getRemoteHost(request), new BigDecimal(orderEntity.getPayMoney()),openid);
logger.info("生成的订单信息======>" + xml);
//订单提交的微信地址(预支付地址)
String result = HttpRequest.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST", xml);
//告诉编译器忽略 unchecked 警告信息,如使用List,ArrayList等未进行参数化产生的警告信息。
@SuppressWarnings("unchecked")
Map<String, Object> map2 = XMLUtil.doXMLParse(result);
logger.info("统一下单接口返回的结果集======>" + map2);
//return_code为微信返回的状态码,SUCCESS表示支付成功
//return_msg 如非空,为错误原因 签名失败 参数格式校验错误
if (map2.get("return_code").toString().equalsIgnoreCase("SUCCESS")
&& map2.get("result_code").toString().equalsIgnoreCase("SUCCESS")) {
//预支付成功
logger.info("微信预支付成功!");
//map2.put("timestamp",Long.toString(System.currentTimeMillis() / 1000));
//传递map2给前台页面处理
Map<String, Object> map = new HashMap<String, Object>();
map.put("appId", map2.get("appid"));
map.put("timeStamp", Long.toString(System.currentTimeMillis() / 1000));
map.put("nonceStr", map2.get("nonce_str"));
map.put("package", "prepay_id=" + map2.get("prepay_id"));
map.put("signType", "MD5");
map.put("paySign", WxTool.getSignUtil(map, ConfigUtil.APP_SECRECT));
logger.info("统一下------->" + map);
return map;
} else {
//支付失败,进行相应的失败业务处理
logger.info("微信预支付失败!");
//传递null给页面处理
return null;
}
}
/**
* 申请退款
*/
@RequestMapping("refund/{orderno}")
@ResponseBody
public String refund(@PathVariable String orderno) throws Exception {
//根据订单号查询订单
CyDishorderEntity orderEntity = (CyDishorderEntity) mobileCyDishorderService.findOne("orderNum", orderno);
//获得随机数
String nonce_str = WxTool.getRandomCode(32);
StringBuilder sb2 = new StringBuilder();
//微信签名需要的参数
Map<String, Object> signMap = new HashMap<String, Object>();
signMap.put("appid",ConfigUtil.APPID);//应用APPID
signMap.put("mch_id",ConfigUtil.MCH_ID);//微信支付商户号
signMap.put("nonce_str",nonce_str);//随机数
signMap.put("op_user_id",ConfigUtil.MCH_ID);
signMap.put("out_trade_no",orderEntity.getOrderNum());//订单号out_refund_no
signMap.put("out_refund_no",orderEntity.getRefund_queryid());//退款流水号
signMap.put("refund_fee",new BigDecimal(orderEntity.getPayMoney()).multiply(new BigDecimal(100)).intValue());//退款金额
signMap.put("total_fee",new BigDecimal(orderEntity.getPayMoney()).multiply(new BigDecimal(100)).intValue());//总金额
signMap.put("transaction_id","");//微信生成的订单号,在支付通知中有返回
//生成xml,微信要求的xml形式
StringBuffer xml =new StringBuffer();
xml.append("<xml>");
xml.append("<appid>"+ConfigUtil.APPID+"</appid>");//应用ID
xml.append("<mch_id>"+ConfigUtil.MCH_ID+"</mch_id>");//微信支付分配的商户号
xml.append("<nonce_str>"+nonce_str+"</nonce_str>");//随机字符串,不长于32位。
xml.append("<op_user_id>"+ConfigUtil.MCH_ID+"</op_user_id>");//操作员,默认为商户号
xml.append("<out_refund_no>"+orderEntity.getRefund_queryid()+"</out_refund_no>");//商户退款单号,商户系统内部的退款单号,商户系统内部唯一
xml.append("<out_trade_no>"+orderEntity.getOrderNum()+"</out_trade_no>");//商户订单号
xml.append("<refund_fee>"+new BigDecimal(orderEntity.getPayMoney()).multiply(new BigDecimal(100)).intValue()+"</refund_fee>");//退款金额
xml.append("<total_fee>"+new BigDecimal(orderEntity.getPayMoney()).multiply(new BigDecimal(100)).intValue()+"</total_fee>");//订单金额
xml.append("<transaction_id>"+"</transaction_id>");//微信订单号,微信生成的订单号,在支付通知中有返回
xml.append("<sign>"+WxTool.getSignUtil(signMap, ConfigUtil.APP_SECRECT)+"</sign>");//签名
xml.append("</xml>");
logger.info("生成的申请退款信息=============================>" + xml.toString());
/**
* JAVA使用证书文件
*/
logger.info("加载证书开始=========================================》》》》》");
//指定读取证书格式为PKCS12
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//读取本机存放的PKCS12证书文件
FileInputStream instream = new FileInputStream(new File("/home/smit/down/apiclient_cert.p12"));
try {
//指定PKCS12的密码(商户ID)
keyStore.load(instream, ConfigUtil.MCH_ID.toCharArray());
} finally {
instream.close();
}
//ssl双向验证发送http请求报文
SSLContext sslcontext = null;
sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, ConfigUtil.MCH_ID.toCharArray()).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund");
StringEntity se = new StringEntity(xml.toString(), "UTF-8");
httppost.setEntity(se);
//定义响应实例对象
CloseableHttpResponse responseEntry = null;
String xmlStr2 = null;//读入响应流中字符串的引用
responseEntry = httpclient.execute(httppost);//发送请求
HttpEntity entity = responseEntry.getEntity();//获得响应实例对象
if (entity != null) {//读取响应流的内容
BufferedReader bufferedReader = null;
bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent(), "UTF-8"));
while ((xmlStr2 = bufferedReader.readLine()) != null) {
sb2.append(xmlStr2);
}
}
Map<String, Object> map = XMLUtil.doXMLParse(sb2.toString());
logger.info("申请退款接口返回的结果集======>" + map);
//return_code为微信返回的状态码,SUCCESS表示申请退款成功,return_msg 如非空,为错误原因 签名失败 参数格式校验错误
if (map.get("return_code").toString().equalsIgnoreCase("SUCCESS")
&& map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) {
logger.info("****************退款申请成功!**********************");
//修改订单状态为申请退款
orderEntity.setOrderStatus(CyOrderStatusEnum.REFUND_SUCCESS.getCode());
mobileCyDishorderService.update(orderEntity);
return "SUCCESS";
} else {
logger.info("*****************退款申请失败!*********************");
return "FAIL";
}
}
//生成xml方法
private static String getXmlData(String body, String nonce_str, String tradeNo,
String ip, BigDecimal totla, String openid) throws Exception {
Map<String, Object> signMap = new HashMap<String, Object>();
signMap.put("appid", ConfigUtil.APPID);//应用APPID
signMap.put("nonce_str", nonce_str);//随机数
signMap.put("body", body);//商品描述
signMap.put("mch_id", ConfigUtil.MCH_ID);//微信支付商户号
signMap.put("notify_url", "http://域名/CyWechatPay/paysuccess.do");//异步通知回调地址
signMap.put("out_trade_no", tradeNo);//订单号
signMap.put("total_fee", totla.multiply(new BigDecimal(100)).intValue());//总金额
signMap.put("trade_type", "JSAPI");//支付类型
signMap.put("spbill_create_ip", ip);//客户端IP地址
signMap.put("openid", openid);//支付用户的唯一标识
StringBuffer xml = new StringBuffer();
xml.append("<xml>");
xml.append("<appid>" + signMap.get("appid") + "</appid>");//应用ID
xml.append("<body>" + signMap.get("body") + "</body>");
xml.append("<mch_id>" + signMap.get("mch_id") + "</mch_id>");//微信支付分配的商户号
xml.append("<nonce_str>" + signMap.get("nonce_str") + "</nonce_str>");//随机字符串,不长于32位。
xml.append("<notify_url>" + signMap.get("notify_url") + "</notify_url>");//接收微信支付异步通知回调地址
xml.append("<openid>" + signMap.get("openid") + "</openid>");//用户的openid 唯一标示,用户授权时或的
xml.append("<out_trade_no>" + signMap.get("out_trade_no") + "</out_trade_no>");//订单号
xml.append("<spbill_create_ip>" + signMap.get("spbill_create_ip") + "</spbill_create_ip>");//用户端实际ip
xml.append("<total_fee>" + signMap.get("total_fee") + "</total_fee>");//订单总金额,单位为分
xml.append("<trade_type>" + signMap.get("trade_type") + "</trade_type>");//支付类型
xml.append("<sign>" + WxTool.getSignUtil(signMap, ConfigUtil.APP_SECRECT) + "</sign>");//签名
xml.append("</xml>");
return xml.toString();
}
//微信支付异步回调
@RequestMapping(value = "/paysuccess")
public void PaySuccess(HttpServletRequest request, HttpServletResponse response){
try {
logger.info("微信支付回调开始========================================================");
PrintWriter print = null;
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String result = new String(outSteam.toByteArray(), "utf-8");
//告诉编译器忽略 unchecked 警告信息,如使用List,ArrayList等未进行参数化产生的警告信息。
@SuppressWarnings("unchecked")
Map<Object, Object> map = XMLUtil.doXMLParse(result);
//return_code为微信返回的状态码,SUCCESS表示支付成功
//return_msg 如非空,为错误原因 签名失败 参数格式校验错误
logger.info("微信返回的信息:"+map.toString());
if (map.get("result_code").toString().equalsIgnoreCase("SUCCESS")
&& map.get("return_code").toString().equalsIgnoreCase("SUCCESS")) {
String tradeNo = map.get("out_trade_no").toString();
//这里做出判断,防止微信服务的多次调用这里,造成一次支付,生成多个订单
CyDishorderEntity order =(CyDishorderEntity)mobileCyDishorderService.findOne("orderNum",tradeNo);
if(order!=null && order.getOrderStatus().equals(CyOrderStatusEnum.PREPAID.getCode())){
//该订单已经支付成功,直接return
response.getWriter().write(
"<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
}
updateOrder(map);//更新订单状态
}
response.getWriter().write(
"<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* 用户授权
*
* @param response
* @throws IOException
*/
@RequestMapping("/outh")
public void outh(@RequestParam(value = "userid", required = true) int userid,
HttpServletResponse response, HttpServletRequest request) {
StringBuffer url = request.getRequestURL();
//String tempContextUrl = url.delete(url.length() - request.getRequestURI().length(), url.length()).append("/").toString();
try {
// 回调地址
String redirecturl = URLEncoder.encode(new String(("http://域名/CyWechatPay/getopenid.do?userid=" + userid).getBytes("utf-8"), "utf-8"),"utf-8");
logger.info("[微信获取OPENID回调地址]"+redirecturl);
// 用户授权 snsapi_userinfo
response.sendRedirect("https://open.weixin.qq.com/connect/oauth2/authorize?appid="
+ ConfigUtil.APPID
+ "&redirect_uri="
+ redirecturl
+ "&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 商户获取用户资料
*
* @param request
* @return
* @throws IOException
*/
@RequestMapping("/getopenid")
public void getopenid(@RequestParam(value = "userid", required = true) int userid,
HttpServletRequest request, HttpServletResponse response) {
String code = request.getParameter("code");
Map<String, Object> map = null; // 存放授权access_token和openid
Map<String, Object> map2 = null;// 存放用户基本资料open
// 获取授权的access_token和openid
logger.info("[获取授权的access_token和openid]");
map = WXUtils.requestUrl("https://api.weixin.qq.com/sns/oauth2/access_token?appid="
+ ConfigUtil.APPID
+ "&secret="
+ConfigUtil.APP_SECRECT
+ "&code="
+ code
+ "&grant_type=authorization_code");
String access_token = map.get("access_token").toString();
String openid = map.get("openid").toString();
// 获取用户资料
map2 = WXUtils.requestUrl("https://api.weixin.qq.com/sns/userinfo?access_token=" + access_token + "&openid=" + openid + "&lang=zh_CN");
//设置用户的openid
memberService.updateuser(Integer.valueOf(userid),openid);
try {
response.sendRedirect("http://域名/Mobile/cyLijiZhifuX.html?=outh=outh");
} catch (IOException e) {
e.printStackTrace();
}
}
//更新订单信息
private void updateOrder(Map<Object, Object> map) {
//TODO:支付成功后更新订单的逻辑。
}
}