【微信支付】微信小程序支付-V3接口(一)

一.微信小程序接入微信支付流程图:

重点步骤说明:

步骤4:用户下单发起支付,商户可通过[JSAPI下单](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml)创建支付订单。

步骤9:商户小程序内使用[小程序调起支付API](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml)(wx.requestPayment)发起微信支付,详见小程序API文档

步骤16:用户支付成功后,商户可接收到微信支付支付结果通知[支付通知API](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_5.shtml)。

步骤21:商户在没有接收到微信支付结果通知的情况下需要主动调用[查询订单API](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_2.shtml)查询支付结果。

二.准备工作:

官方文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml

1. 小程序开通微信支付

首先需要在微信支付的官网注册一个商户;

在管理页面中申请关联小程序,通过小程序的 appid 进行关联;

进入微信公众平台,功能-微信支付中确认关联(如果服务商和小程序的注册主体不一样,还要经过微信的审核)

 2. 获取各种证书、密钥文件-基于微信支付 V3 的版本:

商户 id:这个可以在小程序微信公众平台-功能-微信支付 页面中的已关联商户号中得到

商户密钥:这个需要在微信支付的管理后台中申请获取

证书编号: 同样在微信支付的管理后台中申请证书,申请证书后就会看到证书编号

证书私钥:上一步申请证书的时候同时也会获取到证书的公钥、私钥文件。开发中只需要私钥文件

三.后端代码


> 1.引入微信支付官方的pom依赖:

<dependency>

<groupId>com.github.wechatpay-apiv3</groupId>

<artifactId>wechatpay-apache-httpclient</artifactId>

<version>0.4.9</version>

</dependency>

> 2.微信支付配置信息:

  wx-pay:

  v3:

    #微信关联的小程序的appid

    appId: xxxxx

    #微信支付商户号

    mchId: xxxxxx

    #微信支付证书序列号

    serialnumber: xxxxxx

    #微信支付apiv3的密钥

    apiKey3: xxxxx

    #微信支付证书

    certKeyPath: /cert/xxxxx.pem

    #微信支付回调商户线上地址api

    notifyUrl: https://xxxxx/pay/callback

    #微信支付关闭订单api

    closeOrderUrl: https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}/close

    #微信支付小程序预下单api

    jsApiUrl: https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi

    #微信支付订单查询api

    queryOrder: https://api.mch.weixin.qq.com/v3/pay/transactions/id/


@Data

@Component

@ConfigurationProperties(prefix="wx-pay.v3")

public class WxPayConfig {

    //appId

    private String appId;

    //商户id

    private String mchId;

    //证书序列号

    private String serialnumber;

    //商户秘钥

    private String apiKey3;

    //商户的key【API密匙】存放路径

    private String certKeyPath;

    //回调通知地址

    private String notifyUrl;

    //jsapi预支付url

    private String jsApiUrl;

    //关闭订单接口

    private String closeOrderUrl;

    //查询订单地址

    private String queryOrder;

}


 3.初始化微信支付的参数


@Service

@Transactional

public class SysPayServiceImpl implements SysPayService {

private static final Logger log = LoggerFactory.getLogger(SysPayServiceImpl.class);

    @Autowired

    private WxPayConfig wxPayConfig;

    //微信专业httpClient

    private static CloseableHttpClient httpClient;

    //生成签名用

    private static Sign sign;

    //证书管理器

    private static CertificatesManager certificatesManager;

    @Autowired

    private SysOrderDao sysOrderDao;

//初始化微信支付的参数

    @PostConstruct

    public void init() throws Exception {

        //获取商户私钥

        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(getFileInputStream(wxPayConfig.getCertKeyPath()));

        // 获取证书管理器实例

        certificatesManager = CertificatesManager.getInstance();

        sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, merchantPrivateKey.getEncoded(), null);

        // 向证书管理器增加需要自动更新平台证书的商户信息

        certificatesManager.putMerchant(wxPayConfig.getMchId(), new WechatPay2Credentials(wxPayConfig.getMchId(),

                new PrivateKeySigner(wxPayConfig.getSerialnumber(), merchantPrivateKey)), wxPayConfig.getApiKey3().getBytes(StandardCharsets.UTF_8));

        // 从证书管理器中获取verifier

        Verifier verifier = certificatesManager.getVerifier(wxPayConfig.getMchId());

        //用于构造HttpClient

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()

                .withMerchant(wxPayConfig.getMchId(), wxPayConfig.getSerialnumber(), merchantPrivateKey)

                .withValidator(new WechatPay2Validator(verifier));

        httpClient = builder.build();

    }

/**

    * 使用getResourceAsStream直接从resources根路径下获取文件流

    * @param path

    */

    private InputStream getFileInputStream(String path) {

        InputStream in = this.getClass().getResourceAsStream(path);

        return in;

    }

}

 4.预订单生成返回给小程序请求参数


//预订单生成返回给小程序请求参数

    @Override

    public AjaxResult prePay(WxPayDto dto) {

        log.info("微信小程序支付JSAPI预下单开始------");

        AjaxResult ajaxResult = AjaxResult.success();

        JSONObject obj = new JSONObject();

        //应用ID

        obj.put("appid", wxPayConfig.getAppId());

        //直连商户号

        obj.put("mchid", wxPayConfig.getMchId());

        //商品描述

        obj.put("description", dto.getDescription());

        //商户订单号

        obj.put("out_trade_no", dto.getOrderCode());

        //通知地址

        obj.put("notify_url", wxPayConfig.getNotifyUrl());

        //附加数据 查询API和支付通知中原样返回,可作为自定义参数使用,实际情况下只有支付完成状态才会返回该字段。

        Map<String, String> attach = new HashMap<>();

        attach.put("orderCode", dto.getOrderCode());

        obj.put("attach", JSON.toJSONString(attach));

        //订单金额

        JSONObject amount = new JSONObject();

        amount.put("total", dto.getPrice());

        obj.put("amount", amount);

        //支付者

        JSONObject payer = new JSONObject();

        payer.put("openid", dto.getOpenId());

        obj.put("payer", payer);

        log.info("微信小程序支付JSAPI预下单请求参数:{}" + obj.toJSONString());

        HttpPost httpPost = new HttpPost(wxPayConfig.getJsApiUrl());

        httpPost.addHeader("Accept", "application/json");

        httpPost.addHeader("Content-type", "application/json; charset=utf-8");

        httpPost.setEntity(new StringEntity(obj.toJSONString(), "UTF-8"));

        String bodyAsString ;

        try {

            if(httpClient == null){

                log.info("微信小程序支付JSAPI预下单请求失败");

                return AjaxResult.error("预下单失败,请重试,无法连接微信支付服务器!" );

            }

            //执行请求

            CloseableHttpResponse response = httpClient.execute(httpPost);

            bodyAsString = EntityUtils.toString(response.getEntity());

        } catch (IOException e) {

            log.info("微信小程序支付JSAPI预下单请求失败{}", e.getMessage());

            return AjaxResult.error("预下单失败,请重试!" + e.getMessage());

        }

        String prePayId = JSONObject.parseObject(bodyAsString).getString("prepay_id");

        if (prePayId == null){

            String message = JSONObject.parseObject(bodyAsString).getString("message");

            log.info("微信小程序支付JSAPI预下单请求失败{}", message);

            return AjaxResult.error("预下单失败,请重试!" + message);

        }

        //准备小程序端的请求参数

        ajaxResult.put("appId", wxPayConfig.getAppId());

        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);

        ajaxResult.put("timeStamp", timeStamp);

        String nonceStr = IdUtil.fastSimpleUUID();

        ajaxResult.put("nonceStr", nonceStr);

        String packageStr = "prepay_id=" + prePayId;

        ajaxResult.put("package", packageStr);

        ajaxResult.put("signType", "RSA");

        String signString =  wxPayConfig.getAppId() + "\n" + timeStamp + "\n" + nonceStr + "\n" + packageStr + "\n";

        ajaxResult.put("paySign", Base64.encode(sign.sign(signString.getBytes())));

        return ajaxResult;

    }

> 5.微信支付回调通知

    //微信支付回调通知

    @Override

    public void callback(HttpServletRequest servletRequest, HttpServletResponse response) {

        log.info("----------->微信支付回调开始");

        Map<String, String> map = new HashMap<>(12);

        String timeStamp = servletRequest.getHeader("Wechatpay-Timestamp");

        String nonce = servletRequest.getHeader("Wechatpay-Nonce");

        String signature = servletRequest.getHeader("Wechatpay-Signature");

        String certSn = servletRequest.getHeader("Wechatpay-Serial");

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(servletRequest.getInputStream()))) {

            StringBuilder stringBuilder = new StringBuilder();

            String line;

            while ((line = reader.readLine()) != null) {

                stringBuilder.append(line);

            }

            String obj = stringBuilder.toString();

            log.info("微信支付回调请求参数:{},{},{},{},{}", obj, timeStamp, nonce, signature, certSn);

            Verifier verifier = certificatesManager.getVerifier(wxPayConfig.getMchId());

            String sn = verifier.getValidCertificate().getSerialNumber().toString(16).toUpperCase(Locale.ROOT);

            NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(sn)

                    .withNonce(nonce)

                    .withTimestamp(timeStamp)

                    .withSignature(signature)

                    .withBody(obj)

                    .build();

            NotificationHandler handler = new NotificationHandler(verifier, wxPayConfig.getApiKey3().getBytes(StandardCharsets.UTF_8));

            // 验签和解析请求体

            Notification notification = handler.parse(request);

            JSONObject res = JSON.parseObject(notification.getDecryptData());

            log.info("微信支付回调响应参数:{}", res.toJSONString());

            //做一些操作

            if (res != null) {

                //如果支付成功

                String tradeState = res.getString("trade_state");

                if("SUCCESS".equals(tradeState)){

                    //拿到商户订单号

                    String outTradeNo = res.getString("out_trade_no");

                    JSONObject amountJson = res.getJSONObject("amount");

                    Integer payerTotal = amountJson.getInteger("payer_total");

                    SysOrder sysOrder = sysOrderDao.queryByOrderCode(outTradeNo);

                    if(sysOrder != null){

                        if(sysOrder.getPayStatus() == 1){

                            //如果支付状态为1 说明订单已经支付成功了,直接响应微信服务器返回成功

                            response.setStatus(200);

                            map.put("code", "SUCCESS");

                            map.put("message", "SUCCESS");

                            response.setHeader("Content-type", ContentType.JSON.toString());

                            response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));

                            response.flushBuffer();

                        }

                        //验证用户支付的金额和订单金额是否一致

                        if(payerTotal.equals(sysOrder.getOrderPrice())){

                            //修改订单状态

                            String successTime = res.getString("success_time");

                            sysOrder.setPayStatus(1);

                            sysOrderDao.update(sysOrder);

                            //响应微信服务器

                            response.setStatus(200);

                            map.put("code", "SUCCESS");

                            map.put("message", "SUCCESS");

                            response.setHeader("Content-type", ContentType.JSON.toString());

                            response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));

                            response.flushBuffer();

                        }

                    }

                }

            }

            response.setStatus(500);

            map.put("code", "ERROR");

            map.put("message", "签名错误");

            response.setHeader("Content-type", ContentType.JSON.toString());

            response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));

            response.flushBuffer();

        } catch (Exception e) {

            log.error("微信支付回调失败", e);

        }

    }

> 6.当用户10分钟内未支付,系统自动关闭微信支付订单并删除

//当用户10分钟内未支付,系统自动关闭微信支付订单并删除

    @Override

    public void deleteClosePayOrder(String orderCode) {

        JSONObject obj = new JSONObject();

        //直连商户号

        obj.put("mchid", wxPayConfig.getMchId());

        String ret2 = wxPayConfig.getCloseOrderUrl().replace("{out_trade_no}", orderCode);

        log.info("关闭和删除微信订单--请求地址{}" , ret2);

        log.info("关闭和删除微信订单--请求参数{}" , obj.toJSONString());

        HttpPost httpPost = new HttpPost(ret2);

        httpPost.addHeader("Accept", "application/json");

        httpPost.addHeader("Content-type", "application/json; charset=utf-8");

        httpPost.setEntity(new StringEntity(obj.toJSONString(), "UTF-8"));

        String bodyAsString ;

        try {

            if(httpClient == null){

                log.info("关闭和删除微信订单--关闭订单失败,请重试,无法连接微信支付服务器!");

            }

            //执行请求

            CloseableHttpResponse response = httpClient.execute(httpPost);

            //状态码

            int statusCode = response.getStatusLine().getStatusCode();

            if (statusCode == 204) {

                //关闭订单成功!

                //删除订单

                log.info("关闭和删除微信订单--关闭订单成功orderCode:{}!", orderCode);

                sysOrderDao.deleteByOrderCode(orderCode);

            }else if(statusCode == 202){

                //用户支付中,需要输入密码

                log.info("关闭和删除微信订单--用户支付中,需要输入密码,暂时不做处理!");

            }else{

                log.info("关闭和删除微信订单--关闭支付订单失败,出现未知原因{}", EntityUtils.toString(response.getEntity()));

            }

        } catch (IOException e) {

            log.info("关闭和删除微信订单--关闭订单失败{}", e.getMessage());

        }

    }

> 7.查询订单支付结果接口

//查询订单支付结果

    @Override

    public AjaxResult queryPayResult(String payId){

        //请求URL

        URIBuilder uriBuilder = null;

        try {

            uriBuilder = new URIBuilder(wxPayConfig.getQueryOrder() + payId);

            uriBuilder.setParameter("mchid", wxPayConfig.getMchId());

            if(httpClient == null){

                return AjaxResult.error("查询订单失败,请重试,无法连接微信支付服务器!" );

            }

            //完成签名并执行请求

            HttpGet httpGet = new HttpGet(uriBuilder.build());

            httpGet.addHeader("Accept", "application/json");

            CloseableHttpResponse response = httpClient.execute(httpGet);

            int statusCode = response.getStatusLine().getStatusCode();

            if (statusCode == 200 || statusCode == 204) {

                String ss = EntityUtils.toString(response.getEntity());

                if(StringUtils.isNotEmpty(ss)){

                    JSONObject res = JSON.parseObject(ss);

                    log.info("success,return body = " + ss);

                    //拿到商户订单号

                    String outTradeNo = res.getString("out_trade_no");

                    JSONObject amountJson = res.getJSONObject("amount");

                    Integer payerTotal = amountJson.getInteger("payer_total");

                    SysOrder sysOrder = sysOrderDao.queryByOrderCode(outTradeNo);

                    if(sysOrder != null){

                        if(sysOrder.getPayStatus() == 1){

                            //如果支付状态为1 说明订单已经支付成功了,直接响应成功

                            return AjaxResult.success("查询支付成功!" );

                        }

                        //验证用户支付的金额和订单金额是否一致

                        if(payerTotal.equals(sysOrder.getOrderPrice())){

                            //修改订单状态

                            String successTime = res.getString("success_time");

                            sysOrder.setPayStatus(1);

                            sysOrderDao.update(sysOrder);

                            return AjaxResult.success("查询支付成功!");

                        }

                    }

                }

            }

            return AjaxResult.error("查询支付失败:" + "failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));

        } catch (Exception e) {

            return AjaxResult.error("查询支付失败:" + e.getMessage());

        }

    }

### 四.前端小程序代码

>微信小程序支付需要用到官方的支付API:wx.requestPayment(Object object)

>官方文档地址:https://developers.weixin.qq.com/miniprogram/dev/api/payment/wx.requestPayment.html

wx.requestPayment({

            timeStamp: 'xxx',  // 时间戳,从 1970 年 1 月 1 日 00:00:00 至今的秒数,即当前的时间

            nonceStr: 'xxxxxx',  // 随机字符串,长度为32个字符以下

            package: 'xxxxxxxxxxxx',  // 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=***

            signType: 'xxx',  // 签名算法,应与后台下单时的值一致

            paySign: 'xxxxxxxxxx', // 签名,具体见微信支付文档

            success (res) { // 成功的回调

                wx.showToast({title: '付款成功'})

            },

            fail (res) { // 失败的回调

                wx.showToast({title: '支付失败',icon: 'none'})

            }

          })

})

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容