Micro-letter JSAPI pay (a) unified under a single

Development Readiness

Reference document JSAPI pay development documents

payment method

There are currently six kinds of mainstream micro-channel payment

the way Explanation
Payment payment code Payment payment code is "credit card barcode / 2D code" to direct payments after completion of the merchant system scan mode in the user's wallet to show micro-channel. Silver face in the scene main application line.
Native payment Native payment system merchant is paid by the micro-channel protocol generator payment dimensional code, the user then micro-channel "sweep the" complete payment mode. This mode is suitable for PC site to pay the store a single product or order payment, payment and other media advertising scene.
JSAPI pay JSAPI pay a user to open businesses in the micro-page letter H5, H5 business page in the interface invoking micro-channel payment module to complete the payment by JSAPI call micro-channel pay offer.
APP pay APP payment terminal known as mobile payment, the merchant is by mode of mobile terminal applications APP integrated micro-channel open SDK invoking complete payment of the payment module.
H5 pay H5 paid mainly in mobile phones, ipad and other mobile devices to pay for goods and evoke micro-channel payment through a browser.
Applet pay Applet payment is specifically defined in the applet use payment products. Currently in the applet you can and only use a small program payments to evoke micro-channel pay.

Because the front did articles on public numbers, so here introduces JSAPI paid back, also developed around here.

JSAPI scenarios are:

  • The user enters the number of public business in the micro letter public accounts, open a home page, complete payment
  • User's friends in the circle of friends, chat windows and other businesses to share the connection page, the user clicks on the link to open the business page, complete payment
  • Convert business pages into two-dimensional code, after the user to scan two-dimensional code to open the page in a browser to complete the micro-channel pay

Core terms

Unlike micro channel test development public numbers, may be used within the network penetration test and general account number. Micro-channel payment requirements developer, you must have one verified real business number, and the number of businesses open payment function, and there is a real public numbers this business and so on.

  1. [Micro-channel merchant platform] micro-channel micro-channel business platform is to pay a set of business functions, including configuration parameters, payment data query and statistics, online refunds, vouchers or discount operators stand by the entrance platform features such as: pay.weixin.qq.com .

  2. [Micro] letter public platform for micro-channel public platform is a micro-channel public account application entry and management background. Merchants can submit basic information in the public platform, business information, financial information subscribe to micro-channel payment functions. Platform entrance: mp.weixin.qq.com .

  3. [Micro-channel payment system] micro-channel payment system is a general term for complete system API interface, back-office processing systems, accounting systems, callback notification letter and other micro-payment processes involved.

  4. [Certificate] merchant merchant certificate is provided by the micro-channel binary file, merchant payment system initiated when the backend server communication request with the micro-channel, micro-channel pay as the background to identify the true identity of the merchant's credentials.

  5. [System] background business merchant merchant back-office system is the general term for the background processing business systems, such as: business website, cash register systems, inventory systems, shipping systems, customer service systems, it is generally associated with the developer's own database.

  6. [Signature] business background and a micro-channel pay back the results generated from the same key and algorithm used to verify both the identity legitimacy. Signature algorithm developed and publicly, there are usual signature by the micro-channel pay: MD5, SHA1, SHA256, HMAC and so on.

  7. [Payment] payment password password is the password set by the user to open a separate micro-channel payment to confirm the completion of the payment transaction authorization. The micro-letter password and login passwords are different.

  8. [Openid User identity in a public number, have different numbers in different public openid. Login authorization by the merchant back office systems, payment notifications, orders and other inquiries API is available to users openid. The main purpose is to determine the same user sends a message to call the user, and so the message template.

Core accounts parameters apply:

Parameter Description Account

Mail parameters API parameter name Detailed description
APPID appid appid uniquely identifies the public account or micro-channel open platform of APP, after applying the public account public internet or internet application APP open an account, micro-channel is automatically assigned respective appid, for identifying the application. Can micro-channel public platform - the basic configuration inside view>, micro-channel merchants pay audit will be included in the field value through the mail -> Development.
No micro-channel pay merchants mch_id After the business application letter micro payments, paid by the micro-channel distribution business accounts receivable.
API key key Transaction generated key signatures, leaving only the background in merchant payment systems and micro-letters, does not propagate in the network. Merchants keep the Key, Never transport network, can not be stored in other client, to ensure that key will not be leaked. Merchant prompted to log micro letter can be set according to the message merchant platform. Also provided according to the following path: micro-channel merchant platform (pay.weixin.qq.com) -> Account Center -> Account Settings -> the API Security -> Key Set
Appsecret secret AppSecret is APPID corresponding interface password to obtain credentials to call the interface used access_token. In the micro-channel payment, first by OAuth2.0 interface to obtain the user openid, this openid for the single micro channel interface page payment mode. The public can log on the platform -> micro letter payment, get AppSecret (need to be a developer account and no abnormal state).

Protocol rules

Businesses access micro-channel payment, call the API must adhere to the following rules:

transfer method In order to ensure transaction security, the use of HTTPS transmission
Submission Using the POST method to submit
Data Format Submit and return data as XML, the root node named xml
Character Encoding Unity in UTF-8 character encoding
Signature Algorithm MD5/HMAC-SHA256
Signature Requirements Requesting and receiving data are to be verified signature, please refer to the detailed safety specifications - Signature Algorithm
Certificate Requirements Call to request a refund, cancel the order, the merchant api red interface needs a certificate, each api interface documentation are described.
Analyzing logic First determine the protocol field is returned, then return to business judgment, final judgment transaction status

Development code configuration parameters (actual development recommendation arranged directly in the attribute file, to facilitate context switches)

// 公众号、小程序appid
public static String APP_ID = "xxxxxxxxx"; 
// AppSecret
public static String SECRET = "xxxxxxxxx";
// 商户号
public static final String MCH_ID = "xxxxxxxxx";
// API密钥
public static final String API_KEY = "xxxxxxxxx";
// 网页授权域名,JSAPI支付授权目录,JS接口安全域名
public static final String AUTH_URL = "xxxxxxxxx";
复制代码

The above parameters could not be released. If the company has existing payment account the best, if not, I am afraid it can only be rented at some treasure, but did not affect these business development early.

Traffic grooming

Business Process timing diagram

For developers, the process of initiating the payment,

Rear: The main call JSAPI paid in three interfaces: [ unified under a single API ], [ notification API results payment ], [ Order Tracking API ]

front end:

Micro-channel front end H5 invoking payment, the user provides a trigger button and JSON data transmission channel micro payments.

Began to develop

Project to build

First, the use SpringBoot + Thymeleaf structure, the reference micro-channel rapid development public number (two) projects passive structures and reply

Second, the introduction of the official SDK toolkit

After reading the document, it was found when in fact commonly used method xml parsing, encryption algorithms, micro-channel for us to provide a semi-direct methods commonly used tools, note that these are only semi-finished products, the need to make the appropriate changes to use.

Links: SDK and DEMO download , select JAVA version you can download, unzip

Code development

Public No. Configuration

First, the public number and information about your business is injected into the Bean

@Component
public class WXPayConfigExtend extends WXPayConfig {

    private byte[] certData;

    private WXPayConfigExtend() throws Exception {
//        String certPath = WXPayConstants.APICLIENT_CERT;
//        File file = new File(certPath);
//        InputStream certStream = new FileInputStream(file);
//        this.certData = new byte[(int) file.length()];
//        certStream.read(this.certData);
//        certStream.close();
    }

    @Override
    public String getAppID() {
        return WXPayConstants.APP_ID;
    }
    @Override
    public String getMchID() {
        return WXPayConstants.MCH_ID;
    }
    @Override
    public String getKey() {
        return WXPayConstants.API_KEY;
    }
    @Override
    public InputStream getCertStream() {
        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }
    @Override
    public int getHttpConnectTimeoutMs() {
        return 2000;
    }
    @Override
    public int getHttpReadTimeoutMs() {
        return 10000;
    }
    @Override
    public IWXPayDomain getWXPayDomain() {
        return WXPayDomainSimpleImpl.instance();
    }
    public String getPrimaryDomain() {
        return "api.mch.weixin.qq.com";
    }
    public String getAlternateDomain() {
        return "api2.mch.weixin.qq.com";
    }
    @Override
    public int getReportWorkerNum() {
        return 1;
    }
    @Override
    public int getReportBatchSize() {
        return 2;
    }
}
复制代码

Get openid

Provide web pages need authorization to get openid, micro letter on page authorization may refer to: micro-channel public number for rapid development (four) micro-letter web page authorization

page:

Page directly designed this button can initiate a pre-payment of static pages: templates / preOrder.html

Which contains a jump to the back-end payment interface form:

<form name=wexinpayment action='http://chety.mynatapp.cc/api/v1/wechat1/placeOrder' method=post target="_blank">
    ...
复制代码

Thymeleaf next page forwarding controller:

@Controller
@RequestMapping("/api/v1/wechat1")
public class IndexController {

    // 用于thymeleaf环境下,跳转到字符串相应的html页面
    @RequestMapping("/{path}")
    public String webPath(@PathVariable String path) {
        return path;
    }
}   
复制代码

Web page unauthorized entry controller:

@Controller
@RequestMapping("/api/v1/wechat1")
public class IndexController {

    ...

    @RequestMapping("/index")
    public void index(String code, Model model, HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 显式授权,获得code
        if (code != null) {
            JSONObject json = WeChatUtil.getWebAccessToken(code);
            WXPayUtil.getLogger().info("code: ",json.toJSONString());
            String openid = json.getString(("openid"));
            request.getSession().setAttribute("openid", openid);
            WXPayUtil.getLogger().info("index openid={}",openid);
            // 重定向到预下单页面
            response.sendRedirect("preOrder"); // 重定向到预支付页面
        } else {
            StringBuffer url = RequestUtil.getRequestURL(request);
            WXPayUtil.getLogger().info("index 请求路径:{}"+url);
            String path = WeChatUtil.WEB_REDIRECT_URL.replace("APPID", WeChatConstants.APP_ID).replace("REDIRECT_URI", url).replace("SCOPE", "snsapi_userinfo");
            WXPayUtil.getLogger().info("index 重定向:{}",path);
            // 重定向到授权获取code的页面
            response.sendRedirect(path);
        }
    }
}    
复制代码

Start the project, request interface:

First, the micro-channel developer tools address bar: {pages authoritative name} // api / v1 / wechat1 / index

Second, confirm [agreed] authorization (where the purpose is to get openid, you can also use the base authorized silent mode, without displaying a prompt authorization), jump to the pre-payment page, as shown:

Initiated payment

When the user confirms the pre-paid orders page will request [/ placeOrder Interface, the service will call micro-channel [unified under a single Interface:

First, the micro-channel single unified entity class

@Setter
@Getter
@ToString
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class WxOrderEntity {
    private String appid;
    private String mchId;
    private String deviceInfo;
    private String nonceStr;
    private String sign;
    private String body;
    private String outTradeNo;
    private int totalFee;
    private String spbillCreateIp;
    private String notifyUrl;
    private String tradeType;
    private String openid;
}
复制代码

Second, the micro-channel payment service layer

@Service
public class WxBackendServiceImpl {

    @Autowired
    WXPayConfigExtend wxPayConfigExtend;

    // 统一下单
    public Map<String, Object> unifiedorder(Model model, HttpServletRequest request) throws Exception {
        WXPayUtil.getLogger().info("进入下单控制器...");
        Map<String,Object> data = null;
        try {
            //生成订单编号
            WXPay wxpay = new WXPay(wxPayConfigExtend);
            WxOrderEntity order = new WxOrderEntity();

            double price = 0.01;
            String orderName = "xxx--微信支付";
            int number = (int)((Math.random()*9)*1000);//随机数
            DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");//时间
            String orderNumber = dateFormat.format(new Date()) + number;
            String nonceStr = WXPayUtil.generateNonceStr();
            String openId = (String) request.getSession().getAttribute("openid");
            openId = openId == null ? "o4036jqo2PN9isV6N2FHGRsGRVqg" : openId; // 前一个openid,是chet在xxx公众号下的openid

            order.setBody(orderName);
            order.setOutTradeNo(orderNumber);
            order.setTotalFee(MoneyUtil.Yuan2Fen(price));
            order.setSpbillCreateIp(IpUtils.getIpAddr(request));
            order.setOpenid(openId);
            order.setNotifyUrl(WXPayConstants.NOTIFY_URL);
            order.setTradeType(WXPayConstants.TRADE_TYPE_JSAPI);
            order.setNonceStr(nonceStr);

            WXPayUtil.getLogger().info("save 统一下单接口调用,order:{}",order);
            // 利用sdk统一下单,已自动调用wxpay.fillRequestData(data);
            Map<String, String> response = wxpay.doWxPayApi(order,WXPayConstants.UNIFIEDORDER);
            WXPayUtil.getLogger().info("save 下单结果,response:{}",response);

            if(response.get(WXPayConstants.RETURN_CODE).equals("SUCCESS")&&response.get(WXPayConstants.RESULT_CODE).equals("SUCCESS")){
                String url = request.getQueryString() == null?request.getRequestURL().toString():request.getRequestURL()+"?"+request.getQueryString();
                String prepayId = response.get(WXPayConstants.PREPAY_ID);
                data = wxpay.permissionValidate(nonceStr,url,prepayId,wxPayConfigExtend.getKey());
                return data;
            }
        } catch (Exception e) {
            WXPayUtil.getLogger().error("doUnifiedOrder--下单失败:{}" , e.getMessage());
        }
        return null;
    }
}    
复制代码

wxpay.doWxPayApi (...) encapsulates the call to the single interface:

public Map<String, String> doWxPayApi(WxOrderEntity order,String apiType) {
    Map<String, String> resp = null;
    try {
        Map<String,String> map = new HashMap<>();
        map.put("out_trade_no", order.getOutTradeNo());
        map.put("nonce_str", order.getNonceStr());
        map.put("trade_type", order.getTradeType());

        if ("unifiedorder".equalsIgnoreCase(apiType)) {
            map.put("spbill_create_ip", order.getSpbillCreateIp());
            map.put("openid", order.getOpenid());
            map.put("notify_url", order.getNotifyUrl());
            map.put("total_fee", String.valueOf(order.getTotalFee()));
            map.put("body", order.getBody());

            resp = unifiedOrder(map);
        } else if ("orderquery".equalsIgnoreCase(apiType)) {
            resp = orderQuery(map);
        } else if ("closeorder".equalsIgnoreCase(apiType)) {
            resp = orderQuery(map);
        }
    } catch (Exception e) {
        WXPayUtil.getLogger().error(order.getOutTradeNo()+" -- 调用接口失败 {}",e.getMessage());
    }
    return resp;
}
复制代码

wxPay.doWxPayApi (...) encapsulates secondary verify the signature of:

public Map<String, Object> permissionValidate(String nonceStr, String url, String prepayId, String key) throws Exception {
    //jssdk权限验证参数
    TreeMap<Object, Object> param = new TreeMap<>();
    Map<String, Object> data = new HashMap<>();
    param.put("appId", WeChatConstants.APP_ID);
    String timestamp = String.valueOf(WXPayUtil.getCurrentTimestamp());
    param.put("timestamp", timestamp);//全小写
    param.put("nonceStr", nonceStr);
    //map.put("signature",WeChatUtil.getSignature(timestamp,uuid,RequestUtil.getUrl(request)));
    param.put("signature", WeChatUtil.getSignature(timestamp, nonceStr, url));
    data.put("configMap", param);

    //微信支付权限验证参数
    Map<String, String> payMap = new HashMap<>();
    payMap.put("appId", WeChatConstants.APP_ID);
    payMap.put("timeStamp", timestamp);//驼峰
    payMap.put("nonceStr", nonceStr);
    payMap.put("package", "prepay_id=" + prepayId);
    payMap.put("signType", "MD5");
    payMap.put("paySign", WXPayUtil.generateSignature(payMap, key));
    payMap.put("packageStr", "prepay_id=" + prepayId);
    data.put("payMap", payMap);

    return data;
}
复制代码

Payment result notification and callback

Configure the controller callback interface:

@Controller
@RequestMapping("/api/v1/wechat1")
public class NotifyController {

    WxBackendServiceImpl wxBackendService;

    /**
     * 在调用下单接口时,我们会传入 异步接收微信支付结果通知的回调地址,顾名思义这个地址作用就是用来接收支付结果通知,
     * 当用户在前端支付成功后,微信服务器会自动调用此地址,然后商户再进行处理
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("/wxnotify")
    public String wxNotify(HttpServletRequest request, HttpServletResponse response) {
        String respXml = "";
        try (InputStream in = request.getInputStream();
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = in.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            // 获取微信调用我们notify_url的返回信息
            String notifyData = new String(baos.toByteArray(), "utf-8");
            // 回调处理
            respXml = wxBackendService.payCallBack(notifyData);
        } catch (Exception e) {
            WXPayUtil.getLogger().error("wxnotify:支付回调发布异常:", e.getMessage());
        } finally {
            try (BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())){
                // 处理业务完毕
                bos.write(respXml.getBytes());
            } catch (IOException e) {
                WXPayUtil.getLogger().error("wxnotify:支付回调发布异常:out:", e.getMessage());
            }
        }
        return respXml;
    }
}
复制代码

Callback Business:

public String payCallBack(String notifyData) throws Exception{
    // String respXml = WXPayConstants.RESP_FAIL_XML;
    Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyData);

    if (WXPayConstants.SUCCESS.equalsIgnoreCase(notifyMap.get(WXPayConstants.RESULT_CODE))) {
        WXPayUtil.getLogger().info("payCallBack:微信支付----返回成功");
        if (WXPayUtil.isSignatureValid(notifyMap, WXPayConstants.API_KEY)) {
            // TODO 数据库操作,付款记录修改 & 记录付款日志
            WXPayUtil.getLogger().info("payCallBack:微信支付----验证签名成功,更新数据库");
            /*String outTradeNo = notifyMap.get("out_trade_no");
            OrderTrading dbOrder = transactionService.findByOutTradeNo(outTradeNo);
            // 将未支付状态改为已支付
            if (dbOrder != null && dbOrder.getState() == 1) {
                // 处理业务 - 修改订单状态
                OrderTrading order = new OrderTrading();
                order.setOutTradeNo(outTradeNo);
                order.setNotifyTime(new Date());
                order.setState(1);

                transactionService.updateTransOrderByWxnotify(order);
                // TODO 数据库更新异常,补偿措施
            }*/
            // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
            return WXPayConstants.RESP_SUCCESS_XML;
        } else {
            WXPayUtil.getLogger().error("payCallBack:微信支付----判断签名错误");
        }
    } else {
        WXPayUtil.getLogger().error("payCallBack:支付失败,错误信息:" + notifyMap.get(WXPayConstants.ERR_CODE_DES));
    }
    return WXPayConstants.RESP_FAIL_XML;
}
复制代码

Static pages

Pre-order page: templates / preOrder.html

Single page at confirmation: templates / toOrder.html

This page is for signature validation and parameter passing for ease of viewing opened debug mode

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>立即支付:123</h1>

<button type="submit" id="payBtn">支付</button>

<script th:src="@{/static/js/jquery-1.8.3.min.js}" type="text/javascript" charset="utf-8" rel="stylesheet"></script>
<script type="text/javascript" th:src="@{/static/js/jquery.rotate.min.js}" rel="stylesheet"></script>
<!--微信的JSSDK-->
<script th:src="@{http://res.wx.qq.com/open/js/jweixin-1.2.0.js}"></script>
<script>
    $(function() {
        <!--通过config接口注入权限验证配置-->
        alert('[[${configMap}]]');
        alert('[[${payMap}]]');
        wx.config({
            debug: true, // 开启调试模式
            appId: '[[${configMap.appId}]]', // 公众号的唯一标识
            timestamp: '[[${configMap.timestamp}]]', // 生成签名的时间戳
            nonceStr: '[[${configMap.nonceStr}]]', // 生成签名的随机串
            signature: '[[${configMap.signature}]]',// 签名
            jsApiList: ['chooseWXPay'] // 填入需要使用的JS接口列表,这里是先声明我们要用到支付的JS接口
        });

        <!-- config验证成功后会调用ready中的代码 -->
        wx.ready(function(){
            //点击马上付款按钮
            $("#payBtn").click(function(){              
                //弹出支付窗口
                wx.chooseWXPay({
                    timestamp: '[[${payMap.timeStamp}]]', // 支付签名时间戳,
                    nonceStr: '[[${payMap.nonceStr}]]', // 支付签名随机串,不长于 32 位
                    package: '[[${payMap.packageStr}]]', // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=xxxx)
                    signType: '[[${payMap.signType}]]', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
                    paySign: '[[${payMap.paySign}]]', // 支付签名
                    success: function (res) {
                        // 支付成功后的回调函数
                        alert("支付成功!");
                    }
                });
            })
        });
    });
</script>
</body>
</html>
复制代码

Demonstration effect

When the project started, click OK to see if you can pay debug mode parameter shows. The final results shown in Figure payment:

Note:

  1. Port 80 must be paid callback, it should be for security reasons
  2. web development tool can only be used for debugging, testing payment function, you need to open the phone.
  3. Careful friends may see orders of more than a month earlier. This is my domain name before using company accounts and development, was shot with.

Guess you like

Origin juejin.im/post/5d6297c95188255d51426d50