引言
大家可以参考一下,可以避免几个坑的地方,都是我的亲身经历,起因是我们公司的网站应用和app研发需要接入微信支付
一、注册微信开放平台账号
链接:微信开放平台
基本信息的填写,邮箱需要未在微信开放平台和公众平台注册、未被微信私人账号绑定的邮箱
登记主体信息,这里有一个注意点就是如果是企业的话会有一个开发者资质的认证,是需要收费的
审核费用:中国大陆地区:300元,非中国大陆地区:99美元
交这个费用的时候需要确认一下提交的信息,因为写着是审核费用,可能审核一次就要交一次费用,我这边是一次过的,这个审核还是蛮容易的。支持对公转账也可以在线支付
管理员信息的话,如果公司授权给你管理这个账号的话,可以填自己的,使用绑定上面填写身份信息的微信扫码认证,确认信息提交等微信那边审核成功之后就可以登录进去了
认证资质
正常企业注册好了,登录页面后查看认证资质是这样的
这边说一下,如果你是纯个人开发的,虽然能支持注册账号,但是认证不支持,申请认证必须是企业\组织机构,你可以去注册一个个体工商户
二、创建应用,注册商户号
微信开放平台其实就是管理你的这些应用,每种类型的应用创建都是限制,比如网站应用只能创建10个,多了只能再创建新的微信开放平台了。
商户号其实就是商家号收款的商户罢了,在微信开放平台创建的应用都会返回一个appid给你,将这个appid与某个商户号绑定关联起来,在这个应用调的微信支付api,钱就会到这个商户号中
创建应用——管理中心——选择应用——创建应用,需要审核,审核通过之后就是这样的
这个是我创建的网站应用,需要注意的是,网站应用要填一个网站信息登记表(有模板下载),登记表中有备案号的填写,意思你的这个网站需要备案的。并且网站应用本身只能对接微信登录功能,因为商户号不支持绑定网站应用类型的APPID,如想对接微信支付,需要创建app、小程序、公众号等等,才能关联appid
三、开通支付产品
在注册商户号期间,会让你选使用场景,选择对应的使用场景,需要填写对应的资产,至少选一个,如果你选择网站,注册完后会自动开通JSP支付和Native支付产品,如果你选择APP,就会开通APP支付产品
开通该产品后,你才能使用对应的支付功能,调用对应的api,注册后忘记开通了,或者后续有新的需求了,可以在产品中心 ——我的产品 去进行开通,需要准备对应材料。
四、设置商户信息
在账号中心可以设置商户号的信息:银行卡、管理员、员工管理等等,当然在商户号注册后所对应管理员微信中就会出现一个微信商家助手的服务,可以点进去或者点击我的商家,也可以设置部分信息
在调用api之前有几个东西必须设置好,账号中心——api安全,比如v2、v3密钥,对应着v2、v3的api,商户号证书,平台证书后面微信更换成了微信支付公钥了
五、实现支付
由于我们后续的需求调整,我们使用的是微信合作伙伴模式,而不是做直连商户,直连商户是自己的商户号,自己实现,自己收钱,直销。合作伙伴是服务商,为其他商户提供服务,统一管理多个商户,收钱的是这些商户,我们可以收服务费。左上角可以选择模式。
文件导入
导入依赖
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.15</version>
</dependency>
配置
public static String getFileByPath(String filePath){
ClassPathResource resource = new ClassPathResource(filePath);
try (InputStream inputStream = resource.getInputStream()) {
File tempFile = File.createTempFile("temp", ".tmp");
try (FileOutputStream outputStream = new FileOutputStream(tempFile)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
return tempFile.getAbsolutePath();
} catch (IOException e) {
throw new RuntimeException("Failed to extract the resource file to a temporary location: " + filePath, e);
}
}
public static Config config;
static {
config = new RSAPublicKeyConfig.Builder()
.merchantId(WxPayInfoConfig.merchantId)//商户号
.privateKeyFromPath(WxPayUtil.getFileByPath(privateKeyPath))//私钥
.publicKeyFromPath(WxPayUtil.getFileByPath(publicKeyPath))//公钥
.publicKeyId(WxPayInfoConfig.publicKeyNumber)//公钥序列号
.merchantSerialNumber(WxPayInfoConfig.merchantSerialNumber)//商户证书序列号
.apiV3Key(WxPayInfoConfig.apiV3key)//v3密钥
.build();
}
填入对应的参数即可,由于密钥证书等文件放置resources下,所以路径直接可以通过getFileByPath方法去加载,参数填"wx/apiclient_key.pem"就行。这边注意如果你是平台证书的话就不能使用RSAPublicKeyConfig,需要使用RSAAutoCertificateConfig配置
调用api,根据自己的模式导包
public static JSONObject createOrder(Integer totalPrice, String out_trade_no, String description,String paymentNotice,Integer signature,String code,String subMchId,String openId) {
if(StringUtils.isBlank(openId)){
openId=getOpenId(code);
}
JsapiService service=new JsapiService.Builder().config(WxPayInfoConfig.config).build();
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
Payer payer=new Payer();
amount.setTotal(totalPrice);
payer.setSpOpenid(openId);
request.setSpMchid(WxPayInfoConfig.merchantId);
request.setSubMchid(subMchId);
request.setSpAppid(WxPayInfoConfig.appid);
request.setPayer(payer);
request.setAmount(amount);
request.setDescription(description);
request.setNotifyUrl(paymentNotice);
request.setOutTradeNo(out_trade_no);
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'+08:00'");
request.setTimeExpire(simpleDateFormat.format(new Date()));
// 调用下单方法,得到应答
PrepayResponse response = service.prepay(request);
return getPayReturnMap(response.getPrepayId());
}
public static JSONObject getPayReturnMap(String prepayId) {
JSONObject returnMap = new JSONObject();
//处理成功
//生成代签名的支付信息
String nonceStr = UUID.randomUUID().toString(true);
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String prepay = String.format("prepay_id=%s", prepayId);
String signature = appPaySign(WxPayInfoConfig.appid,prepay, nonceStr, timestamp);
returnMap.put("appId", WxPayInfoConfig.appid);
returnMap.put("partnerId", WxPayInfoConfig.merchantId);
returnMap.put("prepayId", prepayId);
returnMap.put("package", prepay);
returnMap.put("noncestr", nonceStr);
returnMap.put("timestamp", timestamp);
returnMap.put("sign", signature);
return returnMap;
}
openId需要客户端传一个code给你,你通过一个地址去获取openId,openId是每个微信的唯一标识
获取地址:https://api.weixin.qq.com/sns/jscode2session
public static String getOpenId(String code){
OpenIdVo openId=new OpenIdVo(WxPayInfoConfig.appid,WxPayInfoConfig.secret,code);
JSONObject jsonObject = null;
try {
jsonObject = sendHttpRequest("GET", WxPayInfoConfig.appLogin, JSONObject.toJSONString(openId));
} catch (Exception e) {
throw new RuntimeException(e);
}
return jsonObject.getString("openid");
}
public static JSONObject sendHttpRequest(String requestType, String requestUrl, String requestParams) throws Exception {
// 初始化URL对象
URL url;
if("GET".equalsIgnoreCase(requestType)){
JSONObject jsonObject = JSONObject.parseObject(requestParams);
StringBuilder stringBuilder=new StringBuilder();
for (Map.Entry<String, Object> next : jsonObject.entrySet()) {
stringBuilder.append(next.getKey()).append("=").append(next.getValue()).append("&");
}
stringBuilder.deleteCharAt(stringBuilder.length()-1);
String newUrl = requestUrl + "?" + stringBuilder.toString();
url=new URL(newUrl);
}else {
url = new URL(requestUrl);
}
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求类型和通用请求属性
connection.setRequestMethod(requestType.toUpperCase());
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
connection.setRequestProperty("Accept", "application/json");
// POST请求时将参数放入请求体
if ("POST".equalsIgnoreCase(requestType)) {
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
os.write(requestParams.getBytes(StandardCharsets.UTF_8));
os.flush();
}
}
// 读取响应
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
StringBuilder response = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// 将响应转换为JSONObject并返回
return JSONObject.parseObject(response.toString());
}
在调用api的会传一个回调地址,这个回调地址需要你提供,微信会将支付结果通过这个回调地址发送给你,你需要回应,否则微信会重发3次,这个地址必须开放出来,公网能访问。
public static NotificationConfig notificationConfig;
static {
notificationConfig = new RSAPublicKeyNotificationConfig.Builder()
.publicKeyFromPath(WxPayUtil.getFileByPath(publicKeyPath))
.publicKeyId(publicKeyNumber)
.apiV3Key(apiV3key)
.build();
}
@ApiOperation(value = "微信支付异步通知", notes = "账户充值")
@PostMapping(value = "/rechargeNotice")
public Map<String, String> rechargeNotice(HttpServletRequest request) throws IOException {
Map<String,String> map=new HashMap<>();
NotificationParser parser = new NotificationParser(WxPayInfoConfig.notificationConfig);
Transaction transaction=null;
try {
// 以支付通知回调为例,验签、解密并转换成 Transaction
transaction = parser.parse(WxPayUtil.notification(request), Transaction.class);
} catch (ValidationException e) {
// 签名验证失败,返回 401 UNAUTHORIZED 状态码
map.put("code","FAIL");
LogUtil.error("sign verification failed", e);
}
LogUtil.info("充值====>"+transaction);
if(null!=transaction){
orderService.rechargeNotice(transaction);//业务逻辑处理
map.put("code","SUCCESS");
}else {
map.put("code","FAIL");
}
return map;
}