微信支付
文档的地址https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_5.shtml
<dependency><groupId>com.github.wechatpay-apiv3</groupId><artifactId>wechatpay-apache-httpclient</artifactId><version>0.2.2</version></dependency>
微信请求配置
微信请求的配置项,对应的枚举是证书与秘钥
/*** 微信请求都从这里发送** @author 杨胖胖*/@Componentpublic class WeChatPayConfig {public HttpClient weChatPayClient() {// 秘钥PrivateKey merchantPrivateKey = privateKey();//不需要传入微信支付证书了AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(new WechatPay2Credentials(OrderEnum.MCH_ID.getInfo(), new PrivateKeySigner(OrderEnum.MCHSERIAL_NO.getInfo(), merchantPrivateKey)),OrderEnum.API_V3.getInfo().getBytes(StandardCharsets.UTF_8));// 微信请求组装WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create().withMerchant(OrderEnum.MCH_ID.getInfo(), OrderEnum.MCHSERIAL_NO.getInfo(), merchantPrivateKey).withValidator(new WechatPay2Validator(verifier));return builder.build();}public PrivateKey privateKey() {// 秘钥return PemUtil.loadPrivateKey(new ByteArrayInputStream(OrderEnum.PRIVATE_KEY.getInfo().getBytes(StandardCharsets.UTF_8)));}}
发起微信请求(根据业务配置url)
// 组装http请求参数HttpPost httpPost = new HttpPost(orderEnum.getInfo());StringEntity entity = new StringEntity(weChatPayJson, StandardCharsets.UTF_8);entity.setContentType(MediaType.APPLICATION_JSON_VALUE);httpPost.setEntity(entity);httpPost.setHeader(ACCEPT, MediaType.APPLICATION_JSON_VALUE);// 请求微信返回内容String responseEntity;//完成签名并执行请求try (CloseableHttpResponse response = (CloseableHttpResponse) weChatPayConfig.weChatPayClient().execute(httpPost)) {int statusCode = response.getStatusLine().getStatusCode();// 状态码200 代表请求成功 并且有参数返回if (statusCode == ResponseEnum.SUCCESS.getCode()) {// 请求成功并赋值responseEntity = EntityUtils.toString(response.getEntity());} else {throw new RuntimeException("微信Api下单状态码非200:" + EntityUtils.toString(response.getEntity()));}}
解密微信回调的内容
import java.nio.charset.StandardCharsets;import java.security.GeneralSecurityException;import java.security.InvalidAlgorithmParameterException;import java.security.InvalidKeyException;import java.security.NoSuchAlgorithmException;import java.util.Base64;import javax.crypto.Cipher;import javax.crypto.NoSuchPaddingException;import javax.crypto.spec.GCMParameterSpec;import javax.crypto.spec.SecretKeySpec;/*** @author 杨胖胖*/public class AesUtil {static final int KEY_LENGTH_BYTE = 32;static final int TAG_LENGTH_BIT = 128;private final byte[] aesKey;public AesUtil(byte[] key) {if (key.length != KEY_LENGTH_BYTE) {throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");}this.aesKey = key;}public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)throws GeneralSecurityException {try {Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");SecretKeySpec key = new SecretKeySpec(aesKey, "AES");GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);cipher.init(Cipher.DECRYPT_MODE, key, spec);cipher.updateAAD(associatedData);return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {throw new IllegalStateException(e);} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {throw new IllegalArgumentException(e);}}}
回调中解密使用
//判断加密通知是否成功if (!notifyDto.getEvent_type().equals(OrderEnum.TRANSACTION_SUCCESS.getInfo())&& !notifyDto.getResource_type().equals(OrderEnum.ENCRYPT_RESOURCE.getInfo())) {// 失败直接中断请求return new NotifyUrlForm(ResponseEnum.ERROR.getDesc(), ResponseEnum.FAIL.getDesc());}AesUtil aesUtil = new AesUtil(OrderEnum.API_V3.getInfo().getBytes());// 取出加密中的resourceWeChatNotifyDto.ResourceDto encryptResource = notifyDto.getResource();// 解码后的resourceString decrypt;try {decrypt = aesUtil.decryptToString(encryptResource.getAssociated_data().getBytes(),encryptResource.getNonce().getBytes(), encryptResource.getCiphertext());} catch (GeneralSecurityException e) {throw new RuntimeException(e);}// 解码内容转换类对象WeChatNotifyResourceDto notifyResource = JSON.parseObject(decrypt, WeChatNotifyResourceDto.class);// 解密信息中再判断一次支付状态if (!notifyResource.getTrade_state().equals(PayStateEnum.SUCCESS.getState())) {return new NotifyUrlForm(ResponseEnum.ERROR.getDesc(), ResponseEnum.FAIL.getDesc());}
签名拼接(微信下单时要拼接这段内容)
/*** 微信订单签名** @return base64签名字符串*/public String signSha256Rsa(OrderEnum appId, String timestamp, String nonceStr, String prepayId) {// 微信签名拼接规则String message = appId.getInfo() + "\n" + timestamp + "\n" + nonceStr + "\n" + prepayId + "\n";try {Signature privateSignature = Signature.getInstance("SHA256withRSA");privateSignature.initSign(weChatPayConfig.privateKey());privateSignature.update(message.getBytes(StandardCharsets.US_ASCII));byte[] s = privateSignature.sign();return Base64.getEncoder().encodeToString(s);} catch (Exception e) {throw new RuntimeException(e);}}
支付宝
接口文档https://opendocs.alipay.com/open/54/103419#%E6%99%AE%E9%80%9A%E8%B0%83%E7%94%A8%E7%A4%BA%E4%BE%8B
依赖
<dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-sdk-java</artifactId><version>4.4.2.ALL</version></dependency>
配置项代码
注意这里是用普通与证书的区别,商户号配置的时候也得注意这个区别,用什么就配什么,沙盒的时候注意配置证书默认的创建方式就不能选就失效了
/*** 扫码支付* 支付宝建议只创建一次连接,所以可以使用单例模式* @return 支付连接创建*/@Beanpublic AlipayClient aliPayClient() {return new DefaultAlipayClient(serverUrl, appId, privateKey,AliEnum.JSON.getInfo(), AliEnum.CHARSET.getInfo(), aliPayPublicKey, AliEnum.RSA2.getInfo());}@SneakyThrowspublic boolean rsaCheckV1(Map<String, String> params, String signType) {return AlipaySignature.rsaCheckV1(params, aliPayPublicKey, AliEnum.CHARSET.getInfo(), signType);}
发起请求
支付宝特殊一点内部SDK调用
根据不同的业务new 出来new AlipayTradeAppPayRequest()
alipayClient = aliPayConfig.aliPayClient();// 避免重复购买orderUtils(headerUtils.getUserId());AliPayUtilsDto aliPayUtilsDto = aliPaySpreadUtils(orderDto);AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();request.setBizContent(JSON.toJSONString(AliPayAppDto.builder().out_trade_no(aliPayUtilsDto.getOrderId()).total_amount(aliPayUtilsDto.getAmount().toString()).subject(OrderEnum.DESCRIPTION.getInfo()).body(aliPayUtilsDto.getAttach()).timeout_express(AliEnum.TIMEOUT_EXPRESS.getInfo()).product_code(AliEnum.QUICK_MSECURITY_PAY.getInfo()).build()));request.setNotifyUrl(aliNotifyUrl);AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
app创建订单
alipayClient.sdkExecute(request)
native创建订单
alipayClient.execute(request)
订单查询
alipayClient = aliPayConfig.aliPayClient();AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();String str = JSON.toJSONString(AliPayQueryDto.builder().out_trade_no(outTradeNo).build());request.setBizContent(str);AlipayTradeQueryResponse response = alipayClient.execute(request);
支付宝回调
支付宝的所有回调参数通过HttpServletRequest servletRequest来接受,url拼接的方式发送过来
// 参数会在26个左右 26/0.75Map<String, String> params = new HashMap<>(35);// 从servlet中获取回调信息Map<String, String[]> parameterMap = servletRequest.getParameterMap();for (String name : parameterMap.keySet()) {String[] values = parameterMap.get(name);for (String value : values) {params.put(name, value);}}
支付宝验签
boolean rsaCheckV1 = aliPayConfig.rsaCheckV1(params, aliPayNotifyDto.getSign_type());
支付宝回调地址与文档
https://opendocs.alipay.com/open/54/103419
一定要注意看回调通知的类型,订单关闭也会触发回调通知。
华为支付
华为支付的接口文档写得特别好,其实根本不需要再写文档记录了,基本上拿demo就可以调用了,但是要注意国内与海外的站点
