Java实现微信native支付
Java实现微信native支付
1、介绍
Native支付是指商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。
详细介绍以及实现流程可参考官方文档:微信native支付官方指引文档,本文主要为编码者讲解具体实现细节。
2、业务流程
根据业务流程图可以得知,编码主要有两部分:
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;@Beforepublic 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"));// 初始化httpClienthttpClient = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey).withValidator(new WechatPay2Validator(verifier)).build();}@Afterpublic 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) { //处理成功,无返回BodySystem.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; // 应用idprivate String mchid; // 商户idprivate 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 {@Autowiredprivate 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密钥@Overridepublic 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) { //处理成功,无返回BodySystem.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封装的原理以及具体使用方式留在下篇文章讲解。