Java实现微信native支付

Java实现微信native支付

1、介绍

Native支付是指商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。
详细介绍以及实现流程可参考官方文档:微信native支付官方指引文档,本文主要为编码者讲解具体实现细节。

2、业务流程

微信native支付的业务流程图
根据业务流程图可以得知,编码主要有两部分:
1. 调用统一下单API,将返回的预支付交易链接(code_url)生成二维码图片,供用户扫描;
2. 调用查询订单API,获取订单的支付状态;

3、实现逻辑

先说第一部分:调用统一下单API,将返回的预支付交易链接(code_url)生成二维码图片,供用户扫描
1、导入依赖

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.4.9</version>
</dependency>

2、调用统一下单API

public class NativePayCreateOrder {

    // 一些参数,公司提供的,通常定义在项目的配置文件中,以下皆是模拟数据
    private appId = "wx8790234sau98232ehb2"; // 应用号
    private String mchId = "1571814339"; // 商户号
    private String privateKey = ""; // 私钥字符串,此处省略
    private String mchSerialNo = "25FS78SGSGFGG7879SGS987GS675AT6"; // 商户证书序列号
    private String apiV3Key = "CZBK12Y675AHIGA97987987957AD"; // V3密钥

    // 定义httpClient对象
    private CloseableHttpClient httpClient;

    @Before
    public void setup() throws IOException {
        // 加载商户私钥(privateKey:私钥字符串)
        PrivateKey merchantPrivateKey = PemUtil
                .loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
 
        // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo,     merchantPrivateKey)),apiV3Key.getBytes("utf-8"));
 
        // 初始化httpClient
        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier)).build();
    }
 
    @After
    public void after() throws IOException {
        httpClient.close();
    }
上述代码是初始化httpClient对象(就是将商户ID、商户序列号、商户私钥、
平台证书设置到httpClient对象中),
httpClient对象是用来发送请求的,向微信支付系统发送下单请求。

    public void CreateOrder() throws Exception{
        // 定义Post类型的http请求(参数是请求的地址,固定不变的,因为请求的是微信支付系统的地址)
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
        // 定义请求body参数(后面要修改,其包含应用ID、商户号、订单金额等信息)
        String reqdata = "{"
              + "\"time_expire\":\"2018-06-08T10:34:56+08:00\","
              + "\"amount\": {"
              + "\"total\":100,"
              + "\"currency\":\"CNY\""
              + "},"
              + "\"mchid\":\"1230000109\","
              + "\"description\":\"Image形象店-深圳腾大-QQ公仔\","
              + "\"notify_url\":\"https://www.weixin.qq.com/wxpay/pay.php\","
              + "\"out_trade_no\":\"1217752501201407033233368018\","
              + "\"goods_tag\":\"WXG\","
              + "\"appid\":\"wxd678efh567hg6787\","
              + "\"attach\":\"自定义数据说明\","
              + "\"detail\": {"
              + "\"invoice_id\":\"wx123\","
              + "\"goods_detail\": ["
              + "{"
              + "\"goods_name\":\"iPhoneX 256G\","
              + "\"wechatpay_goods_id\":\"1001\","
              + "\"quantity\":1,"
              + "\"merchant_goods_id\":\"商品编码\","
              + "\"unit_price\":828800"
              + "},"
              + "{"
              + "\"goods_name\":\"iPhoneX 256G\","
              + "\"wechatpay_goods_id\":\"1001\","
              + "\"quantity\":1,"
              + "\"merchant_goods_id\":\"商品编码\","
              + "\"unit_price\":828800"
              + "}"
              + "],"
              + "\"cost_price\":608800"
              + "},"
              + "\"scene_info\": {"
              + "\"store_info\": {"
              + "\"address\":\"广东省深圳市南山区科技中一道10000号\","
              + "\"area_code\":\"440305\","
              + "\"name\":\"腾讯大厦分店\","
              + "\"id\":\"0001\""
              + "},"
              + "\"device_id\":\"013467007045764\","
              + "\"payer_client_ip\":\"14.23.150.211\""
              + "}"
              + "}";
          StringEntity entity = new StringEntity(reqdata,"utf-8");
          entity.setContentType("application/json");
          httpPost.setEntity(entity);
          httpPost.setHeader("Accept", "application/json");

          //完成签名并执行请求(调用下单API)
          CloseableHttpResponse response = httpClient.execute(httpPost);

          try {
              int statusCode = response.getStatusLine().getStatusCode();
              if (statusCode == 200) { //处理成功
                  System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
                  // 将上面的返回结果code_url返回(return)给前端,前端根据这个code_url生成二维码图片
              } else if (statusCode == 204) { //处理成功,无返回Body
                  System.out.println("success");
              } else {
                  System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
                  throw new IOException("request failed");
              }
          } finally {
              response.close();
          }
    }

}

3.3、定义订单参数类

import lombok.Builder;
import lombok.Data;

@Builder // 此注解的作用是可以通过builder()方法链式创建对象
@Data
public class NativePayParams {
    private String appid; // 应用id
    private String mchid; // 商户id
    private String description; // 商品描述
    private String out_trade_no; // 订单号
    private String notify_url; // 支付成功回调通知地址
    private Amount amount; // 订单金额信息
}

定义订单金额类

import lombok.Builder;
import lombok.Data;

@Builder
@Data
public class Amount {
    private Integer total; // 金额,单位为分
    private String currency; // 货币类型,目前仅支持人民币
}

3.4、修改第二步中httpPost请求的body参数

// 定义请求body参数
Amount amount = Amount.builder()
        .currency("CNY")
        .total(1)
        .build();
NativePayParams payParams = NativePayParams.builder()
        .appid(appId)
        .mchid(mchId)
        .description("商品描述,如VIP特权")
        .out_trade_no("ALJFKL7987987U98KJK")
        .notify_url("https://项目的域名/native/notify") // 支付成功后通知的回调地址
        .amount(amount)
        .build();	
String reqdata = JSON.toJSONString(payParams);

接下来,就是获取订单的支付状态,官网给了两种方案:一种是用户支付成功后,微信支付系统会将支付成功的结果以回调通知的形式同步给商户,商户的回调地址需要在调用Native下单API时传入notify_url参数;另一种获取订单支付状态的方式是当因网络抖动或本身notify_url存在问题等原因,导致无法接收到回调通知时,商户也可主动调用微信支付系统的查询订单API来获取订单状态

先说第一种方式的实现逻辑:用户支付成功后,微信支付系统会将支付成功的结果以回调通知的形式同步给商户,商户的回调地址需要在调用Native下单API时传入notify_url参数
1、编写接口,接收微信的支付成功通知
编写通知数据实体类

@Data
public class ResourceDto {
    private String algorithm;
    private String ciphertext;
    private String associated_data;
    private String original_type;
    private String nonce;
}

编写支付通知参数实体类

@Data
public class NotifyDto {
    private String id;
    private String create_time;
    private String event_type;
    private String resource_type;
    private ResourceDto resource;
    private String summary;
}

编写controller层接口

@RestController
@RequestMapping("/native")
public class NativePayController {
    
    @Autowired
    private NativePayService nativePayService;
    
    @PostMapping("/notify")
    public Map<String, String> payNotify(@RequestBody NotifyDto dto){
        // 解密支付通知数据(调用业务逻辑层)
        return nativePayService.payNotify(dto);
    }
}

2、解密支付通知的内容
业务层接口

public interface NativePayService {
    Map<String, String> payNotify(NotifyDto dto);
}

业务层实现类

@Service
public class NativePayServiceImpl implements NativePayService {
    
    private String apiV3Key = "CZBK12Y675AHIGA97987987957AD"; // V3密钥
    
    @Override
    public Map<String, String> payNotify(NotifyDto dto) {
        Map<String,String> res = null;
        try {
            // 解密微信支付系统传过来的参数
            String json = new AesUtil(apiV3Key.getBytes()).decryptToString(dto.getResource().getAssociated_data().getBytes(),
                                                                           dto.getResource().getNonce().getBytes(),
                                                                           dto.getResource().getCiphertext());
            String outTradeNo = JSON.parseObject(json, Map.class).get("out_trade_no").toString();
            System.out.println("支付成功的订单号:" + outTradeNo);
        
        } catch (GeneralSecurityException e) {
            // 支付不成功
            e.printStackTrace();
            res.put("code","FAIL");
            res.put("message","失败");
        }
        return res; // 返回正确的信息微信支付系统才不会继续发送通知
    }
}

3、内网穿透(本地测试的IP不是公网IP,外部无法访问,需要域名穿透,才能让微信支付系统回调到本地项目的接口)
可以选择用花生壳这个软件工具来做内容穿透,其实就是一个域名映射,映射到本地IP与项目端口;在实际开发中不需要做内网穿透,因为公司有服务器域名。

4、微信主动通知的地址是通过下单接口中的请求参数notify_url来设置的,要求必须是https地址

接下来说主动查询订单支付状态这种方式,因为微信支付系统不能保证通知成功,所以在企业开发中,都还会用这种方式来获取订单支付状态。本质上就是写接口向微信支付系统发请求。

以查询商户订单号为例:(还可以查询支付订单号,实现逻辑同理,可模仿调用下单API的接口编写)
public void queryOrder() throws Exception{
          // 定义Get类型的http请求
          HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/ALJFKL7987987U98KJK?mchid=1571814339");

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

          //完成签名并执行请求(调用查询订单API)
          CloseableHttpResponse response = httpClient.execute(httpGet);

          try {
              int statusCode = response.getStatusLine().getStatusCode();
              if (statusCode == 200) { //处理成功
                  System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
                  // 上面的返回结果就是json格式的订单信息,包括支付状态
              } else if (statusCode == 204) { //处理成功,无返回Body
                  System.out.println("success");
              } else {
                  System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
                  throw new IOException("request failed");
              }
          } finally {
              response.close();
          }
}

最后还有个问题,就是什么时候去调用刚编写的查询订单状态的这个接口;创建订单1分钟之后调用,用户可能还没支付,创建订单10分钟后调用,用户可能早就支付了,所以要用轮询的方式来调用查询订单状态这个接口,具体实现就是用定时任务,不会用定时任务的可以去看我之前的文章。

到这里其实就已经说完了这个微信支付的实现。下面就是扩展知识了,说一下SpringBoot的starter封装。比如说我们这里做好了微信扫码支付的功能,其他项目也要用这个功能,就不需要再实现这个功能了,直接在项目里引入这个starter就行了。这个SpringBoot的starter封装的原理以及具体使用方式留在下篇文章讲解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值