微信支付服务商api对接
引入官方sdk
<!--微信v3支付sdk {https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient}-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.8</version>
</dependency>
新建配置类
/**
* @Description : 微信支付配置
* @Author : wzkris
* @Version : V1.0.0
* @Date : 2022/11/27 15:41
*/
@ConfigurationProperties("wx.pay")
@Component
@Data
public class WxPayConfig {
/** appid*/
public String appId;
/** appSecret*/
private String appSecret;
/** 商户号 */
public String merchantId;
/** 商户API私钥路径 */
public String privateKeyPath;
/**商户API私钥*/
public PrivateKey privateKey;
/** 商户证书序列号 */
public String merchantSerialNumber;
/**v3密钥*/
public String apiV3key;
}
新建service
/**
* @Description : 微信v3支付服务
* @Author : wzkris
* @Version : V1.0.0
* @Date : 2022/11/27 15:48
* @Description : 微信支付服务,金额一律为分;对账单中的交易金额单位为元
*/
@Component
public class WxPayService { @Resource
private WxPayConfig wxconfig; @Resource
private GtConfig gtConfig; /**
* 请求客户端
*/
private static CloseableHttpClient httpClient; /**
* 微信平台证书验证器
*/
private static Verifier verifier; @PostConstruct
public void init() {
try {
String privateKey = FileUtils.readFileToString(wxconfig.privateKeyPath);
wxconfig.setPrivateKey(PemUtil.loadPrivateKey(privateKey));
//初始化微信平台证书容器,certificatesManager可以放多个商户
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(
wxconfig.merchantId,
new WechatPay2Credentials(wxconfig.merchantId, new PrivateKeySigner(wxconfig.merchantSerialNumber, wxconfig.privateKey)),
wxconfig.getApiV3key().getBytes(StandardCharsets.UTF_8)
);
verifier = certificatesManager.getVerifier(wxconfig.merchantId);
//初始化httpClient
httpClient = WechatPayHttpClientBuilder
.create()
.withMerchant(wxconfig.merchantId, wxconfig.merchantSerialNumber, PemUtil.loadPrivateKey(privateKey))
.withValidator(new WechatPay2Validator(verifier))
.build();
} catch (Exception e) {
e.printStackTrace();
} } /**
* 添加子商户进件申请
*/
public JSONObject addSubMerchant(SubMerchantApplyment applyment) {
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/applyment4sub/applyment/");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
httpPost.addHeader("Wechatpay-Serial", String.valueOf(verifier.getValidCertificate().getSerialNumber()));
//敏感信息加密
encryptSubMerchantInfo(applyment); httpPost.setEntity(new StringEntity(JSONObject.toJSONString(applyment), StandardCharsets.UTF_8));
try {
CloseableHttpResponse response = httpClient.execute(httpPost);
JSONObject jsonObject = JSONObject.parseObject(EntityUtils.toString(response.getEntity()));
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new ServiceException(jsonObject);
}
return jsonObject;
} catch (IOException e) {
throw new RuntimeException(e);
}
} /**
* 进件敏感信息加密
*
* @param applyment 特约商户进件
*/
public void encryptSubMerchantInfo(SubMerchantApplyment applyment) {
//1.超级管理员信息加密
ContactInfo contactInfo = applyment.getContactInfo();
contactInfo.setContactName(encrypt(contactInfo.getContactName()))
.setOpenId(encrypt(contactInfo.getOpenId()))
.setMobilePhone(encrypt(contactInfo.getMobilePhone()))
.setContactEmail(encrypt(contactInfo.getContactEmail()));
//1.1经办人类型
if (contactInfo.getContactType().equals(ContactInfo.contactType.SUPER.toString())) {
contactInfo.setContactIdNumber(encrypt(contactInfo.getContactIdNumber()));
}
//2.主体资料
SubjectInfo subjectInfo = applyment.getSubjectInfo();
//2.1受益人信息加密
if (!subjectInfo.getUboInfoList().isEmpty()) {
//每个受益人信息都加密
for (UboInfo uboInfo : subjectInfo.getUboInfoList()) {
uboInfo.setUboIdDocName(encrypt(uboInfo.getUboIdDocName()))
.setUboIdDocNumber(encrypt(uboInfo.getUboIdDocNumber()))
.setUboIdDocAddress(encrypt(uboInfo.getUboIdDocAddress()));
}
}
//3.结算银行账户
BankAccountInfo bankAccountInfo = applyment.getBankAccountInfo();
bankAccountInfo.setAccountName(encrypt(bankAccountInfo.getAccountName()))
.setAccountNumber(encrypt(bankAccountInfo.getAccountNumber()));
} /**
* 查看申请单状态
*
* @param businessCode 业务申请编号
*/
public ApplymentResult searchApplymentStatus(String businessCode) {
HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/applyment4sub/applyment/business_code/" + businessCode);
try {
CloseableHttpResponse response = httpClient.execute(httpGet);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new ServiceException(JSONObject.parseObject(EntityUtils.toString(response.getEntity())));
}
return JSONObject.parseObject(EntityUtils.toString(response.getEntity()), ApplymentResult.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
} /**
* @param bo 支付参数
* @param enums 支付渠道
* @return 返回参数
* @Description 服务商付款
*/
public JSONObject pay(WxPayParamsBO bo, PayEnums enums) {
//1.组装参数
JSONObject requestParams = new JSONObject();
requestParams.put("sp_appid", wxconfig.getAppId());
requestParams.put("sp_mchid", wxconfig.getMerchantId());
//子商户appid
requestParams.put("sub_appid", bo.getSubAppId());
//子商户号
requestParams.put("sub_mchid", bo.getSubMchId());
requestParams.put("description", bo.getDescription());
requestParams.put("out_trade_no", bo.getOrderNo());
requestParams.put("notify_url", gtConfig.getUrl() + bo.getNotifyUrl());
JSONObject amount = new JSONObject();
amount.put("total", bo.getAmount());
requestParams.put("amount", amount);
JSONObject payer = new JSONObject();
if (enums.equals(PayEnums.JSAPI)) {
payer.put("sp_openid", bo.getOpenId());
requestParams.put("payer", payer);
} else if (enums.equals(PayEnums.H5)) {
JSONObject sceneInfo = new JSONObject();
//拿到HttpServletRequest解析ip
HttpServletRequest request = ServletUtils.getRequest();
sceneInfo.put("payer_client_ip", IpUtils.getIpAddr(request));
JSONObject h5Info = new JSONObject();
h5Info.put("type", IpUtils.getOperatorSys(request));
sceneInfo.put("h5_info", h5Info);
requestParams.put("scene_info", sceneInfo);
} //2.初始化post请求
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/partner/transactions/" + enums.getPath());
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
httpPost.setEntity(new StringEntity(requestParams.toJSONString(), StandardCharsets.UTF_8));
try {
CloseableHttpResponse response = httpClient.execute(httpPost);
//拿到返回参数
JSONObject resParams = JSONObject.parseObject(EntityUtils.toString(response.getEntity()));
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new ServiceException(resParams);
}
return resParams;
} catch (IOException e) {
throw new ServiceException(e.getMessage());
} } /**
* @param bo 查询订单详情
* @Description 查询订单信息
*/
public OrderResultInfo queryOrder(QueryOrderBO bo) {
String url = " https://api.mch.weixin.qq.com/v3/pay/partner/transactions/out-trade-no/"
+ bo.getOrderNo()
+ "?" + "sp_mchid=" + bo.getSpMchid()
+ "&" + "sub_mchid=" + bo.getSubMchid();
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Accept", "application/json");
try {
CloseableHttpResponse response = httpClient.execute(httpGet);
String bodyAsString = EntityUtils.toString(response.getEntity());
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new ServiceException(JSONObject.parseObject(bodyAsString));
}
return JSONObject.parseObject(bodyAsString, OrderResultInfo.class);
} catch (IOException e) {
throw new ServiceException(e);
}
} /**
* @param orderNo 订单号
* @param subMchid 子商户号
* @description: 关闭订单
*/
public void closeOrder(String orderNo, String subMchid) {
String url = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/out-trade-no/" + orderNo + "/close";
//构造参数
JSONObject requestParams = new JSONObject();
requestParams.put("sp_mchid", wxconfig.getMerchantId());
requestParams.put("sub_mchid", subMchid);
//构造请求
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
httpPost.setEntity(new StringEntity(requestParams.toJSONString(), StandardCharsets.UTF_8));
try {
CloseableHttpResponse response = httpClient.execute(httpPost);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT) {
throw new ServiceException(response.getEntity());
}
} catch (IOException e) {
throw new ServiceException(e.getMessage());
} } /**
* 退款
*/
public RefundResultInfo refund(RefundBO bo) {
//1. 构造参数
JSONObject requestParams = new JSONObject();
//子商户号
requestParams.put("sub_mchid", bo.getSubMchId());
//服务商订单号
requestParams.put("out_trade_no", bo.getOrderNo());
//退款单号
requestParams.put("out_refund_no", bo.getRefundNo());
requestParams.put("reason", bo.getReason());
JSONObject amount = new JSONObject();
//退款金额,单位分
amount.put("refund", bo.getRefund());
//原订单金额,单位分
amount.put("total", bo.getTotal());
amount.put("currency", "CNY");
requestParams.put("amount", amount); //2. 初始化post请求
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
httpPost.setEntity(new StringEntity(requestParams.toJSONString(), StandardCharsets.UTF_8));
try {
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
String resString = EntityUtils.toString(httpResponse.getEntity());
if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new ServiceException(JSONObject.parseObject(resString));
}
return JSONObject.parseObject(resString, RefundResultInfo.class);
} catch (IOException e) {
throw new ServiceException(e);
}
} /**
* @param request
* @Description 回调请求的验签、解密
*/
public OrderResultInfo asyncCallback(HttpServletRequest request) {
//1.获取请求头
String body = getRequestBody(request);
String serialNumber = request.getHeader("Wechatpay-Serial");
String timeStamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String signature = request.getHeader("Wechatpay-Signature");
//2.构造请求
NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(serialNumber)
.withNonce(nonce)
.withTimestamp(timeStamp)
.withSignature(signature)
.withBody(body)
.build();
NotificationHandler handler = new NotificationHandler(verifier, wxconfig.getApiV3key().getBytes(StandardCharsets.UTF_8));
// 验签和解析请求体
try {
Notification notification = handler.parse(notificationRequest);
return JSONObject.parseObject(notification.getDecryptData(), OrderResultInfo.class);
} catch (ValidationException | ParseException e) {
throw new ServiceException(e);
}
} /**
* 微信上传图片/视频
*/
public String upload(MultipartFile multipartFile, UploadEnums enums) throws IOException {
String sha256 = DigestUtils.sha256Hex(multipartFile.getBytes());
HttpPost httpPost = new WechatPayUploadHttpPost
.Builder(URI.create("https://api.mch.weixin.qq.com/v3/merchant/media/" + enums.getValue()))
.withImage(multipartFile.getOriginalFilename(), sha256,
new ByteArrayInputStream(multipartFile.getBytes()))
.build();
CloseableHttpResponse response = httpClient.execute(httpPost);
String resString = EntityUtils.toString(response.getEntity());
JSONObject resParams = JSONObject.parseObject(resString);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new ServiceException("请求失败", resParams);
}
return resParams.getString("media_id");
} /**
* @param text 明文
* @return 加密后的数据
*/
public String encrypt(String text) {
// 建议从Verifier中获得微信支付平台证书,或使用预先下载到本地的平台证书文件中
X509Certificate certificate = verifier.getValidCertificate();
try {
return RsaCryptoUtil.encryptOAEP(text, certificate);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}
return null;
} /**
* @param ciphertext 密文
* @return 解密后端数据
*/
public String decrypt(String ciphertext) {
// 使用商户私钥解密
try {
return RsaCryptoUtil.decryptOAEP(ciphertext, wxconfig.privateKey);
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
} /**
* 获取POST请求的json参数
*/
private String getRequestBody(HttpServletRequest request) {
try (BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder responseStrBuilder = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null) {
responseStrBuilder.append(inputStr);
}
return responseStrBuilder.toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
service里面有个方法是服务商进件的方法,里面的请求参数非常多,建议参考微信支付-开发者文档 (qq.com)
微信支付服务商api对接的更多相关文章
- 微信支付服务商模式(受理机构模式)开发注意事项,jsapi支付
1.首先下载的demo,一般都是有些bug的,先要改一下. 2.微信貌似没有为服务商模式单独开发demo,下载的也都是普通商户的支付demo,其实这里没有必要单独写,因为他们区别就是几个参数的区别. ...
- 微信支付JS API使用心得
微信的接口真的很坑爹,只返回成功或失败,从来不会告诉你为什么失败.这个微信支付的js接口也是调了一个下午才成功,期间踩了不少坑,在这里总结一下,而且把支付接口封装成了一个js文件,这样以后调用就很方便 ...
- Java 微信支付分对接记录 (先享后付)
微信支付分(先享后付)对接记录: 微信支付分对接步骤 填写开通支付分的申请表格 此步骤大概需要审核 1-3 个工作日; (模板-服务信息配置表-[先享后付免确认]-[商户名].xls) 填写商户信息 ...
- Java中的微信支付(1):API V3版本签名详解
1. 前言 最近在折腾微信支付,证书还是比较烦人的,所以有必要分享一些经验,减少你在开发微信支付时的踩坑.目前微信支付的API已经发展到V3版本,采用了流行的Restful风格. 今天来分享微信支付的 ...
- C#开发微信门户及应用(32)--微信支付接入和API封装使用
在微信的应用上,微信支付是一个比较有用的部分,但也是比较复杂的技术要点,在微商大行其道的年代,自己的商店没有增加微信支付好像也说不过去,微信支付旨在为广大微信用户及商户提供更优质的支付服务,微信的支付 ...
- 微信支付.NET版开发总结(JS API),好多坑,适当精简
前2天,做一个手机网页的微信支付的项目,费了好些周折,记录一下.接下来,按照开发步骤,细数一下,我遇到的那些坑. [坑1]官方邮件中下载的demo只有PHP版本,其他版本没有给链接.可能让人误以为只有 ...
- 微信支付开发(3) JS API支付
由于微信支付接口更新,本文档已过期,请查看新版微信支付教程.地址 http://www.cnblogs.com/txw1958/category/624506.html 本文介绍如何使用JS API支 ...
- 微信支付开发若干问题总结,API搞死人(谢谢ζั͡ޓއއއ๓http://www.thinkphp.cn/code/1620.html)血淋淋的教训,第二次栽这里了
近日,我研究了微信支付的API,我是用简化版的API,首先简述一下流程: 1.通过APP_ID,APP_SCRECT获取网页授权码code, 2.利用code获取用户openid/userinfo 3 ...
- 微信支付.NET版开发总结(JS API),好多坑,适当精简。
前2天,做一个手机网页的微信支付的项目,费了好些周折,记录一下.接下来,按照开发步骤,细数一下,我遇到的那些坑. [坑1]官方邮件中下载的demo只有PHP版本,其他版本没有给链接.可能让人误以为只有 ...
- 微信支付之h5方式(非微信内置浏览器中支付)
这两天完成了公司网站手机和PC端的支付对接,就是支付宝和微信. 对接完后有所感触,我们来聊一聊,微信支付的坑,为什么这么说呢,因为我在对接完支付宝后是很愉快的,基本上在demo上稍加修改就ok了, 对 ...
随机推荐
- WebService 客户端上传图片,服务器端接收图片并保存到本地
需求:如题,C#本地要调用Webservice接口,上传本地的照片到服务器中: 参考:客户端: https://blog.csdn.net/tiegenZ/article/details/799276 ...
- PL/SQL Initialization error Could not initialize 问题
问题: PL/SQL Initialization error Could not initialize 问题 参考链接: https://blog.csdn.net/luoyanjiewade/ar ...
- 397. 整数替换 (Medium
问题描述 397. 整数替换 (Medium) 给定一个正整数 n ,你可以做如下操作: 如果 n 是偶数,则用 n / 2 替换 n. 如果 n 是奇数,则可以用 n + 1 或 n - 1 替换 ...
- while跟if循环
While(表达式)//循环,达成括号里的条件,一直循环执行里面的程序直到得出的值不成立后退出循环 { 循环体} 几个练习题: using System; namespace while练习2 { c ...
- Oracle UNDOTBS表空间的查看与扩容
1.查看UNDO表空间使用情况 select tablespace_name, round(sum(decode(status, 'ACTIVE', bytes, 'UNEXPIRED', ...
- 每日一抄 Go语言通信顺序进程简述
package main import ( "fmt" "sync" ) /* Go实现了两种并发形式,第一种是大家普遍认知的多线程共享内存,其实就是 Java ...
- Java使用HSSFWorkbook生成Excel
HSSF 是Horrible SpreadSheet Format的缩写,也即"讨厌的电子表格格式". 也许HSSF的名字有点滑稽,就本质而言它是一个非常严肃.正规的API.通过H ...
- 基于Linux的ssh协议配置sftp
sftp采用的是ssh加密隧道,安装性方面较ftp强,而且依赖的是系统自带的ssh服务,不像ftp还需要额外的进行安装 1. 创建sftp组 创建完成之后使用cat /etc/group命令组的信息 ...
- 构建自动发现的Docker服务架构
------------恢复内容开始------------ 建立consul服务 在建立consul服务中,每个提供服务的节点(在Docker主机上)都要部署和运行consul的client,ser ...
- 再见IE