微信支付

文档的地址https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_5.shtml

  1. <dependency>
  2. <groupId>com.github.wechatpay-apiv3</groupId>
  3. <artifactId>wechatpay-apache-httpclient</artifactId>
  4. <version>0.2.2</version>
  5. </dependency>

微信请求配置

微信请求的配置项,对应的枚举是证书与秘钥

  1. /**
  2. * 微信请求都从这里发送
  3. *
  4. * @author 杨胖胖
  5. */
  6. @Component
  7. public class WeChatPayConfig {
  8. public HttpClient weChatPayClient() {
  9. // 秘钥
  10. PrivateKey merchantPrivateKey = privateKey();
  11. //不需要传入微信支付证书了
  12. AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
  13. new WechatPay2Credentials(OrderEnum.MCH_ID.getInfo(), new PrivateKeySigner(OrderEnum.MCHSERIAL_NO.getInfo(), merchantPrivateKey)),
  14. OrderEnum.API_V3.getInfo().getBytes(StandardCharsets.UTF_8));
  15. // 微信请求组装
  16. WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
  17. .withMerchant(OrderEnum.MCH_ID.getInfo(), OrderEnum.MCHSERIAL_NO.getInfo(), merchantPrivateKey)
  18. .withValidator(new WechatPay2Validator(verifier));
  19. return builder.build();
  20. }
  21. public PrivateKey privateKey() {
  22. // 秘钥
  23. return PemUtil.loadPrivateKey(
  24. new ByteArrayInputStream(OrderEnum.PRIVATE_KEY.getInfo().getBytes(StandardCharsets.UTF_8)));
  25. }
  26. }

发起微信请求(根据业务配置url)

  1. // 组装http请求参数
  2. HttpPost httpPost = new HttpPost(orderEnum.getInfo());
  3. StringEntity entity = new StringEntity(weChatPayJson, StandardCharsets.UTF_8);
  4. entity.setContentType(MediaType.APPLICATION_JSON_VALUE);
  5. httpPost.setEntity(entity);
  6. httpPost.setHeader(ACCEPT, MediaType.APPLICATION_JSON_VALUE);
  7. // 请求微信返回内容
  8. String responseEntity;
  9. //完成签名并执行请求
  10. try (CloseableHttpResponse response = (CloseableHttpResponse) weChatPayConfig.weChatPayClient().execute(httpPost)) {
  11. int statusCode = response.getStatusLine().getStatusCode();
  12. // 状态码200 代表请求成功 并且有参数返回
  13. if (statusCode == ResponseEnum.SUCCESS.getCode()) {
  14. // 请求成功并赋值
  15. responseEntity = EntityUtils.toString(response.getEntity());
  16. } else {
  17. throw new RuntimeException("微信Api下单状态码非200:" + EntityUtils.toString(response.getEntity()));
  18. }
  19. }

解密微信回调的内容

  1. import java.nio.charset.StandardCharsets;
  2. import java.security.GeneralSecurityException;
  3. import java.security.InvalidAlgorithmParameterException;
  4. import java.security.InvalidKeyException;
  5. import java.security.NoSuchAlgorithmException;
  6. import java.util.Base64;
  7. import javax.crypto.Cipher;
  8. import javax.crypto.NoSuchPaddingException;
  9. import javax.crypto.spec.GCMParameterSpec;
  10. import javax.crypto.spec.SecretKeySpec;
  11. /**
  12. * @author 杨胖胖
  13. */
  14. public class AesUtil {
  15. static final int KEY_LENGTH_BYTE = 32;
  16. static final int TAG_LENGTH_BIT = 128;
  17. private final byte[] aesKey;
  18. public AesUtil(byte[] key) {
  19. if (key.length != KEY_LENGTH_BYTE) {
  20. throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
  21. }
  22. this.aesKey = key;
  23. }
  24. public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
  25. throws GeneralSecurityException {
  26. try {
  27. Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
  28. SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
  29. GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
  30. cipher.init(Cipher.DECRYPT_MODE, key, spec);
  31. cipher.updateAAD(associatedData);
  32. return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
  33. } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
  34. throw new IllegalStateException(e);
  35. } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
  36. throw new IllegalArgumentException(e);
  37. }
  38. }
  39. }

回调中解密使用

  1. //判断加密通知是否成功
  2. if (!notifyDto.getEvent_type().equals(OrderEnum.TRANSACTION_SUCCESS.getInfo())
  3. && !notifyDto.getResource_type().equals(OrderEnum.ENCRYPT_RESOURCE.getInfo())) {
  4. // 失败直接中断请求
  5. return new NotifyUrlForm(ResponseEnum.ERROR.getDesc(), ResponseEnum.FAIL.getDesc());
  6. }
  7. AesUtil aesUtil = new AesUtil(OrderEnum.API_V3.getInfo().getBytes());
  8. // 取出加密中的resource
  9. WeChatNotifyDto.ResourceDto encryptResource = notifyDto.getResource();
  10. // 解码后的resource
  11. String decrypt;
  12. try {
  13. decrypt = aesUtil.decryptToString(encryptResource.getAssociated_data().getBytes(),
  14. encryptResource.getNonce().getBytes(), encryptResource.getCiphertext());
  15. } catch (GeneralSecurityException e) {
  16. throw new RuntimeException(e);
  17. }
  18. // 解码内容转换类对象
  19. WeChatNotifyResourceDto notifyResource = JSON.parseObject(decrypt, WeChatNotifyResourceDto.class);
  20. // 解密信息中再判断一次支付状态
  21. if (!notifyResource.getTrade_state().equals(PayStateEnum.SUCCESS.getState())) {
  22. return new NotifyUrlForm(ResponseEnum.ERROR.getDesc(), ResponseEnum.FAIL.getDesc());
  23. }

签名拼接(微信下单时要拼接这段内容)

  1. /**
  2. * 微信订单签名
  3. *
  4. * @return base64签名字符串
  5. */
  6. public String signSha256Rsa(OrderEnum appId, String timestamp, String nonceStr, String prepayId) {
  7. // 微信签名拼接规则
  8. String message = appId.getInfo() + "\n" + timestamp + "\n" + nonceStr + "\n" + prepayId + "\n";
  9. try {
  10. Signature privateSignature = Signature.getInstance("SHA256withRSA");
  11. privateSignature.initSign(weChatPayConfig.privateKey());
  12. privateSignature.update(message.getBytes(StandardCharsets.US_ASCII));
  13. byte[] s = privateSignature.sign();
  14. return Base64.getEncoder().encodeToString(s);
  15. } catch (Exception e) {
  16. throw new RuntimeException(e);
  17. }
  18. }

支付宝

接口文档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

依赖

  1. <dependency>
  2. <groupId>com.alipay.sdk</groupId>
  3. <artifactId>alipay-sdk-java</artifactId>
  4. <version>4.4.2.ALL</version>
  5. </dependency>

配置项代码

注意这里是用普通与证书的区别,商户号配置的时候也得注意这个区别,用什么就配什么,沙盒的时候注意配置证书默认的创建方式就不能选就失效了

  1. /**
  2. * 扫码支付
  3. * 支付宝建议只创建一次连接,所以可以使用单例模式
  4. * @return 支付连接创建
  5. */
  6. @Bean
  7. public AlipayClient aliPayClient() {
  8. return new DefaultAlipayClient(serverUrl, appId, privateKey,
  9. AliEnum.JSON.getInfo(), AliEnum.CHARSET.getInfo(), aliPayPublicKey, AliEnum.RSA2.getInfo());
  10. }
  11. @SneakyThrows
  12. public boolean rsaCheckV1(Map<String, String> params, String signType) {
  13. return AlipaySignature.rsaCheckV1(params, aliPayPublicKey, AliEnum.CHARSET.getInfo(), signType);
  14. }

发起请求

支付宝特殊一点内部SDK调用
根据不同的业务new 出来new AlipayTradeAppPayRequest()

  1. alipayClient = aliPayConfig.aliPayClient();
  2. // 避免重复购买
  3. orderUtils(headerUtils.getUserId());
  4. AliPayUtilsDto aliPayUtilsDto = aliPaySpreadUtils(orderDto);
  5. AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
  6. request.setBizContent(JSON.toJSONString(AliPayAppDto.builder().
  7. out_trade_no(aliPayUtilsDto.getOrderId())
  8. .total_amount(aliPayUtilsDto.getAmount().toString())
  9. .subject(OrderEnum.DESCRIPTION.getInfo())
  10. .body(aliPayUtilsDto.getAttach())
  11. .timeout_express(AliEnum.TIMEOUT_EXPRESS.getInfo())
  12. .product_code(AliEnum.QUICK_MSECURITY_PAY.getInfo())
  13. .build())
  14. );
  15. request.setNotifyUrl(aliNotifyUrl);
  16. AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);

app创建订单

alipayClient.sdkExecute(request)

native创建订单

alipayClient.execute(request)

订单查询

  1. alipayClient = aliPayConfig.aliPayClient();
  2. AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
  3. String str = JSON.toJSONString(AliPayQueryDto.builder()
  4. .out_trade_no(outTradeNo)
  5. .build());
  6. request.setBizContent(str);
  7. AlipayTradeQueryResponse response = alipayClient.execute(request);

支付宝回调

支付宝的所有回调参数通过HttpServletRequest servletRequest来接受,url拼接的方式发送过来

  1. // 参数会在26个左右 26/0.75
  2. Map<String, String> params = new HashMap<>(35);
  3. // 从servlet中获取回调信息
  4. Map<String, String[]> parameterMap = servletRequest.getParameterMap();
  5. for (String name : parameterMap.keySet()) {
  6. String[] values = parameterMap.get(name);
  7. for (String value : values) {
  8. params.put(name, value);
  9. }
  10. }

支付宝验签

  1. boolean rsaCheckV1 = aliPayConfig.rsaCheckV1(params, aliPayNotifyDto.getSign_type());

支付宝回调地址与文档

https://opendocs.alipay.com/open/54/103419
一定要注意看回调通知的类型,订单关闭也会触发回调通知。

华为支付

接口地址:https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/order-verify-purchase-token-0000001050033078

华为支付的接口文档写得特别好,其实根本不需要再写文档记录了,基本上拿demo就可以调用了,但是要注意国内与海外的站点