对接微信支付全流程讲解

背景:

随着最近小程序的流行,很多人开始使用小程序唤起微信支付本文以小程序对接全流程进行讲解。

环境:

本文采用java1.8 ,maven 搭建项目,springboot 2.5.15
利用微信最新的sdk "微信支付 APIv3 Java SDK"
目前的sdk版本

<dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java</artifactId>
            <version>0.2.12</version>
 </dependency>

概念

merchantId - 商户id 只要有心就可以看到的 ;位置:账户中心->商户信息->微信支付商户号
privateKeyPath - 商户API私钥路径 一会讲解怎么生成
merchantSerialNumber - 这个在证书的生成之后才会有;位置:账户中心->API安全->API证书管理
apiV3Key - 如果使用v3的话需要设置 秘钥
appId - 就是你要绑定的按个小程序或者公众号等(据说不同主体商户号和appid绑定会有问题,但是本次介入的时候直接绑定了)。;位置: 产品中心->AppID账号管理

开发准备

地址:下载并配置商户证书 - 通用规则 | 微信支付商户文档中心 (qq.com)

注意事项:

  1. 第一登录话需要设置api操作密码
  2. 建议先下载并配置商户号 具体按照流程操作就行了
  3. 配置API key ,建议v3 ,记录APIv3 秘钥
  4. 需要再 商户平台上绑定appid,绑定之后 在appid的管理平台进行授权,再次查看商户平台是否绑定成功。

开发

  1. 由于调用平次较多,所有利用spring容器,将类进行容器管理
  2. 准备配置
@Slf4j
@Service
public class JsapiInitService {
    @Value("${pay.wxmin.merchantId}")
    public String merchantId  ;
    /**
     * 商户API私钥路径
     */
    @Value("${pay.wxmin.privateKeyPath}")
    public String privateKeyPath  ;
    /**
     * 商户证书序列号
     */

    @Value("${pay.wxmin.merchantSerialNumber}")
    public String merchantSerialNumber ;
    /**
     * 商户APIV3密钥
     */
    @Value("${pay.wxmin.apiV3Key}")
    public String apiV3Key ;
// 该方法返回 小程序唤起支付的所需信息,也有只返回预下单单号,但是小程序生成所需信息需要商户的私钥,这样不利于安全管理
   public JsapiServiceExtension getJsapiServiceExtension(){
       Config  config = new RSAAutoCertificateConfig.Builder()
               .merchantId(merchantId)
               .privateKeyFromPath(privateKeyPath)
               .merchantSerialNumber(merchantSerialNumber)
               .apiV3Key(apiV3Key)
               .build();
      return  new JsapiServiceExtension.Builder().config(config).build();
   }

//退款的请求
   public RefundService getRefundService() {
       Config config =
               null;

           config = new RSAAutoCertificateConfig.Builder()
                   .merchantId(merchantId)
                   .privateKeyFromPath(privateKeyPath)
                   .merchantSerialNumber(merchantSerialNumber)
                   .apiV3Key(apiV3Key)
                   .build();

       // 构建service
       return new RefundService.Builder().config(config).build();
   }
// 回调解密处理
   public NotificationParser getNotificationParser(){
       NotificationConfig config = null;

           config = new RSAAutoCertificateConfig.Builder()
                   .merchantId(merchantId)
                   .privateKeyFromPath(privateKeyPath)
                   .merchantSerialNumber(merchantSerialNumber)
                   .apiV3Key(apiV3Key)
                   .build();

       // 初始化 NotificationParser
       return new NotificationParser(config);
   }
}
  1. 发起支付
    小程序下单
 String payOrderId = orderId+ System.currentTimeMillis();
        payOrderId = payOrderId.substring(0, 32);
     
        PrepayRequest request = new PrepayRequest();
        Amount amount = new Amount();
        amount.setTotal(amount);
        request.setAmount(amount);
        request.setAppid(appId);
        request.setMchid(merchantId);
        request.setDescription("测试商品标题");
        request.setNotifyUrl(notifyUrl);
        request.setOutTradeNo(payOrderId);
        Payer payer = new Payer();
        payer.setOpenid(sysUser.getOpenid());
        request.setPayer(payer);
        try {
            PrepayWithRequestPaymentResponse prepayWithRequestPaymentResponse = jsapiInitService.getJsapiServiceExtension().prepayWithRequestPayment(request);
            JSONObject result = new JSONObject();
            result.put("appId", prepayWithRequestPaymentResponse.getAppId());
            result.put("timeStamp", prepayWithRequestPaymentResponse.getTimeStamp());
            result.put("nonceStr", prepayWithRequestPaymentResponse.getNonceStr());
            result.put("package", prepayWithRequestPaymentResponse.getPackageVal());
            result.put("signType", prepayWithRequestPaymentResponse.getSignType());
            result.put("paySign", prepayWithRequestPaymentResponse.getPaySign());
         
            return AjaxResult.success(result);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("微信支付预下单失败,message:" + e.getMessage());
        }
  1. 退款
    注意: 本次返回状态 不出意外为:PROCESSING
    退款申请
   CreateRequest request = new CreateRequest();
        request.setOutTradeNo(bdRefundRecord.getPayNo());
        request.setOutRefundNo(bdRefundRecord.getRefundNo());
     
        AmountReq amountReq = new AmountReq();
        amountReq.setRefund(amount);
        amountReq.setCurrency("CNY");
        amountReq.setTotal(amount);
        request.setAmount(amountReq);
        request.setNotifyUrl(refundNotifyUrl);
        log.info("发起微信退款详细参数:{}", request);
        Refund refund = refundService.create(request);
        if (Objects.equals(refund.getStatus().name(), "PROCESSING")) {
        }
  1. 支付退款回调
    支付通知
    回调需要读取header中数据,如果controller 接受request
private String getRequestBody(HttpServletRequest request) {
        StringBuffer sb = new StringBuffer();
        try {
            ServletInputStream inputStream = request.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String line = "";
            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }
       String wechatSignature = request.getHeader("Wechatpay-Signature");
        String wechatPaySerial = request.getHeader("Wechatpay-Serial");
        String wechatpayNonce = request.getHeader("Wechatpay-Nonce");
        String wechatTimestamp = request.getHeader("Wechatpay-Timestamp");
        String wechatpaySignatureType = request.getHeader("Wechatpay-Signature-Type");
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(wechatPaySerial)
                .nonce(wechatpayNonce)
                .signature(wechatSignature)
                .timestamp(wechatTimestamp)
                .body(getRequestBody(request))
                .build(); 
            // 以支付通知回调为例,验签、解密并转换成 Transaction 
            Transaction transaction = jsapiInitService.getNotificationParser().parse(requestParam, Transaction.class);
 // 以退款通知回调为例,验签、解密并转换成 Transaction 
             RefundNotification transaction = jsapiInitService.getNotificationParser().parse(requestParam, RefundNotification.class);;
  1. 附带工具
    对时间处理
  DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX");
                    ZonedDateTime dateTime = ZonedDateTime.parse(transaction.getSuccessTime(), formatter);
         Date date =  Date.from(dateTime.toInstant());

总结

  1. 本次开发需要前后端进行配合完成, 下单环境可以提前完成,交给小程序,但是唤起支付需要小程序的配合,这时候可以完成 发起之后和回调的开发。 这么说因为当唤起支付之后,其他的任务也就一并而来,导致自己工期不足。
  2. 本次开发也是参考了很多,找了很多资料,但大多数意义不大,这次从开发角度来说 应该是全的。不全不理解我也没有办法,除非我给你写
  3. 没了
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354