700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > JAVA 实现微信Native扫码支付功能

JAVA 实现微信Native扫码支付功能

时间:2022-03-23 23:48:18

相关推荐

JAVA 实现微信Native扫码支付功能

目录

1、在pom.xml中添加依赖

2、在application.yml中添加如下配置

3、创建微信支付config配置类

4、微信支付下单

5、支付通知

6、主动查询订单状态

7、apiclient_key.pem(私钥文件)

1、在pom.xml中添加依赖

<!--微信支付SDK--><dependency><groupId>com.github.wechatpay-apiv3</groupId><artifactId>wechatpay-apache-httpclient</artifactId><version>0.3.0</version></dependency><!-- json处理器:引入gson依赖 --><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.6</version></dependency>

2、在application.yml中添加如下配置

# 微信支付相关参数wxpay:# 商户号mch-id: xxx1228xx# 商户API证书序列号mch-serial-no: xxx6xxx5414D07xxxxxxxxECBBD047AA6xxx# 商户私钥文件# 注意:该文件放在项目根目录下private-key-path: ./apiclient_key.pem# APIv3密钥api-v3-key: xxxxxxxycUJLDxxxZplQR683Mxxxxxx# APPIDappid: xxxxxxc27e0e7cxxx# 微信服务器地址domain: https://api.mch.# 接收结果通知地址# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置notify-domain: https://c7c1-240e-3b5-3015-be0-1bc-9bed-fca4-d09b.ngrok.io

3、创建微信支付config配置类

@Configuration@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点@Data //使用set方法将wxpay节点中的值填充到当前类的属性中@Slf4jpublic class WxPayConfig {// 商户号private String mchId;// 商户API证书序列号private String mchSerialNo;// 商户私钥文件private String privateKeyPath;// APIv3密钥private String apiV3Key;// APPIDprivate String appid;// 微信服务器地址private String domain;// 接收结果通知地址private String notifyDomain;/*** 获取商户的私钥文件** @param filename* @return*/private PrivateKey getPrivateKey(String filename) {try {return PemUtil.loadPrivateKey(new FileInputStream(filename));} catch (FileNotFoundException e) {throw new RuntimeException("私钥文件不存在", e);}}/*** 获取签名验证器** @return*/@Beanpublic ScheduledUpdateCertificatesVerifier getVerifier() {log.info("获取签名验证器");//获取商户私钥PrivateKey privateKey = getPrivateKey(privateKeyPath);//私钥签名对象PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);//身份认证对象WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);// 使用定时更新的签名验证器,不需要传入证书ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(wechatPay2Credentials,apiV3Key.getBytes(StandardCharsets.UTF_8));return verifier;}/*** 获取http请求对象** @param verifier* @return*/@Bean(name = "wxPayClient")public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) {log.info("获取httpClient");//获取商户私钥PrivateKey privateKey = getPrivateKey(privateKeyPath);WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, privateKey).withValidator(new WechatPay2Validator(verifier));// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新CloseableHttpClient httpClient = builder.build();return httpClient;}/*** 获取HttpClient,无需进行应答签名验证,跳过验签的流程*/@Bean(name = "wxPayNoSignClient")public CloseableHttpClient getWxPayNoSignClient() {//获取商户私钥PrivateKey privateKey = getPrivateKey(privateKeyPath);//用于构造HttpClientWechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()//设置商户信息.withMerchant(mchId, mchSerialNo, privateKey)//无需进行签名验证、通过withValidator((response) -> true)实现.withValidator((response) -> true);// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新CloseableHttpClient httpClient = builder.build();log.info("== getWxPayNoSignClient END ==");return httpClient;}}

4、微信支付下单

Controller:

/*** native下单** @param nativeReq* @return* @throws Exception*/@ApiOperation(value = "微信支付下单", notes = "微信支付下单")@PostMapping("/native")@Log(BusinessTypeEnum.OTHER)public Result<NativeRes> nativePay(@RequestBody NativeReq nativeReq) throws Exception {log.info("发起支付请求, params:{}", Json.toJsonString(nativeReq));UxmOrder uxmOrder = orderService.createOrderByProductId(nativeReq, PayType.WXPAY);if (nativeReq.getPrice().compareTo(uxmOrder.getAmount()) != 0) {throw new ServiceException(ResultCode.ORDER_AMOUNT_NOT_FIT.getMessage(), ResultCode.ORDER_AMOUNT_NOT_FIT.getCode());}//生成订单orderService.save(uxmOrder);//调用统一下单api(返回支付二维码连接和订单号)NativeRes nativeRes = wxPayService.nativePay(uxmOrder);return Result.success(nativeRes);}

service:

@Resourceprivate WxPayConfig wxPayConfig;@Resourceprivate CloseableHttpClient wxPayClient;@Resourceprivate IUxmOrderService orderService;/*** 调用统一下单api** @param uxmOrder* @return* @throws Exception*/@Transactional(rollbackFor = Exception.class)@Overridepublic NativeRes nativePay(UxmOrder uxmOrder) throws Exception {HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));// 请求body参数Gson gson = new Gson();Map paramsMap = new HashMap();paramsMap.put("appid", wxPayConfig.getAppid());paramsMap.put("mchid", wxPayConfig.getMchId());paramsMap.put("description", uxmOrder.getSubscribePlan());paramsMap.put("out_trade_no", uxmOrder.getOrderNo());paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxApiType.NATIVE_NOTIFY.getType()));Map amountMap = new HashMap();//由单位:元 转换为单位:分,并由Bigdecimal转换为整型String amount = uxmOrder.getAmount().toString();BigDecimal total = BigDecimalUtil.mul(amount, "100");amountMap.put("total", total.intValue());amountMap.put("currency", "CNY");paramsMap.put("amount", amountMap);//将参数转换成json字符串String jsonParams = gson.toJson(paramsMap);log.info("请求参数 ===> {}" + jsonParams);StringEntity entity = new StringEntity(jsonParams, "utf-8");entity.setContentType("application/json");httpPost.setEntity(entity);httpPost.setHeader("Accept", "application/json");//完成签名并执行请求CloseableHttpResponse response = wxPayClient.execute(httpPost);try {String bodyAsString = EntityUtils.toString(response.getEntity());//响应体int statusCode = response.getStatusLine().getStatusCode();//响应状态码if (statusCode == 200) { //处理成功log.info("成功, 返回结果 = " + bodyAsString);} else if (statusCode == 204) { //处理成功,无返回Bodylog.info("成功");} else {log.info("Native下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);throw new IOException("request failed");}//响应结果Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);//二维码String codeUrl = resultMap.get("code_url");//保存二维码String orderNo = uxmOrder.getOrderNo();orderService.saveCodeUrl(orderNo, codeUrl);//返回二维码\NativeRes nativeRes = new NativeRes();nativeRes.setCodeUrl(codeUrl);nativeRes.setOrderNo(uxmOrder.getOrderNo());return nativeRes;} finally {response.close();}}

封装微信支付api接口:

@AllArgsConstructor@Getterpublic enum WxApiType {/*** Native下单*/NATIVE_PAY("/v3/pay/transactions/native"),/*** 查询订单*/ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),/*** 关闭订单*/CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),/*** 支付通知*/NATIVE_NOTIFY("/api/wx-pay/native/notify");/*** 类型*/private final String type;}

5、支付通知

Controller:

/*** 支付通知* 微信支付通过支付通知接口将用户支付成功消息通知给商户*/@ApiOperation(value = "支付通知", notes = "支付通知")@PostMapping("/native/notify")@Log(BusinessTypeEnum.OTHER)public Result<String> nativeNotify(HttpServletRequest request, HttpServletResponse response) {Gson gson = new Gson();Map<String, String> map = new HashMap<>();try {//处理通知参数String body = HttpUtils.readData(request);Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);String requestId = (String) bodyMap.get("id");//签名的验证WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest= new WechatPay2ValidatorForRequest(verifier, requestId, body);if (!wechatPay2ValidatorForRequest.validate(request)) {log.error("通知验签失败");//失败应答response.setStatus(500);map.put("code", "ERROR");map.put("message", "通知验签失败");return Result.success(gson.toJson(map));}log.info("通知验签成功");//处理订单wxPayService.processOrder(bodyMap);//应答超时//模拟接收微信端的重复通知TimeUnit.SECONDS.sleep(5);//成功应答response.setStatus(200);map.put("code", "SUCCESS");map.put("message", "成功");return Result.success(gson.toJson(map));} catch (Exception e) {e.printStackTrace();//失败应答response.setStatus(500);map.put("code", "ERROR");map.put("message", "失败");return Result.success(gson.toJson(map));}}

HttpUtils.java

public class HttpUtils {/*** 将通知参数转化为字符串** @param request* @return*/public static String readData(HttpServletRequest request) {BufferedReader br = null;try {StringBuilder result = new StringBuilder();br = request.getReader();for (String line; (line = br.readLine()) != null; ) {if (result.length() > 0) {result.append("\n");}result.append(line);}return result.toString();} catch (IOException e) {throw new RuntimeException(e);} finally {if (br != null) {try {br.close();} catch (IOException e) {e.printStackTrace();}}}}}

签名验证类:

/*** 签名验证*/public class WechatPay2ValidatorForRequest {protected static final Logger log = LoggerFactory.getLogger(WechatPay2ValidatorForRequest.class);/*** 应答超时时间,单位为分钟*/protected static final long RESPONSE_EXPIRED_MINUTES = 5;protected final Verifier verifier;protected final String requestId;protected final String body;public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body) {this.verifier = verifier;this.requestId = requestId;this.body = body;}protected static IllegalArgumentException parameterError(String message, Object... args) {message = String.format(message, args);return new IllegalArgumentException("parameter error: " + message);}protected static IllegalArgumentException verifyFail(String message, Object... args) {message = String.format(message, args);return new IllegalArgumentException("signature verify fail: " + message);}public final boolean validate(HttpServletRequest request) throws IOException {try {//处理请求参数validateParameters(request);//构造验签名串String message = buildMessage(request);String serial = request.getHeader(WECHAT_PAY_SERIAL);String signature = request.getHeader(WECHAT_PAY_SIGNATURE);//验签if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",serial, message, signature, requestId);}} catch (IllegalArgumentException e) {log.warn(e.getMessage());return false;}return true;}protected final void validateParameters(HttpServletRequest request) {// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at lastString[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};String header = null;for (String headerName : headers) {header = request.getHeader(headerName);if (header == null) {throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);}}//判断请求是否过期String timestampStr = header;try {Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));// 拒绝过期请求if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);}} catch (DateTimeException | NumberFormatException e) {throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);}}protected final String buildMessage(HttpServletRequest request) throws IOException {String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);String nonce = request.getHeader(WECHAT_PAY_NONCE);return timestamp + "\n"+ nonce + "\n"+ body + "\n";}protected final String getResponseBody(CloseableHttpResponse response) throws IOException {HttpEntity entity = response.getEntity();return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";}}

Service:

private final ReentrantLock lock = new ReentrantLock(); /*** 处理订单** @param bodyMap*/@Overridepublic void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {log.info("处理订单");//解密报文String plainText = decryptFromResource(bodyMap);//将明文转换成mapGson gson = new Gson();HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);String orderNo = (String) plainTextMap.get("out_trade_no");/*在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱*///尝试获取锁:// 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放if (lock.tryLock()) {try {//处理重复的通知//接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。String orderStatus = orderService.getOrderStatus(orderNo);if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {return;}//模拟通知并发try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}//设置订单状态、升级版本、设置过期时间、添加支付记录this.setOrderInfo(orderNo, plainText);} finally {//要主动释放锁lock.unlock();}}}

密文解密:

/*** 对称解密** @param bodyMap* @return*/private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {log.info("密文解密");//通知数据Map<String, String> resourceMap = (Map) bodyMap.get("resource");//数据密文String ciphertext = resourceMap.get("ciphertext");//随机串String nonce = resourceMap.get("nonce");//附加数据String associatedData = resourceMap.get("associated_data");AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));//数据明文String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),ciphertext);return plainText;}

/*** 设置订单状态、添加支付记录* @param orderNo* @param result*/private void setOrderInfo(String orderNo, String result){UxmOrder order = orderService.getOne(new LambdaQueryWrapper<UxmOrder>().eq(UxmOrder::getOrderNo, orderNo), false);if (OrderStatus.NOTPAY.getType().equals(order.getPayStatus())) {//更新订单状态orderService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);//记录支付日志paymentInfoService.createPaymentInfo(result);}}

6、主动查询订单状态

Controller:

/*** 查询订单状态*/@ApiOperation(value = "查询订单状态", notes = "查询订单状态")@PostMapping("/order/confirm/{orderNo}")public Result<String> orderConfirm(@PathVariable String orderNo) throws Exception {//通过订单号查询订单UxmOrder uxmOrder = orderService.getOrderByOrderNo(orderNo);if (ObjectUtil.isNull(uxmOrder)) {throw new Exception("参数错误,该订单不存在!");}//判断订单是否已支付,未支付则调用微信查询订单接口if (OrderStatus.NOTPAY.getType().equals(uxmOrder.getPayStatus())) {//未支付订单状态:调用微信支付查单接口return Result.success(wxPayService.checkOrderStatus(orderNo));}return Result.success(WxTradeState.SUCCESS.getType());//SUCCESS NOTPAY}

Service:

/*** 查询订单状态** @param orderNo* @return*/@Overridepublic String checkOrderStatus(String orderNo) throws Exception {log.warn("根据订单号核实订单状态 ===> {}", orderNo);//调用微信支付查单接口String result = this.queryOrder(orderNo);Gson gson = new Gson();Map<String, String> resultMap = gson.fromJson(result, HashMap.class);//获取微信支付端的订单状态String tradeState = resultMap.get("trade_state");//判断订单状态if (WxTradeState.SUCCESS.getType().equals(tradeState)) {log.warn("核实订单已支付 ===> {}", orderNo);//设置订单状态、升级版本、设置过期时间、添加支付记录this.setOrderInfo(orderNo, result);return tradeState;}return WxTradeState.NOTPAY.getType();}

/*** 调用微信支付查单接口** @param orderNo* @return* @throws Exception*/private String queryOrder(String orderNo) throws Exception {log.info("查单接口调用 ===> {}", orderNo);String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());HttpGet httpGet = new HttpGet(url);httpGet.setHeader("Accept", "application/json");//完成签名并执行请求CloseableHttpResponse response = wxPayClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());//响应体int statusCode = response.getStatusLine().getStatusCode();//响应状态码if (statusCode == 200) { //处理成功log.info("成功, 返回结果 = " + bodyAsString);} else if (statusCode == 204) { //处理成功,无返回Bodylog.info("成功");} else {log.info("查单接口调用,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);throw new IOException("request failed");}return bodyAsString;} finally {response.close();}}

7、apiclient_key.pem(私钥文件)

文件内容如下:(注:该文件存放位置根据application.yml中的“商户私钥文件”路径为准)

-----BEGIN PRIVATE KEY-----MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC6KmMvuf/NVU8ch/0OSkGP3ngmA+quLz/BTg/bcVYgBvEnB6UC3xQg6TdyBcu55t6Toa37+NnPGezhTNixKPZpNRTkCJ0PV0Y5ns/KmBC7TJ+iiYmT3pJ3jZQR1we4M+cre6z805XohQiqyp4zJObG4KLE6LJkFB7M2tArXQI8DFHp2j8SnDozzUQEwkffTHZJuO5exHmi214Vt/lNVfXBZXipisn9YQpUk6D+pXrASVdOTepgXQNIdzbQt07jO6N2UHtYxwNDZjO9OJf6tGOqYYnebMQ7X+HT9xpxqViCVR2LrymPWe2Xl6+1mtmFoNsiE5Cm5skoqraAbgc8E5LlAgMBAAECggEABgzuoFR5PeEx6wl4bgh3zQc7/HBQJk0e01eIKGesluni2JPlBwzdCJzL0obhsi8QuNeeYfwaiCKdkkz/FfLw30Z8YVTuVdtOSv0gX8NFd/Drl0rFD+tB82TElTfZ5mC5eK5SVv1BeAcq2vIu5hai8X/HioLNmXcV8S6DaYViVzU5GV7n2YC2rWWJ1vKZpNmXUWtxeVelmiSN+l20WHok3R/aziJ34KR1GXSfrjyYXgVLWM7UVcAmzywKsHp5o3sU522kCKd3TGgHEsweaWgsBsqsvqvt5QDEMPbhYb99aUS9TPwBy08Un1Yio7YQR/w7oOVe0pn8l1g7MdVc1gzK2QKBgQDh/Fa+4UF+q0qtQhKbr/ElfsAb0q9RJ/A5jyxvL6oxVyE7cTzcSNvJVJXmFiakuQ5QMP+qa9dopBye+v63/iT/yzmLfS82EnPyo1qx13zcCxQ6I9B/K00/597srUIZLVg3OumkTJi/vS4Zu/TJlcMjzXkC17RRC1vO8CW+ftgcqwKBgQDS5CNxuJ5BpkpuzLqopTuQ2gYtEwn8wc98Junr7UvvxUhVf8iExJXe7+tTM/CzlJyyP+YHozdqKtynKAeosB6GhYaJLpUpwEqI38sBHdvGPpYzcXSKLczHsY3r1CnP8mpxSSmp4Uwf3HwY/5LVR4qXtKJHn4Foz5Fse8iQcRPurwKBgA5gKACCgdEWAm0dG+PtgFCbTIs4jtCB0uVGd1QnWxNCcKnAXVfCBsE68UIuvIyT/RYa19i2fYB5mByA6P05XI8tFV8LOpqc9+VCgP15MMcqqUG9j4DXd1WOYX760o6ZdNgmlkBOYxUnaqxWaY79SOmZI46LvDu/ljqGyk/g78x7AoGASRgvvMvLdl+nrs2g6LEUezlGKLtPm96lBpgKPe6qgjlzv8ahfnsQowuvGdCH3gZlZVbGaOFGZLLkdb9nIC2i9ucy4TtXEfiHHPfMSd/Ke+TXdI8fYIFNV+2PjiykLWINSKSeHzZqhySJkrSKdQft3nUKRh7f4K8I7Xvd4UqAKRsCgYBXieQ+7hHwUF71l1RXafJL9N2+hMBRoMXdY8wuwlCiI6mfo92G0bwg0HGBRyWvbD25oBiE8yq+jFRhhaOIZHuYn6TG1ogxgu/LWlrsJ9m4x2DiWvCe/A+aFdFOXukkCqGq4n5tSzrYycl8JDAg1lQBd48QoGAp06HNYefd3AT54w==-----END PRIVATE KEY-----

补充:

微信支付接口文档地址:产品中心 - 微信支付商户平台

到此结束,如若对你有帮助,点个赞再走啦~~~

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。