springBoot对接微信APP支付和支付宝APP支付
微信APP支付
微信开发文档: pay.weixin.qq.com/wiki/doc/ap…
引入微信sdk
<dependency> <groupId>com.github.javen205</groupId> <artifactId>IJPay-WxPay</artifactId> <version>2.7.4</version> </dependency>
1、相关配置:
#由微信生成的应用ID
appId:
#商户号
mchId:
#api秘钥
apiKey:
#apiv3秘钥
apiKey3:
#秘钥 这里需要用绝对路径
keyPath:
#CA证书 格式.pem 这里需要用绝对路径
certPath:
#CA证书 格式.12这里需要用绝对路径
certP12Path:
#平台证书 格式.pem 这里需要用绝对路径
platformCertPath:
#平台证书 格式.pem 这里需要用绝对路径
platformCertPathWindows:
# 支付回调地址
domain:
WxPayV3Bean相关代码:
@Component
@Data
@ConfigurationProperties(prefix = "v3")
public class WxPayV3Bean {
private String appId;
private String keyPath;
private String certPath;
private String certP12Path;
private String platformCertPath;
private String mchId;
private String apiKey;
private String apiKey3;
private String domain;
private String platformCertPathWindows;
}
1.1在内网环境中测试支付时,回调地址必须公网可以访问才行,否则微信调用不到我们的回调接口,可以使用内网穿透映射自己本地服务的ip端口从而进行测试,相关操作请自行百度
1.2api证书文件的获取:
请登录商户平台进入【账户中心】->【账户设置】->【API安全】根据提示指引下载证书。
1.3api密钥设置
请登录商户平台进入【账户中心】->【账户设置】->【API安全】->【APIv3密钥】中设置 API 密钥。
1.4平台证书需要我们自己去生成相关代码如下:
生成平台证书Service层代码:
public String get() {
try {
String path = getPath("apiclient_key.pem");
IJPayHttpResponse response = WxPayApi.v3(
RequestMethod.GET,
WxDomain.CHINA.toString(),
WxApiType.GET_CERTIFICATES.toString(),
wxPayV3Bean.getMchId(),
getSerialNumber(),
null,
path,
""
);

String timestamp = response.getHeader("Wechatpay-Timestamp");
String nonceStr = response.getHeader("Wechatpay-Nonce");
String serialNumber = response.getHeader("Wechatpay-Serial");
String signature = response.getHeader("Wechatpay-Signature");
String body = response.getBody();
int status = response.getStatus();
logger.info("serialNumber: {}", serialNumber);
logger.info("status: {}", status);
logger.info("body: {}", body);
int isOk = 200;
if (status == isOk) {
JSONObject jsonObject = JSONUtil.parseObj(body);
JSONArray dataArray = jsonObject.getJSONArray("data");
// 默认认为只有一个平台证书
JSONObject encryptObject = dataArray.getJSONObject(0);
JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
String associatedData = encryptCertificate.getStr("associated_data");
String cipherText = encryptCertificate.getStr("ciphertext");
String nonce = encryptCertificate.getStr("nonce");
String serialNo = encryptObject.getStr("serial_no");
final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText,
getPlatformCertPath());
logger.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
}
// 根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCertPath());
System.out.println("verifySignature:" + verifySignature);
return body;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
保存平台证书:
private String savePlatformCert(String associatedData, String nonce, String cipherText, String certPath) {
try {
AesUtil aesUtil = new AesUtil(wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));
// 平台证书密文解密
// encrypt_certificate 中的 associated_data nonce ciphertext
String publicKey = aesUtil.decryptToString(
associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
cipherText
);
// 保存证书
FileWriter writer = new FileWriter(certPath);
writer.write(publicKey);
writer.close();
// 获取平台证书序列号
X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
return certificate.getSerialNumber().toString(16).toUpperCase();
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
}
获取证书序列号:
private String getSerialNumber() throws IOException {
if (StrUtil.isEmpty(serialNo)) {
// 获取证书序列号
String path = getPath("apiclient_cert.pem");
X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(path));
serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
}
return serialNo;
}
1.5证书路径获取相关代码
private String getPath(String fileName) throws IOException {
String path = "";
String osName = System.getProperties().getProperty("os.name");
if (osName.equals("Windows")) {
path = getCerPath(fileName);
return path;
}else {
InputStream stream = this.getClass().getClassLoader().getResourceAsStream("cert/" + fileName);
String proFilePath = System.getProperty("user.dir");
path = proFilePath + File.separator + fileName;
File file = new File(path);
if (!file.exists()) {
FileUtils.copyInputStreamToFile(stream,file);
}
}
return path;
}
本地获取证书路径方法:
private String getCerPath(String cerName){
ClassLoader classLoader = this.getClass().getClassLoader();
URL resource = classLoader.getResource("cert");
String path = resource.getPath() + File.separatorChar + cerName;
return path;
}
服务器获取证书路径:
private String getPlatformCertPath() {
String platformCertPath = "";
String osName = System.getProperties().getProperty("os.name");
if (osName.equals("Linux")) {
platformCertPath = wxPayV3Bean.getPlatformCertPath();
} else {
platformCertPath = wxPayV3Bean.getPlatformCertPathWindows();
}
File file = new File(platformCertPath);
if (!file.getParentFile().isDirectory()) {
file.getParentFile().mkdirs();
}
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
return platformCertPath;
}
1.6本地获取相关证书路径使用getResource()方法是可以的,但是在服务器上是不行的,因为服务器上是jar包运行的,获取不到jar包中的相关资源,会报找不到文件的异常,最终使用this.getClass().getClassLoader().getResourceAsStream("cert/" + fileName)方法获取到相关文件的流,将他写入到docker容器下的一个目录中,使用这个临时文件来进行生成平台证书的操作,然后挂载出平台证书;
2、核心代码:
2.1微信下单
@Override
public ResponseDto appPay(WxOrderParam param) {
StockCarsManagement stockCar = stockCarsManagementService.getOne(new LambdaQueryWrapper<StockCarsManagement>()
.eq(StockCarsManagement::getVehicleId, param.getVehicleId()));
stockCar = Optional.ofNullable(stockCar).orElseThrow(() -> new BizException(BizExceptionEnum.INVALID_ARGUMENT,
"车辆信息不存在"));
OrderManagement orderManagement = orderService.getOne(new LambdaQueryWrapper<OrderManagement>()
.eq(OrderManagement::getOrderNo, param.getOrderNo()));
orderManagement =
Optional.ofNullable(orderManagement).orElseThrow(() -> new BizException(BizExceptionEnum.INVALID_ARGUMENT,
"订单信息不存在"));
if(!OrderStatusEnum.WAIT.getStatusCode().equals(orderManagement.getStatus())){
throw new BizException(BizExceptionEnum.DATA_NOT_EXIST, "订单状态有误");
}
try {
BigDecimal money = param.getAmount().multiply(BigDecimal.valueOf(100d));//微信1分是100
//统一下单参数封装,过期时间为24小时
String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 1440);
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(wxPayV3Bean.getAppId())
.setMchid(wxPayV3Bean.getMchId())
.setDescription(stockCar.getTheTitle())
.setOut_trade_no(param.getOrderNo())
.setTime_expire(timeExpire)
.setNotify_url(wxPayV3Bean.getDomain())
.setAmount(new Amount().setTotal(money.intValue()));
logger.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
IJPayHttpResponse response = WxPayApi.v3(
RequestMethod.POST,
WxDomain.CHINA.toString(),
WxApiType.APP_PAY.toString(),
wxPayV3Bean.getMchId(),
getSerialNumber(),
null,
getPath("apiclient_key.pem"),
JSONUtil.toJsonStr(unifiedOrderModel)
);
boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCertPath());
logger.info("verifySignature: {}", verifySignature);
if (verifySignature) {
String body = response.getBody();
JSONObject jsonObject = JSONUtil.parseObj(body);
String prepayId = jsonObject.getStr("prepay_id");
Map<String, String> map = appPrepayIdCreateSign(wxPayV3Bean.getAppId(), wxPayV3Bean.getMchId(),
prepayId, getPath("apiclient_key.pem"));
logger.info("唤起支付参数:{}", map);
return ReturnUtil.returnSuccess(map);
} else {
return ReturnUtil.returnError();
}
} catch (Exception e) {
e.printStackTrace();
return ReturnUtil.returnError(null, e.getMessage());
}
}
2.2微信支付异步回调通知:
@Override
public void payNotify(HttpServletRequest request, HttpServletResponse response) {
Map<String, String> map = new HashMap<>(12);
try {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
logger.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
String result = HttpKit.readData(request);
logger.info("支付通知密文 {}", result);
// 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
wxPayV3Bean.getApiKey3(), getPlatformCertPath());
logger.info("支付通知明文 {}", plainText);
if (StringUtils.isNotEmpty(plainText)) {
JSONObject jsonObject = JSONUtil.parseObj(plainText);
String out_trade_no = jsonObject.getStr("out_trade_no");
String transaction_id = jsonObject.getStr("transaction_id");
Date success_time = jsonObject.getDate("success_time");
String trade_state = jsonObject.getStr("trade_state");
OrderManagement orderManagement = orderService.getOne(new LambdaQueryWrapper<OrderManagement>()
.eq(OrderManagement::getOrderNo, out_trade_no));
orderManagement.setPayNo(transaction_id);
orderManagement.setPayType(PayTypeEnum.WECHAT.getStatusCode());
orderManagement.setPayTime(success_time);
if ("SUCCESS".equals(trade_state)
&& "支付成功".equals(jsonObject.getStr("trade_state_desc"))) {
orderManagement.setStatus(OrderStatusEnum.DOING.getStatusCode());
}
PayLog payLog = new PayLog();
payLog.setAmount(orderManagement.getAmount());
payLog.setOrderNo(out_trade_no);
payLog.setPayNo(orderManagement.getOrderNo());
payLog.setPayType(PayTypeEnum.WECHAT.getStatusCode());
payLog.setCustomerPhone(orderManagement.getCustomerPhone());
payLog.setPayNo(transaction_id);
payLog.setPayTime(success_time);
payLog.setStatus(jsonObject.getStr("trade_state"));
payLog.setCreateTime(new Date());
switch (trade_state) // 判断交易结果
{
case "SUCCESS":
payLog.setMessage("支付成功");
break;
case "REFUND":
payLog.setMessage("转入退款");
break;
case "NOTPAY":
payLog.setMessage("未支付");
break;
case "CLOSED":
payLog.setMessage("已关闭");
break;
case "REVOKED":
payLog.setMessage("已撤销(付款码支付)");
break;
case "USERPAYING":
payLog.setMessage("用户支付中(付款码支付)");
break;
case "PAYERROR":
payLog.setMessage("支付失败(其他原因,如银行返回失败)");
break;
default:
break;
}
orderService.updateById(orderManagement);
payLogService.save(payLog);
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", jsonObject.getStr("trade_state_desc"));
} else {
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "签名错误");
}
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
} catch (Exception e) {
e.printStackTrace();
}
}
2.3重新封装的APP支付签名
public static Map<String, String> appPrepayIdCreateSign(String appId, String partnerId, String prepayId,
String keyPath) throws Exception {
Map<String, String> packageParams = new HashMap<>(8);
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String noncestr = PayKit.generateStr();
packageParams.put("appid", appId);
packageParams.put("partnerid", partnerId);
packageParams.put("prepayid", prepayId);
packageParams.put("package", "Sign=WXPay");
packageParams.put("noncestr", noncestr);
packageParams.put("timestamp", timestamp);
ArrayList<String> list = new ArrayList<>();
list.add(appId);
list.add(timestamp);
list.add(noncestr);
list.add(prepayId);
String packageSign = PayKit.createSign(
PayKit.buildSignMessage(list),
keyPath
);
packageParams.put("sign", packageSign);
return packageParams;
}
到这微信APP支付就结束了!!!!
支付宝APP支付
支付宝开发文档: pay.weixin.qq.com/wiki/doc/ap…
1、相关配置
引入支付宝sdk包
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.13.0.ALL</version>
</dependency>
@Configuration
public class AlipayConfig {
// 1.商户appid
public static String APPID = "";
// 2.私钥 pkcs8格式的
public static String RSA_PRIVATE_KEY = "";
// 3.支付宝公钥
public static String ALIPAY_PUBLIC_KEY = "";
// 4.服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参 数,必须外网可以正常访问
public static String notify_url = "";
// 6.请求支付宝的网关地址,测试环境可以使用沙箱环境
public static String URL = "https://openapi.alipay.com/gateway.do";
// 7.编码
public static String CHARSET = "UTF-8";
// 8.返回格式
public static String FORMAT = "json";
// 9.加密类型
public static String SIGNTYPE = "RSA2";
}
1.1测试环境可以使用支付宝沙箱环境进行验证测试
1.2正式环境的相关配置需要登录支付宝商户端去配置
支付宝开放平台->控制台->开发设置中去获取
2、核心代码
2.1获取支付宝加签后台的订单信息字符串
//记录支付宝支付记录
String orderInfo = "";
try {
// 实例化客户端
AlipayClient alipayClient = AlipayUtils.getAlipayClient();
// 实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
// SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
// model.setBody("我是测试数据");
model.setSubject(stockCar.getTheTitle());// 商品名称
model.setOutTradeNo(payDto.getOrderNo());// 交易订单号
model.setTimeoutExpress("24h");// 交易超时时间
model.setTotalAmount(payDto.getAmount().toString());// 支付金额
model.setProductCode("QUICK_MSECURITY_PAY");// 销售产品码(固定值)
request.setBizModel(model);
request.setNotifyUrl(AlipayConfig.notify_url);// 异步回调地址
// request.setReturnUrl(AlipayConfig.notify_url);
// 这里和普通的接口调用不同,使用的是sdkExecute
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
logger.info("支付宝返回加签订单号:" + response.getBody());
if (!response.isSuccess()) {
throw new RuntimeException(response.getMsg());
}
orderInfo = response.getBody();
} catch (AlipayApiException e) {
e.printStackTrace();
logger.error("===================支付宝支付接口,调用异常错误");
}
return orderInfo;
2.2支付宝支付成功后,异步请求该接口
这个接口中返回支付宝APP支付的真正结果,我们可以在这做一些业务逻辑,比如说状态的变更,日志记录等
public String notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("异步回调开始:---------------------------------------------------------------");
// 1.从支付宝回调的request域中取值
// 获取支付宝返回的参数集合
Map<String, String[]> aliParams = request.getParameterMap();
// 用以存放转化后的参数集合
Map<String, String> conversionParams = new HashMap<String, String>();
for (Iterator<String> iter = aliParams.keySet().iterator(); iter.hasNext(); ) {
String key = iter.next();
String[] values = aliParams.get(key);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
// 乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
conversionParams.put(key, valueStr);
}
String status = alipayService.notify(conversionParams);
return status;
}
@Override
public String notify(Map<String, String> conversionParams) throws Exception {
logger.info("支付宝异步回调返回结果:" + conversionParams.toString());
// 签名验证(对支付宝返回的数据验证,确定是支付宝返回的)
boolean signVerified = false;
try {
// 调用SDK验证签名
signVerified = AlipaySignature.rsaCheckV1(conversionParams, AlipayConfig.ALIPAY_PUBLIC_KEY,
AlipayConfig.CHARSET, AlipayConfig.SIGNTYPE);
} catch (AlipayApiException e) {
System.out.println(e.getErrCode());
System.out.println(e.getErrMsg());
throw new Exception("支付宝回调签名认证失败");
}
System.out.println(signVerified);
if (signVerified) {
// 验签通过
// 获取需要保存的数据
String appId = conversionParams.get("app_id");// 支付宝分配给开发者的应用Id
String notifyTime = conversionParams.get("notify_time");// 通知时间:yyyy-MM-dd HH:mm:ss
String gmtCreate = conversionParams.get("gmt_create");// 交易创建时间:yyyy-MM-dd HH:mm:ss
String gmtPayment = conversionParams.get("gmt_payment");// 交易付款时间
String gmtRefund = conversionParams.get("gmt_refund");// 交易退款时间
String gmtClose = conversionParams.get("gmt_close");// 交易结束时间
String tradeNo = conversionParams.get("trade_no");// 支付宝的交易号
String outTradeNo = conversionParams.get("out_trade_no");// 获取商户之前传给支付宝的订单号(商户系统的唯一订单号)
String outBizNo = conversionParams.get("out_biz_no");// 商户业务号(商户业务ID,主要是退款通知中返回退款申请的流水号)
String buyerLogonId = conversionParams.get("buyer_logon_id");// 买家支付宝账号
String sellerId = conversionParams.get("seller_id");// 卖家支付宝用户号
String sellerEmail = conversionParams.get("seller_email");// 卖家支付宝账号
Double totalAmount = Double.parseDouble(conversionParams.get("total_amount"));// 订单金额:本次交易支付的订单金额,单位为人民币(元)
String receiptAmount = conversionParams.get("receipt_amount");// 实收金额:商家在交易中实际收到的款项,单位为元
String invoiceAmount = conversionParams.get("invoice_amount");// 开票金额:用户在交易中支付的可开发票的金额
String buyerPayAmount = conversionParams.get("buyer_pay_amount");// 付款金额:用户在交易中支付的金额
String tradeStatus = conversionParams.get("trade_status");// 获取交易状态
// 支付宝官方建议校验的值(out_trade_no、total_amount、sellerId、app_id)
OrderManagement orderManagement = orderService.getOne(new LambdaQueryWrapper<OrderManagement>()
.eq(OrderManagement::getOrderNo, outTradeNo));
if (orderManagement != null && AlipayConfig.APPID.equals(appId) && orderManagement.getOrderNo().equals(outTradeNo)) {
PayLog payLog = new PayLog();
payLog.setAmount(orderManagement.getAmount());
payLog.setOrderNo(orderManagement.getOrderNo());
payLog.setPayType(PayTypeEnum.ALIPAY.getStatusCode());
payLog.setCustomerPhone(orderManagement.getCustomerPhone());
payLog.setPayNo(tradeNo);
// SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
// Date date2 = sdf2.parse(gmtClose);
payLog.setPayTime(new Date());
payLog.setStatus(tradeStatus);
payLog.setMessage(conversionParams.get("msg"));
payLog.setCreateTime(new Date());
// 修改数据库支付宝订单表(因为要保存每次支付宝返回的信息到数据库里,以便以后查证)
try {
orderManagement.setPayNo(tradeNo);
orderManagement.setPayType(PayTypeEnum.ALIPAY.getStatusCode());
orderManagement.setPayTime(new Date());
} catch (Exception e) {
e.printStackTrace();
}
switch (tradeStatus) // 判断交易结果
{
case "TRADE_FINISHED": // 交易结束并不可退款
payLog.setMessage("交易结束并不可退款");
break;
case "TRADE_SUCCESS": // 交易支付成功
payLog.setMessage("交易支付成功");
orderManagement.setStatus(OrderStatusEnum.DOING.getStatusCode());
break;
case "TRADE_CLOSED": // 未付款交易超时关闭或支付完成后全额退款
payLog.setMessage("未付款交易超时关闭或支付完成后全额退款");
break;
case "WAIT_BUYER_PAY": // 交易创建并等待买家付款
payLog.setMessage("交易创建并等待买家付款");
break;
default:
break;
}
boolean returnResult = orderService.updateById(orderManagement);
payLogService.save(payLog);
if (tradeStatus.equals("TRADE_SUCCESS")) { // 只处理支付成功的订单: 修改交易表状态,支付成功
if (returnResult) {
return "success";
} else {
return "fail";
}
} else {
return "fail";
}
} else {
return "fail";
}
} else { // 验签不通过
return "fail";
}
}
到这里支付宝APP支付的简单操作基本就完成了,当然还涉及一些退款、订单查询等操作,感兴趣的可以去官方文档中查看,第一次写文档有啥不足的地方求指教,谢谢!!!