微信支付-模式二

微信业务流程

(1)商户后台系统根据用户选购的商品生成订单。

(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;

(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。

(4)商户后台系统根据返回的code_url生成二维码。

(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。

(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。

(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。

(8)微信支付系统根据用户授权完成支付交易。

(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。

(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。

(11)未收到支付通知的情况,商户后台系统调用【查询订单API】。

(12)商户确认订单已支付后给用户发货。

简而言之:
调用微信生成预支付订单 –> 使用微信返回的微信地址生成二维码 –> 用户扫描二维码支付 –>
支付成功客户端下单时设置的回调地址调用 –> 写入成功后本地订单业务

基础配置:

  1. 下载微信sdk微信sdk、demo下载
  2. 打包sdk到maven本地仓库、引入sdk本地jar包引入本地maven仓库
  3. 注入sdk基础配置类、添加配置信息
package web.pay.config;

import com.github.wxpay.sdk.IWXPayDomain;
import com.github.wxpay.sdk.WXPayConfig;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 微信相关配置常量
 *
 * @author LiRui
 */
@Component
public class WeChatConfig extends WXPayConfig {

    /**
     * 交易类型
     */
    public static final String TRADE_TYPE_JSAPI = "JSAPI";
    /**
     * 扫码支付
     */
    public static final String TRADE_TYPE_NATIVE = "NATIVE";
    /**
     * app支付
     */
    public static final String TRADE_TYPE_APP = "APP";

    /**
     * 加密Key
     */
    @Value(value = "${wechat.key}")
    private String key;

    /**
     * 微信appId值
     */
    @Value(value = "${wechat.appid}")
    private String appId;

    /**
     * 微信appSecret值
     */
    @Value(value = "${wechat.appsecret}")
    private String appSecret;

    /**
     * 商户ID
     */
    @Value(value = "${wechat.mchid}")
    private String mchId;

    /**
     * 通知地址(支付完成后,回调地址)
     */
    @Value(value = "${wechat.paynotifyurl}")
    private String notifyUrl;

    /**
     * 支付宝域名
     */
    @Value(value = "${wechat.domain}")
    private String domain;

    /**
     * 证书位置
     */
    @Value(value = "${wechat.certPath}")
    private String certPath;

    /**
     * 使用沙盒
     */
    @Value(value = "${wechat.useSandbox}")
    private boolean useSandbox;

    @Override
    public String getAppID() {
        return appId;
    }

    @Override
    public String getMchID() {
        return mchId;
    }

    @Override
    public String getKey() {
        return key;
    }

    @Override
    public InputStream getCertStream() {
        if (StringUtils.isBlank(certPath)) {
            return null;
        }
        //证书位置
        File file = new File(certPath);
        InputStream certStream;
        try {
            certStream = new FileInputStream(file);
            certStream.read(new byte[(int) file.length()]);
            certStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    public String getAppSecret() {
        return appSecret;
    }

    public String getDomain() {
        return domain;
    }

    public String getCertPath() {
        return certPath;
    }

    @Override
    public IWXPayDomain getWXPayDomain() {
        return new IWXPayDomainImpl(domain);
    }

    public String getNotifyUrl() {
        return notifyUrl;
    }

    public boolean isUseSandbox() {
        return useSandbox;
    }

    public static class IWXPayDomainImpl implements IWXPayDomain {

        private static final Log LOG = LogFactory.getLog(IWXPayDomainImpl.class);

        private String domain;

        IWXPayDomainImpl(final String domain) {
            this.domain = domain;
        }

        @Override
        public void report(final String s, final long l, final Exception e) {
            LOG.info(String.format("域名:%s,耗时:%S,网络请求异常信息:%s", s, l,
                    null == e ? "无" : e.getMessage()));
        }

        /**
         * 设置域名
         *
         * @param wxPayConfig
         * @return
         */
        @Override
        public DomainInfo getDomain(final WXPayConfig wxPayConfig) {
            return new DomainInfo(this.domain, false);
        }
    }
}

配置Spring注入sdk连接

<!--引入配置文件-->
  <bean id="propertyConfigurer"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
      <list>
        <value>/WEB-INF/config/pay.properties</value>
      </list>
    </property>
  </bean>
<!--微信支付-->
  <bean name="wxPay" class="com.github.wxpay.sdk.WXPay">
    <!--微信配置-->
    <constructor-arg name="config" ref="weChatConfig"/>
    <!--开启自动报告-->
    <constructor-arg name="autoReport" value="${wechat.autoReport}"/>
    <!--回调地址-->
    <constructor-arg name="notifyUrl" value="${wechat.paynotifyurl}"/>
    <!--是否使用沙箱-->
    <constructor-arg name="useSandbox" value="${wechat.useSandbox}"/>
  </bean>

pay.properties

#微信支付参数
#加密Key
wechat.key=xxxxxxxxx
#微信appId值
wechat.appid=xxxxxxx
#微信appSecret值
wechat.appsecret=xxxxxxx
#商户ID
wechat.mchid=xxxxx
#通知地址(支付完成后,回调地址)
wechat.paynotifyurl=http://www.xxxx.com/xx/xx/xxxxx.do
#微信域名
wechat.domain=api.mch.weixin.qq.com
#证书地址
wechat.certPath=xxxxx
#使用沙盒环境
wechat.useSandbox=true
#自动通知
wechat.autoReport=true

controller下单接口设计:

/**
     * 微信扫码支付
     *
     * @return
     */
    @RequestMapping(value = "/weChatPay", method = RequestMethod.GET)
    @ResponseBody
    public Map weChatPay(HttpServletRequest request, @RequestParam BigDecimal money) {
        final String userSession = BaseUtil.getUserSession(request);
        if (StringUtils.isBlank(userSession)) {
            LOG.error("pc微信扫码支付用户登录过期");
            return JsonWrapperResult.failureWrapper(ErrorCode.NOT_LOGIN);
        }
        //生成订单
        Map<String, String> weChatPay;
        try {
            //生成订单
            weChatPay = webUserPayManager
                    .requestWeChatPay(money, userSession, request.getRemoteAddr());
        } catch (Exception e) {
            e.printStackTrace();
            LOG.error(String.format("pc微信扫码支付生成订单错误:%s", e.getMessage()), e);
            return JsonWrapperResult.failureWrapperMsg("微信支付调用错误");
        }
        final String url = weChatPay.get("code_url");
        return JsonWrapperResult
                .successWrapper("img", QRCodeUtil.createQRCodeBase64(url), "orderId",
                        weChatPay.get("orderId"));
    }

支付业务代码(service):

    @Autowired
    private WXPay wxPay;

    @Autowired
    private WeChatConfig weChatConfig;
    /**
     * 微信请求
     *
     * @return
     * @throws PayException 支付异常,返回失败抛出异常 回滚订单数据
     */
    @Transactional(rollbackFor = Exception.class)
    public Map<String, String> requestWeChatPay(BigDecimal buyMoney, String userId, String ip)
            throws Exception {
        final WzzwwUserPay userPay = saveUserPay(buyMoney, userId, true);
        //预下单
        HashMap<String, String> paramMap = new HashMap<>(9);
        String nonceStr = BaseUtil.generateUUID();
        //公众账号ID
        paramMap.put("appid", weChatConfig.getAppID());
        //商户号
        paramMap.put("mch_id", weChatConfig.getMchID());
        //随机字符串
        paramMap.put("nonce_str", nonceStr);
        //商品描述
        paramMap.put("body", "wzzww-coin");
        //订单号
        paramMap.put("out_trade_no", userPay.getOrderId());
        //标价金额(单位为分)
        paramMap.put("total_fee",
                String.valueOf(userPay.getBuyMoney().multiply(BigDecimal.valueOf(100)).setScale(0,
                        BigDecimal.ROUND_DOWN)));
        //通知地址
        paramMap.put("notify_url", weChatConfig.getNotifyUrl());
        //交易类型
        paramMap.put("trade_type", WeChatConfig.TRADE_TYPE_NATIVE);
        //商品类型,扫码支付必传
        paramMap.put("product_id", "coin");
        //ip
        paramMap.put("spbill_create_ip", ip);
        String sign = WXPayUtil.generateSignature(paramMap, weChatConfig.getKey());
        paramMap.put("sign", sign);
        final Map<String, String> stringMap = wxPay.unifiedOrder(paramMap);
        if (!"SUCCESS".equals(stringMap.get("return_code"))) {
            //预下单失败
            throw new PayException(String.format("调用支付出错:%s", JSONObject.toJSONString(stringMap)));
        }
        stringMap.put("orderId", userPay.getOrderId());
        return stringMap;
    }

生成对应字符串二维码代码工具类QRCodeUtil

package common.util;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Hashtable;
import java.util.Map;
import javax.imageio.ImageIO;
import org.apache.commons.codec.binary.Base64;

/**
 * 二维码
 *
 * @author LiRui
 * @version 1.0
 */
public class QRCodeUtil {

    private static final int BLACK = 0xFF000000;
    private static final int WHITE = 0xFFFFFFFF;
    /**
     * 默认宽度
     */
    private static final int WIDTH = 300;
    /**
     * 默认高度
     */
    public static final int HEIGHT = 300;
    /**
     * 默认格式
     */
    public static final String FORMAT = "gif";

    private QRCodeUtil() {
    }

    /**
     * 生成二维码
     * 默认:300*300 gif 格式
     *
     * @param text 内容
     * @param deposit 存放地址
     * @param name 名称
     * @throws Exception
     */
    public static void createQRCode(String text, String deposit, String name) throws Exception {
        createQRCode(text, WIDTH, HEIGHT, FORMAT, deposit, name);
    }


    /**
     * 生成二维码
     * 默认:300*300 gif 格式
     *
     * @param text 内容
     */
    public static String createQRCodeBase64(String text) {
        Map<EncodeHintType, Object> hints = new Hashtable<>();
        String base64Img = "data:image/png;base64,";
        // 指定编码格式
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        try {
            // 生成输出流
            BitMatrix bitMatrix1 = new MultiFormatWriter().encode(text,
                    BarcodeFormat.QR_CODE, WIDTH, HEIGHT, hints);
            BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix1);
            base64Img = base64Img + encodeToString("png", image);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return base64Img;
    }

    /**
     * 生成二维码
     * 默认:300*300 gif 格式
     *
     * @param text 内容
     * @throws Exception
     */
    public static void createQRCode(String text, OutputStream stream)
            throws WriterException, IOException {
        Hashtable<EncodeHintType, String> hints = new Hashtable<>();
        // 内容所使用字符集编码
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
        BitMatrix bitMatrix = new MultiFormatWriter()
                .encode(text, BarcodeFormat.QR_CODE, WIDTH, HEIGHT, hints);
        MatrixToImageWriter.writeToStream(bitMatrix, "gif", stream);
    }


    /**
     * 生成二位码
     *
     * @param text 内容
     * @param width 宽度
     * @param height 高度
     * @param format 格式
     * @param deposit 存放地址
     * @param name 图片名称
     * @throws Exception
     */
    public static void createQRCode(String text, int width, int height, String format,
            String deposit, String name) throws Exception {
        deposit = deposit.endsWith("/") ? deposit.substring(0, deposit.lastIndexOf("/")) : deposit;
        Hashtable<EncodeHintType, String> hints = new Hashtable<>();
        // 内容所使用字符集编码
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
        BitMatrix bitMatrix = new MultiFormatWriter()
                .encode(text, BarcodeFormat.QR_CODE, width, height, hints);
        // 生成二维码
        File outputFile = new File(deposit + File.separator + name + ".gif");
        MatrixToImageWriter.writeToPath(bitMatrix, format, outputFile.toPath());
    }


    private static BufferedImage toBufferedImage(BitMatrix matrix) {
        int width = matrix.getWidth();
        int height = matrix.getHeight();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
            }
        }
        return image;
    }

    public static void writeToFile(BitMatrix matrix, String format, File file) throws IOException {
        BufferedImage image = toBufferedImage(matrix);
        if (!ImageIO.write(image, format, file)) {
            throw new IOException("Could not write an image of format " + format + " to " + file);
        }
    }

    public static void writeToStream(BitMatrix matrix, String format, OutputStream stream)
            throws IOException {
        BufferedImage image = toBufferedImage(matrix);
        if (!ImageIO.write(image, format, stream)) {
            throw new IOException("Could not write an image of format " + format);
        }
    }

    /**
     * 将图片转换成base64格式进行存储
     *
     * @param formatName 文件格式
     * @param image 图片流
     * @return base64字符串
     */
    private static String encodeToString(String formatName, BufferedImage image) {
        String imageString = null;
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            ImageIO.write(image, formatName, bos);
            byte[] imageBytes = bos.toByteArray();
            imageString = new String(Base64.encodeBase64(imageBytes));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return imageString;
    }


}

回调接口,即以上配置中配置的回调接口地址(wechat.paynotifyurl)

   /**
     * 微信支付后,回调通知地址
     *
     * @param request
     */
    @RequestMapping("weChatPayNotify")
    public void weChatPayNotify(HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        BufferedReader reader = request.getReader();
        String line;
        StringBuffer inputString = new StringBuffer();
        while ((line = reader.readLine()) != null) {
            inputString.append(line);
        }
        String xmlString = inputString.toString();
        request.getReader().close();
        LOG.info("微信回调信息:" + xmlString);
        //微信jdk提供的xml转Map
        Map<String, String> requestParams = WXPayUtil.xmlToMap(xmlString);
        //返回结果
        String returnCode = requestParams.get("return_code");
        //业务返回结果
        String businessResultCode = requestParams.get("result_code");
        response.setContentType("text/xml");
        if (!"SUCCESS".equals(returnCode) && !"SUCCESS".equals(businessResultCode)) {
            LOG.error("微信回调支付失败:" + xmlString);
            // 返回错误
            response.getWriter().write("<xml>\n"
                    + "  <return_code><![CDATA[FAIL]]></return_code>\n"
                    + "  <return_msg><![CDATA[下单失败]]></return_msg>\n"
                    + "</xml>");
        }
        //订单号
        String outTradeNo = requestParams.get("out_trade_no");
        //返回金额
        String totalFee = requestParams.get("total_fee");
        //调用业务接口,返回成功失败
        final boolean finishOrder = xxxx
                .xxxxx(outTradeNo, BigDecimal.valueOf(Float.valueOf(totalFee)));
        if (finishOrder) {
            //返回成功
            response.getWriter().write(
                    "<xml>\n"
                            + "  <return_code><![CDATA[SUCCESS]]></return_code>\n"
                            + "  <return_msg><![CDATA[OK]]></return_msg>\n"
                            + "</xml>");
        }
        LOG.error("微信回调支付回写数据失败:" + xmlString);
        //返回失败
        response.getWriter().write("<xml>\n"
                + "  <return_code><![CDATA[FAIL]]></return_code>\n"
                + "  <return_msg><![CDATA[回写数据失败]]></return_msg>\n"
                + "</xml>");
    }

这里可以使用注解使用Spring MVC返回xml

最后前端轮询查询订单状态展示相应页面即可。

这里整个支付流程就完成了,顺便说一下,官方sdk有测试流程说明文档,开启沙箱时返回的地址是不能扫描的,回调接口会自动调用,只需等待几秒查看返回结果就行。沙箱只能使用文档中几个金额,其他金额都会调用失败,每个金额对应的调用支付的不同情况,需要注意。微信仿真系统,最下面下载测试用例文档

猜你喜欢

转载自blog.csdn.net/qq_28325291/article/details/81814347