From ec4e65ba157bded805081aa40977b1d996fd2b21 Mon Sep 17 00:00:00 2001 From: KKSU <15274802129@163.com> Date: Wed, 17 Jan 2024 11:29:57 +0800 Subject: [PATCH] fapiao --- src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java | 44 +- src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java | 17 src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateDto.java | 17 + src/main/java/cc/mrbird/febs/pay/model/FPUserTitleDto.java | 20 + src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java | 17 + src/main/java/cc/mrbird/febs/common/utils/AppContants.java | 3 src/test/java/cc/mrbird/febs/ProfitTest.java | 145 +++++++++-- pom.xml | 10 src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateCustomCellDto.java | 21 + src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java | 331 +++++++++++++++++++++++++-- src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateInformationDto.java | 22 + src/main/resources/wxP12/apiclient_key.pem | 28 ++ 12 files changed, 580 insertions(+), 95 deletions(-) diff --git a/pom.xml b/pom.xml index 3f7a5b4..af6a5ee 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,11 @@ <dependencies> <dependency> <groupId>com.github.wechatpay-apiv3</groupId> + <artifactId>wechatpay-apache-httpclient</artifactId> + <version>0.4.7</version> + </dependency> + <dependency> + <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-java</artifactId> <version>0.2.12</version> </dependency> @@ -357,6 +362,11 @@ <artifactId>poi-ooxml-schemas</artifactId> <version>3.8</version> </dependency> + <dependency> + <groupId>com.github.wechatpay-apiv3</groupId> + <artifactId>wechatpay-apache-httpclient</artifactId> + <version>0.4.7</version> + </dependency> </dependencies> <build> diff --git a/src/main/java/cc/mrbird/febs/common/utils/AppContants.java b/src/main/java/cc/mrbird/febs/common/utils/AppContants.java index 43d6356..a212e5c 100644 --- a/src/main/java/cc/mrbird/febs/common/utils/AppContants.java +++ b/src/main/java/cc/mrbird/febs/common/utils/AppContants.java @@ -23,6 +23,8 @@ public static final String PC_LOGIN_PREFIX = "pc_"; + public static final String FP_TOKEN_HEADER_TYPE = "WECHATPAY2-SHA256-RSA2048 "; + /** * token头部 */ @@ -72,5 +74,6 @@ public static final String AGENT_LEVEL_REQUIRE = "AGENT_LEVEL_REQUIRE"; public static final String SIGN_MD5 = "MD5"; + public static final String WX_CARD_NUM = "221D49AEC4EA538A63941D1936709C8559EB05C5";//证书编号 } diff --git a/src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java b/src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java index bef7b04..3f9d310 100644 --- a/src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java +++ b/src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java @@ -3,6 +3,7 @@ import cc.mrbird.febs.common.annotation.ControllerEndpoint; import cc.mrbird.febs.common.entity.FebsResponse; import cc.mrbird.febs.common.enumerates.DataDictionaryEnum; +import cc.mrbird.febs.common.utils.AppContants; import cc.mrbird.febs.mall.dto.*; import cc.mrbird.febs.mall.entity.DataDictionaryCustom; import cc.mrbird.febs.mall.mapper.DataDictionaryCustomMapper; @@ -29,6 +30,8 @@ import java.io.IOException; import java.math.BigDecimal; import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -136,39 +139,34 @@ DataDictionaryEnum.FP_CALLBACK_URL.getCode(), faPiaoDto.getCallbackUrl() ); - KeyPair privateKey = wxFaPiaoService.getPrivateKey(); HeaderDto headerDto = new HeaderDto(); - headerDto.setCallback_url(faPiaoDto.getCallbackUrl()); + headerDto.setCallback_url("https://api.blnka.cn/api/xcxPay/fapiaoCallBack"); headerDto.setShow_fapiao_cell(false); String parseObj = JSONUtil.parseObj(headerDto).toString(); String baseUrl = "https://api.mch.weixin.qq.com"; String canonicalUrl = "/v3/new-tax-control-fapiao/merchant/development-config"; - String postStr = wxFaPiaoService.createAuthorization( - "POST", - canonicalUrl, - parseObj, - privateKey - ); - // 创建httppost + String postStr = null; try { - HttpClient httpClient = new HttpClient(); - PostMethod post = new PostMethod(baseUrl+canonicalUrl); - post.setRequestHeader("Accept", "application/json"); - post.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36"); - post.setRequestHeader("Content-Type", "application/json"); - post.setRequestHeader("Connection", "keep-alive"); - post.setRequestHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 "+postStr); - RequestEntity entity = new StringRequestEntity(parseObj, "text/html", "utf-8"); - post.setRequestEntity(entity); - httpClient.executeMethod(post); - String responseBodyAsString = post.getResponseBodyAsString(); - cn.hutool.json.JSONObject maps = JSONUtil.parseObj(responseBodyAsString); - System.out.println(maps); + + PrivateKey privateKey = wxFaPiaoService.getPrivateKeyV3(); + postStr = wxFaPiaoService.createAuthorization( + "PATCH", + baseUrl+canonicalUrl, + parseObj, + privateKey + ); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } - return new FebsResponse().success().message("操作成功"); + String token = AppContants.FP_TOKEN_HEADER_TYPE+postStr; + System.out.println("WECHATPAY2-SHA256-RSA2048 "+postStr); + String s = wxFaPiaoService.sendPatch(baseUrl + canonicalUrl, parseObj, token); + log.info("配置开发选项:"+s); + + return new FebsResponse().success().message(s); } @PostMapping(value = "/agentDetail") diff --git a/src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java b/src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java index 6830a9f..fef552f 100644 --- a/src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java +++ b/src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java @@ -13,6 +13,7 @@ import cc.mrbird.febs.mall.service.IMallMoneyFlowService; import cc.mrbird.febs.pay.model.NotifyData; import cc.mrbird.febs.pay.service.IXcxPayService; +import cc.mrbird.febs.pay.service.WxFaPiaoService; import cc.mrbird.febs.pay.util.Signature; import cc.mrbird.febs.pay.util.Util; import cc.mrbird.febs.pay.util.WechatConfigure; @@ -21,6 +22,7 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; +import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -31,9 +33,11 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.BufferedReader; import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; +import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -68,6 +72,9 @@ @Autowired private IXcxPayService iXcxPayService; + + @Autowired + private WxFaPiaoService wxFaPiaoService; private final XcxProperties xcxProperties = SpringContextHolder.getBean(XcxProperties.class); /** @@ -242,13 +249,11 @@ */ @Transactional(rollbackFor = Exception.class) @RequestMapping(value = "/fapiaoCallBack") - public Map<Object, Object> fapiaoCallBack(HttpServletResponse response, HttpServletRequest request) throws IOException { - log.info("微信电子发票回调接口...."); - Map<Object, Object> objectObjectHashMap = new HashMap<>(); - objectObjectHashMap.put("code","SUCCESS"); - objectObjectHashMap.put("message",""); - return objectObjectHashMap; + public Map<String, Object> fapiaoCallBack(HttpServletResponse response, HttpServletRequest request) throws IOException { + return wxFaPiaoService.fapiaoCallBack(response,request); + } + /** * 微信支付回调接口 */ diff --git a/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateCustomCellDto.java b/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateCustomCellDto.java new file mode 100644 index 0000000..ece6db2 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateCustomCellDto.java @@ -0,0 +1,21 @@ +package cc.mrbird.febs.pay.model; + +import lombok.Data; + +@Data +public class FPCardTemplateCustomCellDto { + /** + * 【cell位文字】 展示在卡券详情页自定义cell位上的文字 + */ + private String words; + /** + * 【cell位描述】 展示在卡券详情页自定义cell位右侧的描述 + */ + private String description; + + private String jump_url; + + private String miniprogram_user_name; + + private String miniprogram_path; +} diff --git a/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateDto.java b/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateDto.java new file mode 100644 index 0000000..525fc96 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateDto.java @@ -0,0 +1,17 @@ +package cc.mrbird.febs.pay.model; + +import lombok.Data; + +@Data +public class FPCardTemplateDto { + /** + * 【插卡公众号AppID】 插卡公众号AppID。 + * 若是服务商模式,则可以是服务商申请的AppId,也可以是子商户申请的AppId; + * 若是直连模式,则是直连商户申请的AppId + */ + private String card_appid; + /** + * 【卡券模板信息】 卡券模板信息 + */ + private FPCardTemplateInformationDto card_template_information; +} diff --git a/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateInformationDto.java b/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateInformationDto.java new file mode 100644 index 0000000..56f2851 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateInformationDto.java @@ -0,0 +1,22 @@ +package cc.mrbird.febs.pay.model; + +import lombok.Data; + +/** + * 【卡券模板信息】 卡券模板信息 + */ +@Data +public class FPCardTemplateInformationDto { + /** + * 【收款方名称】 收款方名称,显示在电子发票卡券信息中,若不传则默认取商户名称 + */ + private String payee_name; + /** + * 【卡券logo地址】 卡券logo地址 + */ + private String logo_url; + /** + * 【卡券自定义cell位配置】 + */ + private FPCardTemplateCustomCellDto custom_cell; +} diff --git a/src/main/java/cc/mrbird/febs/pay/model/FPUserTitleDto.java b/src/main/java/cc/mrbird/febs/pay/model/FPUserTitleDto.java new file mode 100644 index 0000000..48889ec --- /dev/null +++ b/src/main/java/cc/mrbird/febs/pay/model/FPUserTitleDto.java @@ -0,0 +1,20 @@ +package cc.mrbird.febs.pay.model; + +import lombok.Data; + +@Data +public class FPUserTitleDto { + /** + * 【发票申请单号】 发票申请单号,唯一标识一次开票行为。 + * 当开票场景为WITHOUT_WECHATPAY时,为调用【获取抬头填写链接】接口时指定的发票申请单号; + * 当开票场景为WITH_WECHATPAY时,为与本次开票关联的微信支付订单号,且必须是属于相应商户的订单 + */ + private String fapiao_apply_id; + /** + * 【开票场景】 开票场景 + * 可选取值: + * WITH_WECHATPAY: 微信支付场景 + * WITHOUT_WECHATPAY: 非微信支付场景 + */ + private String scene; +} diff --git a/src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java b/src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java index 960e8e2..333cabb 100644 --- a/src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java +++ b/src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java @@ -1,11 +1,22 @@ package cc.mrbird.febs.pay.service; -import java.security.KeyPair; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.util.Map; public interface WxFaPiaoService { - String createAuthorization(String method, String canonicalUrl, String body, KeyPair keyPair);//生成Token + String createAuthorization(String method, String canonicalUrl, String body, PrivateKey keyPair) throws UnsupportedEncodingException, NoSuchAlgorithmException;//生成Token - KeyPair getPrivateKey();//通过证书获取私钥公钥 + PrivateKey getPrivateKeyV3() throws IOException;//获取私钥 + String sendPatch(String url, String params, String token); + + String sendPost(String url, String params, String token); + + Map<String, Object> fapiaoCallBack(HttpServletResponse response, HttpServletRequest request); } diff --git a/src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java b/src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java index a35aa8d..0182525 100644 --- a/src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java +++ b/src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java @@ -1,22 +1,50 @@ package cc.mrbird.febs.pay.service.impl; import cc.mrbird.febs.common.properties.XcxProperties; +import cc.mrbird.febs.common.utils.AppContants; import cc.mrbird.febs.common.utils.SpringContextHolder; +import cc.mrbird.febs.mall.entity.MallOrderInfo; +import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper; +import cc.mrbird.febs.pay.model.HeaderDto; import cc.mrbird.febs.pay.service.WxFaPiaoService; import cc.mrbird.febs.pay.util.RandomStringGenerator; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier; +import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; +import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials; +import com.wechat.pay.contrib.apache.httpclient.notification.Notification; +import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler; +import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest; +import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import okhttp3.HttpUrl; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; -import org.springframework.util.Base64Utils; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.*; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -25,44 +53,290 @@ @RequiredArgsConstructor public class WxFaPiaoServiceImpl implements WxFaPiaoService { + private final MallOrderInfoMapper mallOrderInfoMapper; + private final XcxProperties xcxProperties = SpringContextHolder.getBean(XcxProperties.class); @Override - public String createAuthorization(String method, String canonicalUrl, String body, KeyPair keyPair) { + public String createAuthorization(String method, String canonicalUrl, String body, PrivateKey keyPair) throws UnsupportedEncodingException, NoSuchAlgorithmException { String nonceStr = RandomStringGenerator.getRandomStringByLength(32);//随机字符串 long timestamp = System.currentTimeMillis() / 1000;//时间戳 - String signature = sign(method, canonicalUrl, timestamp, nonceStr, body, keyPair);//签名加密 + HttpUrl httpurl = HttpUrl.parse(canonicalUrl); + String message = buildMessage(method, httpurl, timestamp, nonceStr, body); + log.info("签名串:\n"+message); + log.info("签名串长度:\n"+getWordCount(message)); + String signature = sign2(message.getBytes("utf-8"), keyPair); + + log.info("签名串sign:\n"+signature); + log.info("签名串长度sign:\n"+getWordCount(signature)); +// String yourCertificateSerialNo = "221D49AEC4EA538A63941D1936709C8559EB05C5"; return "mchid=\"" + xcxProperties.getWecharpayMchid() + "\"," + "nonce_str=\"" + nonceStr + "\"," + "timestamp=\"" + timestamp + "\"," - + "serial_no=\"" + "50F37206347BCC9E6AC9860DAACE52AC035F7C24" + "\","//证书序列号 + + "serial_no=\"" + AppContants.WX_CARD_NUM + "\"," + "signature=\"" + signature + "\""; } - @Override - public KeyPair getPrivateKey() { - return createPKCS12("Tenpay Certificate", "1658958205"); + public int getWordCount(String s) + { + int length = 0 ; + for ( int i = 0 ; i < s.length(); i ++ ) + { + int ascii = Character.codePointAt(s, i); + if (ascii >= 0 && ascii <= 255 ) + length ++ ; + else + length += 2 ; + + } + return length; + } + + public String sign2(byte[] message,PrivateKey keyPair) throws NoSuchAlgorithmException { + Signature sign = Signature.getInstance("SHA256withRSA"); + String s = null; + try { + sign.initSign(keyPair); + sign.update(message); + s = Base64.getEncoder().encodeToString(sign.sign()); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } catch (SignatureException e) { + e.printStackTrace(); + } + return s; + } + + public String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) { + String canonicalUrl = url.encodedPath(); + if (url.encodedQuery() != null) { + canonicalUrl += "?" + url.encodedQuery(); + } + return method + "\n" + + canonicalUrl + "\n" + + timestamp + "\n" + + nonceStr + "\n" + + body + "\n"; + } + + @Override + public PrivateKey getPrivateKeyV3() throws IOException { + InputStream inputStream = new ClassPathResource("wxP12/apiclient_key.pem") + .getInputStream(); + + String content = new BufferedReader(new InputStreamReader(inputStream)) + .lines().collect(Collectors.joining(System.lineSeparator())); + try { + String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s+", ""); + + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePrivate( + new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("当前Java环境不支持RSA", e); + } catch (InvalidKeySpecException e) { + throw new RuntimeException("无效的密钥格式"); + } + } + + @Override + public String sendPatch(String url, String params, String token) { + String result = ""; + CloseableHttpClient httpClient = HttpClients.createDefault(); + HttpPatch httpPatch = new HttpPatch(url); + httpPatch.setHeader("Content-type", "application/json"); + httpPatch.setHeader("Charset", "utf-8"); + httpPatch.setHeader("Accept", "application/json"); + httpPatch.setHeader("Accept-Charset", "utf-8"); + httpPatch.setHeader("Authorization", token); + try { + StringEntity data = new StringEntity(params, "utf-8"); + httpPatch.setEntity(data); + HttpResponse response = httpClient.execute(httpPatch); + HttpEntity entity = response.getEntity(); + result = EntityUtils.toString(entity); + } catch (Exception e) { + result = "{\"status\":\"1\",\"error\":\"" + e.getMessage() + "\"}"; + } + return result; + } + + @Override + public String sendPost(String url, String params, String token) { + String result = ""; + int err = 0; + while (true) { + CloseableHttpClient client = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost(url); + try { + httpPost.addHeader("Content-type", "application/json"); + httpPost.addHeader("Charset", "utf-8"); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("Accept-Charset", "utf-8"); + httpPost.addHeader("Authorization", token); + + StringEntity data = new StringEntity(params, "utf-8"); + httpPost.setEntity(data); + HttpResponse response = client.execute(httpPost); + HttpEntity resEntity = response.getEntity(); + result = EntityUtils.toString(resEntity); + return result; + } catch (IOException e) { + result = "{\"status\":\"1\",\"errors\":\"" + e.getMessage() + "\"}"; + if (err++ > 2) { + break; + } + try { + Thread.sleep((err + 2) * 1000); + } catch (InterruptedException e1) { + result = "{\"status\":\"1\",\"errors\":\"" + e1.getMessage() + "\"}"; + } + } + } + return result; + } + + @Override + public Map<String, Object> fapiaoCallBack(HttpServletResponse response, HttpServletRequest request) { + log.info("微信电子发票回调接口...."); + Map<String,Object> map = new HashMap<>(); + try { + BufferedReader br = request.getReader(); + String str = null; + StringBuilder sb = new StringBuilder(); + while ((str = br.readLine())!=null) { + sb.append(str); + } + // 构建request,传入必要参数 + NotificationRequest requests = new NotificationRequest.Builder() + .withSerialNumber(request.getHeader("Wechatpay-Serial")) + .withNonce(request.getHeader("Wechatpay-Nonce")) + .withTimestamp(request.getHeader("Wechatpay-Timestamp")) + .withSignature(request.getHeader("Wechatpay-Signature")) + .withBody(String.valueOf(sb)) + .build(); + //验签 + NotificationHandler handler = new NotificationHandler(getVerifier(AppContants.WX_CARD_NUM), xcxProperties.getWecharpaySecretV3().getBytes(StandardCharsets.UTF_8)); + //解析请求体 + Notification notification = handler.parse(requests); + log.info("微信电子发票回调接口....解析请求体:"+notification.toString()); + String decryptData = notification.getDecryptData();//可能是支付业务的回调数据 + log.info("微信电子发票回调接口....decryptData:"+notification.toString()); + Notification.Resource resource = notification.getResource();//电子发票的回调加密数据 + log.info("微信电子发票回调接口....resource:"+notification.toString()); + + if ("FAPIAO.USER_APPLIED".equals(notification.getEventType())//用户发票抬头填写完成类型:FAPIAO.USER_APPLIED + && !"encryptresource".equals(notification.getResourceType())) {//通知的资源数据类型,确认成功通知为encryptresource。 + //解密 + AesUtil aesUtil = new AesUtil(xcxProperties.getWecharpaySecretV3().getBytes("utf-8")); + String decryptToString = aesUtil.decryptToString( + resource.getAssociatedData().getBytes("utf-8"), + resource.getNonce().getBytes("utf-8"), + resource.getCiphertext()); + log.info("微信电子发票回调接口....resource解密:"+decryptToString); + + JSONObject parseObj = JSONUtil.parseObj(decryptToString); + + log.info("微信电子发票回调接口....resource解密-JSONObject:"+parseObj); + + String mchid = String.valueOf(parseObj.get("mchid")); + String fapiao_apply_id = String.valueOf(parseObj.get("fapiao_apply_id")); + String apply_time = String.valueOf(parseObj.get("apply_time")); + MallOrderInfo mallOrderInfo = mallOrderInfoMapper.selectByOrderNo(fapiao_apply_id); + if(ObjectUtil.isNotEmpty(mallOrderInfo)){ + //省略查询订单 + //此处处理业务 + map.put("code","SUCCESS"); + map.put("message","成功"); + //消息推送成功 + return map; + } + } + map.put("code","RESOURCE_NOT_EXISTS"); + map.put("message", "订单不存在"); + return map; + }catch (Exception e) { + e.printStackTrace(); + } + map.put("code","FAIL"); + map.put("message", "失败"); + return map; + } + /** - * V3 SHA256withRSA 签名. + * 功能描述: 验证签名 + * 注意:使用微信支付平台公钥验签 + * Wechatpay-Signature 微信返签名 + * Wechatpay-Serial 微信平台证书序列号 * - * @param method 请求方法 GET POST PUT DELETE 等 - * @param canonicalUrl 例如 https://api.mch.weixin.qq.com/v3/pay/transactions/app?version=1 ——> /v3/pay/transactions/app?version=1 - * @param timestamp 当前时间戳 因为要配置到TOKEN 中所以 签名中的要跟TOKEN 保持一致 - * @param nonceStr 随机字符串 要和TOKEN中的保持一致 - * @param body 请求体 GET 为 "" POST 为JSON - * @param keyPair 商户API 证书解析的密钥对 实际使用的是其中的私钥 - * @return the string + * @return java.lang.String + * @author 影子 */ @SneakyThrows - public String sign(String method, String canonicalUrl, long timestamp, String nonceStr, String body, KeyPair keyPair) { - String signatureStr = Stream.of(method, canonicalUrl, String.valueOf(timestamp), nonceStr, body) - .collect(Collectors.joining("\n", "", "\n")); - Signature sign = Signature.getInstance("SHA256withRSA"); - sign.initSign(keyPair.getPrivate()); - sign.update(signatureStr.getBytes(StandardCharsets.UTF_8)); - return Base64Utils.encodeToString(sign.sign()); + public boolean verifySign(HttpServletRequest request,String body) { + boolean verify = false; + try { + String wechatPaySignature = request.getHeader("Wechatpay-Signature"); + String wechatPayTimestamp = request.getHeader("Wechatpay-Timestamp"); + String wechatPayNonce = request.getHeader("Wechatpay-Nonce"); + String wechatPaySerial = request.getHeader("Wechatpay-Serial"); + //组装签名串 + String signStr = Stream.of(wechatPayTimestamp, wechatPayNonce, body) + .collect(Collectors.joining("\n", "", "\n")); + //获取平台证书 + AutoUpdateCertificatesVerifier verifier = getVerifier(wechatPaySerial); + //获取失败 验证失败 + if (verifier != null) { + Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initVerify(verifier.getValidCertificate()); + //放入签名串 + signature.update(signStr.getBytes(StandardCharsets.UTF_8)); + verify = signature.verify(Base64.getDecoder().decode(wechatPaySignature.getBytes())); + } + } catch (InvalidKeyException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return verify; } + + /** + * 保存微信平台证书 + */ + private static final ConcurrentHashMap<String, AutoUpdateCertificatesVerifier> verifierMap = new ConcurrentHashMap<>(); + + /** + * 功能描述:获取平台证书,自动更新 + * 注意:这个方法内置了平台证书的获取和返回值解密 + */ + public AutoUpdateCertificatesVerifier getVerifier(String mchSerialNo) { + AutoUpdateCertificatesVerifier verifier = null; + if (verifierMap.isEmpty() || !verifierMap.containsKey(mchSerialNo)) { + verifierMap.clear(); + try { + //传入证书 + PrivateKey privateKey = getPrivateKeyV3(); + //刷新 + PrivateKeySigner signer = new PrivateKeySigner(mchSerialNo, privateKey); + WechatPay2Credentials credentials = new WechatPay2Credentials(xcxProperties.getWecharpayMchid(), signer); + verifier = new AutoUpdateCertificatesVerifier(credentials + , xcxProperties.getWecharpaySecretV3().getBytes("utf-8")); + verifierMap.put(verifier.getValidCertificate().getSerialNumber()+"", verifier); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + verifier = verifierMap.get(mchSerialNo); + } + return verifier; + } + /** * 获取公私钥.通过证书 @@ -70,7 +344,8 @@ private KeyStore store; private final Object lock = new Object(); public KeyPair createPKCS12(String keyAlias, String keyPass) { - ClassPathResource resource = new ClassPathResource(xcxProperties.getCertLocalPath()); +// ClassPathResource resource = new ClassPathResource(xcxProperties.getCertLocalPath()); + ClassPathResource resource = new ClassPathResource("wxP12/apiclient_cert.p12"); // File file = new File("src/main/resources/wxP12/apiclient_cert.p12"); char[] pem = keyPass.toCharArray(); try { @@ -98,11 +373,11 @@ } public static void main(String[] args) { - try { - System.out.println(new ClassPathResource("wxP12/apiclient_cert.p12").getFile().exists()); - } catch (IOException e) { - e.printStackTrace(); - } + HeaderDto headerDto = new HeaderDto(); + headerDto.setCallback_url("https://api.blnka.cn/api/xcxPay/fapiaoCallBack"); + headerDto.setShow_fapiao_cell(true); + String parseObj = JSONUtil.parseObj(headerDto).toString(); + System.out.println(parseObj); } } diff --git a/src/main/resources/wxP12/apiclient_key.pem b/src/main/resources/wxP12/apiclient_key.pem new file mode 100644 index 0000000..a754832 --- /dev/null +++ b/src/main/resources/wxP12/apiclient_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDfoX/j+3GsXcRf +vVGaTBtewpzyKhkJPXK/QElmEAaGFyjX3PkevbXm5HiL1HXtZnewaLnLOrBiiPyP +PRuuRIyx5Yx7OK2MWGtzj5pByYnvAqWJD048kYdW2sMuF17ARlj+DzRxNNmySCZF +EFI0QKJTz4CQTcYHPOJQXPR5kCzWXgueIWowdUQCRWQZM3xB8l0nhMA3dqMQjZXK +cYbISrNfU5X0aynHhhwozk1onQG19a1o2mF+FCIpqViis6iJ0rVi9NRax+9mvA1x +CAyqAmw2XrYd+uNbAHpxMLDr8A4Nu+e9U9I7Ns3HPuPkGUITTIdShfQWIdKYy+Ye +tx5oTtnjAgMBAAECggEAD9feOqWeuQpvliTQejD4I2+ANPvciDYNQhVo3c022Rsc +7qYeFBp2n6QCEk38CNSgmCPjPc2rn197HR7ELkV2/kd3+2bZjL7a5fX/hLeliQRT ++/DUpx/cSb/34fUP5mlVdrJXEcofviy0YWI1oLQPjAaAYjjQVcTvd6gOPeqD8q/X +Fa3glF/bpm7xNJay7FSaSDywtvaDnhJmTo1norJNV0Sg7Gz5x0eKGa+kW/rm1z8Z +kumhEdaK+yl4mZ47hz/R6xbUyh7BV3xLekC49nAduGgCfU8TQm1Gq8eQdxjmtJeJ +WM7zFy2/1/2ZRMfJ9zdh4B/jiosmikpyxc5tCQIvaQKBgQDwXJascXQ7bLa6Pzmq +Ddby3v+xTJBOsB/NbI275kHEMCNc3ExAhnbwrPcrWyRsWR7mKPClTiK2drFfbo7o +vqEi+pC5vuShxv55riK7ehIcIttG9H0iKFMapJKYsF199JpGoYKyQ8pPtokdd3eH +xYzc65S0/bW9gQBgixs0/DG9dQKBgQDuLj8GLpOkDcckuNgMl31cAv5qvUn8ICNv +/DQPsCO6jWs1c8N7ru3mxVoUkKCDKMsLSak535Mzskg154M+236y8YesgOVfeZse +ZmueUJNJ0STFKGmZMjTlHe5qWZoShknwC6U91/MUfiHUMAiERPZ3bYRCb1WxN0da +/zmcl8UW9wKBgQDYlbW3oWvo+CcXYE1nrJzZsJOagbEvFokxo/V7MRpl3DKhRGj5 +Y7DdYh4+1RvW/d9X6eAeNHAXFpDxuz/O/adZS4sJtLd0B0na/0yQvoh/DfGk2D3B +f7CSu2TVbWZxTu2NY4/PbkJo3cRj5viwQRSTJTyUcmFHZ2ydkftsHSv82QKBgQCZ +uD0yxzLAaagCWk4N6mOc083Ro/MxBqj4aIRNL7gPeaAcsmcS1zZxfyOBLEp7/AL3 +eQX/9PyzG7ghGhcJpW3jHaEbw2DGOtzTRPTmC0jUFmgt2sQ0fEPJL4UlHEkk9YiY +pVtzS4F102YP5SRKAZw0PfljSym6I1wpLZnIJ+eqxwKBgQDEtVO2Nja7AVl0n99Q +DXW/mrtRZ6ToROXmzcJMiMEN24zstitaB+s01QAasz1VwdFZkZLBHFBzekBSkCJa +xiwZOQXJhzMSL9MYGLTXo/Jhqlsb03923zO45ZFJKnlY7T0W33G0oEfCyAVsXEMM +nemv1UBmk/fW7KrS2Eg46wanwg== +-----END PRIVATE KEY----- diff --git a/src/test/java/cc/mrbird/febs/ProfitTest.java b/src/test/java/cc/mrbird/febs/ProfitTest.java index 9ee9405..2dc3b0a 100644 --- a/src/test/java/cc/mrbird/febs/ProfitTest.java +++ b/src/test/java/cc/mrbird/febs/ProfitTest.java @@ -4,6 +4,7 @@ import cc.mrbird.febs.common.enumerates.FlowTypeEnum; import cc.mrbird.febs.common.enumerates.MoneyFlowTypeEnum; import cc.mrbird.febs.common.exception.FebsException; +import cc.mrbird.febs.common.utils.AppContants; import cc.mrbird.febs.common.utils.HttpCurlUtil; import cc.mrbird.febs.common.utils.MallUtils; import cc.mrbird.febs.common.utils.RedisUtils; @@ -12,8 +13,7 @@ import cc.mrbird.febs.mall.mapper.*; import cc.mrbird.febs.mall.service.*; import cc.mrbird.febs.mall.vo.MallMemberCouponVo; -import cc.mrbird.febs.pay.model.BrandWCPayRequestData; -import cc.mrbird.febs.pay.model.HeaderDto; +import cc.mrbird.febs.pay.model.*; import cc.mrbird.febs.pay.service.IXcxPayService; import cc.mrbird.febs.pay.service.WxFaPiaoService; import cc.mrbird.febs.pay.util.WechatConfigure; @@ -35,10 +35,12 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -52,6 +54,8 @@ import java.net.URLConnection; import java.nio.charset.Charset; import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.util.*; import java.io.ByteArrayOutputStream; import java.io.InputStream; @@ -106,59 +110,130 @@ @Autowired ResourceLoader resourceLoader; @Test - public void rankProfit() throws IOException { + public void rankProfit() throws IOException {//配置开发选项 // System.out.println(new ClassPathResource("wxP12/apiclient_cert.p12").getFile().exists()); // System.out.println(new File("src/main/resources/wxP12/apiclient_cert.p12").exists()); // // InputStream inputStream = new FileInputStream(file); // System.out.println(resourceLoader.getResource("classpath:/wxP12/apiclient_cert.p12").exists()); - KeyPair privateKey = wxFaPiaoService.getPrivateKey(); + PrivateKey privateKey = wxFaPiaoService.getPrivateKeyV3(); HeaderDto headerDto = new HeaderDto(); headerDto.setCallback_url("https://api.blnka.cn/api/xcxPay/fapiaoCallBack"); - headerDto.setShow_fapiao_cell(true); + headerDto.setShow_fapiao_cell(false); String parseObj = JSONUtil.parseObj(headerDto).toString(); String baseUrl = "https://api.mch.weixin.qq.com"; String canonicalUrl = "/v3/new-tax-control-fapiao/merchant/development-config"; - String postStr = wxFaPiaoService.createAuthorization( - "POST", - canonicalUrl, - parseObj, - privateKey - - ); - // 创建httppost + String postStr = null; try { - HttpClient httpClient = new HttpClient(); - PostMethod post = new PostMethod("https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/merchant/development-config"); - post.setRequestHeader("Accept", "application/json"); - post.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36"); - post.setRequestHeader("Content-Type", "application/json; charset=UTF-8"); - post.setRequestHeader("Connection", "keep-alive"); - post.setRequestHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 "+postStr); - RequestEntity entity = new StringRequestEntity(parseObj, "text/html", "utf-8"); - post.setRequestEntity(entity); - httpClient.executeMethod(post); - String responseBodyAsString = post.getResponseBodyAsString(); - cn.hutool.json.JSONObject maps = JSONUtil.parseObj(responseBodyAsString); - System.out.println(maps); - } catch (IOException e) { + postStr = wxFaPiaoService.createAuthorization( + "PATCH", + baseUrl+canonicalUrl, + parseObj, + privateKey + ); + } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } + String token = AppContants.FP_TOKEN_HEADER_TYPE+postStr; + System.out.println("WECHATPAY2-SHA256-RSA2048 "+postStr); + String s = wxFaPiaoService.sendPatch(baseUrl + canonicalUrl, parseObj, token); + System.out.println(s); } @Test - public void rankProfit2() throws IOException { - KeyPair privateKey = wxFaPiaoService.getPrivateKey(); + public void rankProfit03() throws IOException {//创建电子发票卡券模板 + FPCardTemplateDto fpCardTemplateDto = new FPCardTemplateDto(); + fpCardTemplateDto.setCard_appid("wxad2fdb2fcad10fb2"); + + FPCardTemplateInformationDto fpCardTemplateInformationDto = new FPCardTemplateInformationDto(); + fpCardTemplateInformationDto.setLogo_url("http://mmbiz.qpic.cn/mmbiz/iaL1LJM1mF9aRKPZJkmG8xXhiaHqkKSVMMWeN3hLut7X7hicFNjakmxibMLGWpXrEXB33367o7zHN0CwngnQY7zb7g/0"); + fpCardTemplateInformationDto.setPayee_name("发票测试"); + + FPCardTemplateCustomCellDto fpCardTemplateCustomCellDto = new FPCardTemplateCustomCellDto(); + fpCardTemplateCustomCellDto.setWords("电子发票"); + fpCardTemplateCustomCellDto.setDescription("查看发票"); + fpCardTemplateCustomCellDto.setJump_url("http://www.qq.com"); + fpCardTemplateCustomCellDto.setMiniprogram_path("gh_86a091e50ad4@app"); + fpCardTemplateCustomCellDto.setMiniprogram_user_name("pages/xxxPage"); + fpCardTemplateInformationDto.setCustom_cell(fpCardTemplateCustomCellDto); + + fpCardTemplateDto.setCard_template_information(fpCardTemplateInformationDto); + + String parseObj = JSONUtil.parseObj(fpCardTemplateDto).toString(); + System.out.println(parseObj); + String baseUrl = "https://api.mch.weixin.qq.com"; + String canonicalUrl = "/v3/new-tax-control-fapiao/card-template"; + PrivateKey privateKey = wxFaPiaoService.getPrivateKeyV3(); + String postStr = null; + try { + postStr = wxFaPiaoService.createAuthorization( + "POST", + baseUrl+canonicalUrl, + parseObj, + privateKey + ); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + String token = AppContants.FP_TOKEN_HEADER_TYPE+postStr; + + System.out.println("WECHATPAY2-SHA256-RSA2048 "+postStr); + String s = wxFaPiaoService.sendPost(baseUrl + canonicalUrl, parseObj, "WECHATPAY2-SHA256-RSA2048 "+postStr); + System.out.println(s); + } + + + + @Test + public void rankProfit04() throws IOException {//获取用户填写的抬头 + FPUserTitleDto fpUserTitleDto = new FPUserTitleDto(); + fpUserTitleDto.setScene("WITH_WECHATPAY"); + fpUserTitleDto.setFapiao_apply_id(""); + + String parseObj = JSONUtil.parseObj(fpUserTitleDto).toString(); + System.out.println(parseObj); + String baseUrl = "https://api.mch.weixin.qq.com"; + String canonicalUrl = "/v3/new-tax-control-fapiao/user-title"; + PrivateKey privateKey = wxFaPiaoService.getPrivateKeyV3(); + String postStr = null; + try { + postStr = wxFaPiaoService.createAuthorization( + "POST", + baseUrl+canonicalUrl, + parseObj, + privateKey + ); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + String token = AppContants.FP_TOKEN_HEADER_TYPE+postStr; + + System.out.println("WECHATPAY2-SHA256-RSA2048 "+postStr); + String s = wxFaPiaoService.sendPost(baseUrl + canonicalUrl, parseObj, "WECHATPAY2-SHA256-RSA2048 "+postStr); + System.out.println(s); + cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(s); + System.out.println(jsonObject); + } + + @Test + public void rankProfit2() throws IOException {//查询配置开发选项 + PrivateKey privateKey = wxFaPiaoService.getPrivateKeyV3(); String baseUrl = "https://api.mch.weixin.qq.com"; String canonicalUrl = "/v3/new-tax-control-fapiao/merchant/development-config"; - String postStr = wxFaPiaoService.createAuthorization( - "GET", - canonicalUrl, - "", - privateKey + String postStr = null; + try { + postStr = wxFaPiaoService.createAuthorization( + "GET", + baseUrl+canonicalUrl, + "", + privateKey - ); + ); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + System.out.println("WECHATPAY2-SHA256-RSA2048"+postStr); try { HttpClient httpClient = new HttpClient(); GetMethod method = new GetMethod(baseUrl+canonicalUrl); -- Gitblit v1.9.1