美文网首页
SSM实现微信JSAPI支付以及退款

SSM实现微信JSAPI支付以及退款

作者: 东西汉 | 来源:发表于2019-04-15 16:03 被阅读0次

  微信支付方式有很多,这里主要记录网页微信支付唤起以及退款的主要流程,微信JSAPI支付必传openid,这里需要实现网页授权获取,网页授权先获取用户授权的code,然后拿code去换取access_token,基本实现可以参考PHP实现获取微信网页授权,获取用户信息

开发前准备

1.微信公众号后台配置业务域名和网页授权域名


配置业务域名和网页授权域名

2.微信支付平台的产品中心绑定APPID和开发配置域名


绑定APPID和开发配置域名
3.下载微信退款的证书工具
微信退款的校验证书

4.使用证书工具生成相应证书(1年有效期,注意及时更换)


证书文件
5.下载Java的SDK与DEMO下载
SDK与DEMO下载

以下附上开发代码

前端默认页面访问路径

https://open.weixin.qq.com/connect/oauth2/authorize?appid=你的appId&redirect_uri=URLEncoder.encode(你的网页地址&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

前端获取url中的code通过ajax来换取openId
    /**
     *  code换取用户的open_id
     */
    @RequestMapping(value = "getOpenid.htm")
    @ResponseBody
    public JSONObject getOpenid(HttpServletRequest request){
        String code = request.getParameter("code");
        String user_id = request.getParameter("user_id");
        JSONObject jsonObject = new JSONObject();
        if (RedisUtil.getString("is_web_access_token_valid" + user_id) != null) {
            jsonObject = JSONObject.parseObject(RedisUtil.getString("is_web_access_token_valid" + user_id));
        }else{
            String app_secrect = WechatUtil.app_secrect;
            String app_id = WechatUtil.app_id;
            String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
            jsonObject = HttpUtil.doGet(url.replace("APPID", app_id).replace("SECRET", app_secrect).replace("CODE", code));
            RedisUtil.setString("is_web_access_token_valid" + user_id, jsonObject.toJSONString(), 7000);
        }

        return jsonObject;
    }
微信继承SDK的工共配置文件
package com.shadmin.common.util;

import com.shadmin.common.util.wxpay.IWXPayDomain;
import com.shadmin.common.util.wxpay.WXPayConfig;
import com.shadmin.common.util.wxpay.WXPayConstants;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.math.BigDecimal;

public class WechatPayUtil extends WXPayConfig {

    private byte[] certData;

    public WechatPayUtil() throws Exception {
        // 获取证书存放的路径(放在SSM项目根目录)
        String certPath =  Thread.currentThread().getContextClassLoader().getResource("1283062301_20181206_cert.p12").toString().replace("file:","");
        // certPath = "/E:/myjava/sy_shadmin/target/sy_shadmin/WEB-INF/classes/1283062301_20181206_cert.p12";
        File file = new File(certPath);
        InputStream certStream = new FileInputStream(file);
        this.certData = new byte[(int) file.length()];
        certStream.read(this.certData);
        certStream.close();
    }

    public String getAppID() {
        return "你的AppID";
    }

    public String getMchID() {
        return "你的商户ID";
    }

    public String getKey() {
        return "你的商户支付密钥";
    }

    public InputStream getCertStream() {
        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }

    public int getHttpConnectTimeoutMs() {
        return 8000;
    }

    public int getHttpReadTimeoutMs() {
        return 10000;
    }

    @Override
    protected IWXPayDomain getWXPayDomain() {
        IWXPayDomain iwxPayDomain = new IWXPayDomain() {
            @Override
            public void report(String domain, long elapsedTimeMillis, Exception ex) {

            }
            @Override
            public DomainInfo getDomain(WXPayConfig config) {
                return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
            }
        };
        return iwxPayDomain;
    }


    /**
     * 元转分(微信支付以分为单位)
     * @param yuan
     * @return
     */
    public static String Yuan2Fen(String yuan) {
        return new BigDecimal(yuan).movePointRight(2).toString();
    }

    /**
     * 分转元
     * @param fen
     * @return
     */
    public static String Fen2Yuan(String fen) {
        return new BigDecimal(fen).movePointLeft(2).toString();
    }
}
微信统一下单
/**
     * 获取微信支付参数
     */
    public JSONObject getWechatPayData(JSONObject param) throws Exception {
        WechatPayUtil wechatPayUtil = new WechatPayUtil();
        WXPay wxpay = new WXPay(wechatPayUtil);
        Map<String, String> data = new HashMap<String, String>();
        data.put("body", param.getString("goods_name"));
        data.put("out_trade_no", System.currentTimeMillis() + UUID.randomUUID().toString().replace("-", "").substring(0,5));
        data.put("device_info", param.getString("user_id"));
        data.put("fee_type", "CNY");
        data.put("total_fee", WechatPayUtil.Yuan2Fen(param.getString("total_fee")));
        data.put("spbill_create_ip", param.getString("spbill_create_ip"));
        data.put("notify_url", "你的微信支付服务器回调地址");
        data.put("trade_type", "JSAPI");  // 此处指定为JSAPI支付
        data.put("openid", param.getString("open_id"));
        Map<String, String> resp = wxpay.unifiedOrder(data);
        JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(resp));
        //新增返回值,方便模拟微信回调
        jsonObject.put("out_trade_no", data.get("out_trade_no"));
        jsonObject.put("spbill_create_ip", data.get("spbill_create_ip"));
        //新增支付等待有效期,方便定时任务扫描滤除缓存中过期未支付订单
        jsonObject.put("time_expire",System.currentTimeMillis());
        //新增订单编号,方便定时任务扫描滤除数据库中过期未支付订单
        jsonObject.put("out_trade_no", data.get("out_trade_no"));
        return jsonObject;
    }
后台将微信统一下单的返回值拼接成前段调用的参数(注意:微信统一下单的sign并不等同于前端微信支付唤起的paySign,否则会报错chooseWXPay:fail the permission value is offline verifying)
    /**
     * 查询是否有支付资格
     * @param request
     * @return
     */
    @RequestMapping(value = "checkIsPaying.htm")
    @ResponseBody
    public JSONObject checkIsPaying(HttpServletRequest request) throws Exception {

        String user_id = request.getParameter("user_id");
        JSONObject jsonObject = new JSONObject();
        //获取抢购的库存总数
        Object store_num_object = RedisUtil.getMapKey("second_kill_activity_goods_info", "store_num");
        int store_num = (int) ((store_num_object !=null)? store_num_object :0);
        //判断是否抢购失败
        if (RedisUtil.getMapSize("second_kill_order") < store_num) {
            //从redis的order_to_pay的map中读取对应的值
            if (RedisUtil.getMapKey("second_kill_pay", user_id) != null) {
                JSONObject pay_detail = JSONObject.parseObject(RedisUtil.getMapKey("second_kill_pay", user_id).toString());
                //根据微信支付参数增添返回值
                HashMap<String, String> map = new HashMap<>();
                map.put("appId", pay_detail.getString("appid"));
                map.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
                map.put("nonceStr", pay_detail.getString("nonce_str"));
                map.put("package", "prepay_id=" + pay_detail.getString("prepay_id"));
                map.put("signType", "MD5");
                //调用微信SDK自带的生成签名的方法生成paySign
                WechatPayUtil payUtil = new WechatPayUtil();
                map.put("paySign", WXPayUtil.generateSignature(map, payUtil.getKey()));
                map.put("out_trade_no", pay_detail.getString("out_trade_no"));
                jsonObject.put("code", 1);
                jsonObject.put("data", map);
                jsonObject.put("message", "订单已生成,请支付!");
            } else {
                jsonObject.put("code", 0);
                jsonObject.put("message", "抢购排队中,请稍后尝试!");
            }
        }else{
            jsonObject.put("code", -1);
            jsonObject.put("message", "抢购失败,抢购已结束!");
        }

        return jsonObject;
    }
获取微信支付设备的ip地址
package com.shadmin.common.util;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


/**
 * 工具类
 * @author Administrator
 *
 */
@SuppressWarnings("all")
public class ToolUtil {

    /**
     * 获取客户端IP
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        if (ip.equals("127.0.0.1") || ip.endsWith("0:0:0:0:0:0:0:1")) {
            ip = "127.0.0.1";
        }

        if (ip != null && ip.length() > 15) { // "***.***.***.***".length()= 15
            if (ip.indexOf(",") > 0) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        return ip;

    }

    /**
     * 把request转为map
     *
     * @param request
     * @return
     */
    public static Map<String, Object> getParameterMap(HttpServletRequest request) {
        // 参数Map
        Map<?, ?> properties = request.getParameterMap();
        // 返回值Map
        Map<String, Object> returnMap = new HashMap<String, Object>();
        Iterator<?> entries = properties.entrySet().iterator();

        Map.Entry<String, Object> entry;
        String name = "";
        String value = "";
        Object valueObj =null;
        while (entries.hasNext()) {
            entry = (Map.Entry<String, Object>) entries.next();
            name = (String) entry.getKey();
            valueObj = entry.getValue();
            if (null == valueObj) {
                value = "";
            } else if (valueObj instanceof String[]) {
                String[] values = (String[]) valueObj;
                for (int i = 0; i < values.length; i++) {
                    value = values[i] + ",";
                }
                value = value.substring(0, value.length() - 1);
            } else {
                value = valueObj.toString();
            }
            returnMap.put(name, value);
        }
        return returnMap;
    }
}
微信支付回调处理
    /**
     * 处理微信付款回调
     * 1.获取微信的回调参数  return_code 返回状态码   SUCCESS
     * 2.获取second_kill_pay中对应user_id的参数
     * 3.根据回调结果判断是否订单成功
     * 4.成功——添加到second_kill_order表
     * 5.失败——触发退款机制
     * 6.删除second_kill_pay队列数据
     * 7.返回给微信回调应答
     * @return
     */
    @RequestMapping("second_kill_notify.htm")
    public void second_kill_notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //响应微信回调,默认失败
        String notify_xml_response = resFailXml;
        // 1.获取微信支付回调xml
        InputStream inputStream = request.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length = 0;
        while ((length = inputStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, length);
        }
        outSteam.close();
        inputStream.close();
        String resultXml = new String(outSteam.toByteArray(), "utf-8");
        logger.info("微信支付回调:----result----:" + resultXml);
        WechatPayUtil config = new WechatPayUtil();
        WXPay wxpay = new WXPay(config);
        Map<String, String> notifyMap = WXPayUtil.xmlToMap(resultXml);  // 转换成map
        //  2.获取second_kill_pay中对应user_id的参数,user_id对应notifyMap中的device_info,下单时用device_info存放user_id
        String user_id = notifyMap.get("device_info");
        // 获取对应订单out_trade_no的订单second_order_pay值
        JSONObject pay_detail = JSONObject.parseObject(RedisUtil.getMapKey("second_kill_pay", user_id).toString());
        // 签名正确并且是首次调用
        if (wxpay.isPayResultNotifySignatureValid(notifyMap) && pay_detail.getString("out_trade_no").equals(notifyMap.get("out_trade_no"))) {
            //获取second_kill_activity_goods_info中的对应的秒杀价格参数
            String total_fee = RedisUtil.getMapKey("second_kill_activity_goods_info", "sale_price").toString();
            HashMap<Object, Object> map = new HashMap<>();
            map.put("pay_amount", WechatPayUtil.Fen2Yuan(notifyMap.get("total_fee")));
            map.put("out_trade_no", notifyMap.get("out_trade_no"));
            map.put("transaction_id", notifyMap.get("transaction_id"));
            map.put("update_time", df.format(new Date()));
            //  3.根据回调结果判断是否订单成功(total_fee支付金额一致,bank_type银行类型必须为建行信用卡CCB_CREDIT)
            if (notifyMap.get("bank_type").equals("CCB_CREDIT") && WechatPayUtil.Fen2Yuan(notifyMap.get("total_fee")).equals(total_fee)) {
                //  4.成功——添加到second_kill_order表,并将该订单写入成功队列
                map.put("status", "1");     //status 1-支付成功
                secondKillOrderListService.updateOrderList(map);
                //  往成功订单列表second_kill_order插入该user_id
                RedisUtil.addMap("second_kill_order", user_id, map);

            } else {
                //  5.失败——触发退款机制,并将失败订单写入订单表
                Map<String, String> data = new HashMap<String, String>();
                data.put("out_trade_no", notifyMap.get("out_trade_no"));
                data.put("out_refund_no", System.currentTimeMillis() + UUID.randomUUID().toString().replace("-", "").substring(0,18));
                data.put("total_fee", WechatPayUtil.Yuan2Fen(total_fee));
                data.put("refund_fee", notifyMap.get("total_fee"));
                data.put("refund_desc", "付款金额不一致或者非建行信用卡支付!");
                Map<String, String> refund_result = wxpay.refund(data);
                map.put("status", "2");     //status 2-已退款
                map.put("out_refund_no", data.get("out_refund_no"));
                map.put("pay_amount", WechatPayUtil.Fen2Yuan(notifyMap.get("total_fee")));
                secondKillOrderListService.updateOrderList(map);
                // 失败的是后删除排队列表的信息,方便再次发起抢购
                RedisUtil.delMapKey("second_kill_queue_data", user_id);
                logger.info("订单支付回调删除second_kill_queue_data:user_id="+user_id);
            }
            //  6.无论成功与否,删除second_kill_pay队列数据
            RedisUtil.delMapKey("second_kill_pay", user_id);
            logger.info("订单支付回调删除second_kill_pay:user_id="+user_id);
            //  7.响应微信回调,支付成功
            notify_xml_response = resSuccessXml;
        }
        //  7.返回给微信回调应答
        response.setContentType("text/xml");
        response.getWriter().write(notify_xml_response);
        response.flushBuffer();
    }
微信的支付DEMO已经很齐全了,所有微信支付开放的接口都可以参照README.MD去一一实现,

这里重点说两点:
1.统一下单接口的sign不等同于唤起微信支付时的paySign,否则手机微信端会看到微信支付闪一下就没了,微信开发者工具提示chooseWXPay:fail the permission value is offline verifying
2.JAVA的SDK支付实际下单时(区别与沙箱模式)默认是使用HMAC-SHA256方式加密,
但是在微信支付回调时校验回调签名默认使用MD5,所以可以将WXPAY文件的45行改成

this.signType = SignType.MD5; // 指定为Md5加密

3.高并发生成唯一订单号的方式

System.currentTimeMillis() + UUID.randomUUID().toString().replace("-", "").substring(0,18)

4.微信证书配置的位置在SSM项目的src根目录下,获取该路径的方式

String certPath = Thread.currentThread().getContextClassLoader().getResource("1283062301_20181206_cert.p12").toString().replace("file:","");
// certPath = "/E:/myjava/sy_shadmin/target/sy_shadmin/WEB-INF/classes/1283062301_20181206_cert.p12";

以下附上参考链接

微信支付唤起报错:the permission value is offline verifying
微信统一下单接口说明

相关文章

  • SSM实现微信JSAPI支付以及退款

      微信支付方式有很多,这里主要记录网页微信支付唤起以及退款的主要流程,微信JSAPI支付必传openid,这里需...

  • 微信支付(退款为例)

    微信支付退款的官方文档 https://pay.weixin.qq.com/wiki/doc/api/jsapi....

  • JSAPI 简介

    JSAPI 简介 JSAPI支付是指商户通过调用微信支付提供的JSAPI接口,在支付场景中调起微信支付模块完成收款...

  • 微信APP支付和退款(JAVA)

    微信APP支付和退款 微信支付流程说明 Java demo实例 退款 转账 1、微信支付 1 微信支付流程说明 本...

  • 对接微信和支付宝退款api

    1、微信退款 参考:(41条消息) weixin-java-pay实现公众号微信支付与退款_earthhour的博...

  • rails 微信申请退款

    微信申请退款 微信官方文档https://pay.weixin.qq.com/wiki/doc/api/jsapi...

  • 微信支付JSAPI支付

    记一次微信支付JSAPI支付功能集成。 JSAPI支付前提是必须在微信环境中(公众号、聊天界面打开链接),否则无法...

  • 苹果手机微信JSAPI支付诡异bug之特殊字符

    近日在项目中接入的微信JSAPI支付出现了很诡异的事情,H5 支付页面利用JSAPI支付时,安卓手机微信app中可...

  • 公众号网页对接微信支付

    一、背景 公众号网页对接微信支付,因为该网页会在多端打开,所以需对接H5支付,Native支付以及JSAPI支付,...

  • PHP微信支付开发(2)-退款

    一、概述 本系列博客将讨论基于微信支付的项目开发中,涉及到的下单与支付、退款、以及订单查询的后端代码实现。在本系列...

网友评论

      本文标题:SSM实现微信JSAPI支付以及退款

      本文链接:https://www.haomeiwen.com/subject/mkduwqtx.html