作者 徐宝林

支付功能复制

@@ -234,6 +234,20 @@ @@ -234,6 +234,20 @@
234 <type>pom</type> 234 <type>pom</type>
235 </dependency> 235 </dependency>
236 236
  237 + <!-- 微信支付 -->
  238 + <dependency>
  239 + <groupId>com.github.wechatpay-apiv3</groupId>
  240 + <artifactId>wechatpay-java</artifactId>
  241 + <version>0.2.17</version>
  242 + </dependency>
  243 +
  244 + <!-- 支付宝支付 -->
  245 + <dependency>
  246 + <groupId>com.alipay.sdk</groupId>
  247 + <artifactId>alipay-sdk-java</artifactId>
  248 + <version>4.40.354.ALL</version>
  249 + </dependency>
  250 +
237 <!-- Test --> 251 <!-- Test -->
238 <dependency> 252 <dependency>
239 <groupId>org.springframework.boot</groupId> 253 <groupId>org.springframework.boot</groupId>
  1 +package com.aigeo.entity;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.io.Serializable;
  6 +
  7 +@Data
  8 +public class ApiConfig implements Serializable {
  9 + /**
  10 + * 主键id
  11 + */
  12 + private Integer id;
  13 +
  14 + /**
  15 + * 接口类型(1.文档转换 2.文档扫描 3.图片识别 4.智能翻译 5.短信 6.微信 7.邮件 8.EDM)
  16 + */
  17 + private Integer apiType;
  18 +
  19 + /*
  20 + * 接口类型子模块
  21 + * */
  22 + private Integer apiClassify;
  23 +
  24 + /**
  25 + * 接口提供方
  26 + */
  27 + private String apiName;
  28 +
  29 + /**
  30 + * 企业id
  31 + */
  32 + private Integer companyId;
  33 +
  34 + /**
  35 + * API的ID号
  36 + */
  37 + private String apiId;
  38 +
  39 + /**
  40 + * API的key
  41 + */
  42 + private String apiKey;
  43 +
  44 + /**
  45 + * API的code值
  46 + */
  47 + private String apiCode;
  48 +
  49 + /**
  50 + * API的host
  51 + */
  52 + private String apiUrl;
  53 +
  54 + /**
  55 + * API的方法名
  56 + */
  57 + private String apiPath;
  58 +
  59 + /*
  60 + * Api回调地址
  61 + * */
  62 + private String apiRedirectUri;
  63 +
  64 + /**
  65 + * API其它
  66 + */
  67 + private String apiTest;
  68 +
  69 + /**
  70 + * API的更新日期
  71 + */
  72 + private String updateTime;
  73 +
  74 + /**
  75 + * 备注说明
  76 + */
  77 + private String remarks;
  78 +
  79 + /*
  80 + * 系统标识
  81 + * */
  82 + private String code;
  83 +
  84 + /*
  85 + * 是否启用(0:否 1:是)
  86 + * */
  87 + private Integer isEnable;
  88 +
  89 + //--------------------20250804新增以下微信支付相关配置字段----------------------
  90 + /**
  91 + * 微信支付私钥路径
  92 + */
  93 + private String privateKeyPath;
  94 + /**
  95 + * 微信支付公钥路径
  96 + */
  97 + private String publicKeyPath;
  98 + /**
  99 + * 微信退款回调地址
  100 + */
  101 + private String refundRedirectUrl;
  102 + /**
  103 + * 商户号
  104 + */
  105 + private String merchantId;
  106 + //--------------------20250804新增以上微信支付相关配置字段----------------------
  107 +
  108 + private static final long serialVersionUID = 1L;
  109 +}
  1 +package com.aigeo.entity;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class PayDto {
  7 + private String desc;//描述
  8 + private String amount;//金额
  9 +
  10 + private String orderId;
  11 +
  12 + private ApiConfig apiConfig;
  13 +
  14 + private Integer companyId;
  15 +}
  1 +package com.aigeo.entity;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class PayPalDTO {
  7 + private String intent;
  8 + private PaymentSource payment_source;
  9 + private PurchaseUnit[] purchase_units;
  10 +
  11 + @Data
  12 + public static class PaymentSource {
  13 + private Paypal paypal;
  14 +
  15 + @Data
  16 + public static class Paypal {
  17 + private ExperienceContext experience_context;
  18 +
  19 + @Data
  20 + public static class ExperienceContext {
  21 + private String return_url;
  22 + private String cancel_url;
  23 + }
  24 + }
  25 + }
  26 +
  27 + @Data
  28 + public static class PurchaseUnit {
  29 + private String custom_id;
  30 + private Amount amount;
  31 +
  32 + @Data
  33 + public static class Amount {
  34 + private String currency_code;
  35 +
  36 + private String value;
  37 + }
  38 + }
  39 +}
  40 +
  1 +package com.aigeo.entity;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class PayPalOrderRecord {
  7 + private Long id;
  8 + private Integer companyId;
  9 + private String paypalId;
  10 + private String payerId;
  11 + private String orderId;
  12 + private String createTime;
  13 + private String updateTime;
  14 + private Integer state;
  15 +}
  1 +package com.aigeo.entity;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class PayPalResponse {
  7 + private String id;
  8 + private String status;
  9 + private ResMsg[] links;
  10 +
  11 + @Data
  12 + public static class ResMsg{
  13 + private String href;
  14 + private String rel;
  15 + private String method;
  16 + }
  17 +}
  1 +package com.aigeo.util.pay;
  2 +
  3 +import com.aigeo.entity.ApiConfig;
  4 +import com.aigeo.entity.PayDto;
  5 +import com.alipay.api.AlipayClient;
  6 +import com.alipay.api.AlipayConfig;
  7 +import com.alipay.api.DefaultAlipayClient;
  8 +import com.alipay.api.diagnosis.DiagnosisUtils;
  9 +import com.alipay.api.domain.AlipayTradePrecreateModel;
  10 +import com.alipay.api.request.AlipayTradePrecreateRequest;
  11 +import com.alipay.api.response.AlipayTradePrecreateResponse;
  12 +
  13 +import lombok.extern.slf4j.Slf4j;
  14 +import org.springframework.stereotype.Component;
  15 +
  16 +import java.io.File;
  17 +import java.io.IOException;
  18 +import java.nio.charset.StandardCharsets;
  19 +import java.nio.file.Files;
  20 +import java.nio.file.Paths;
  21 +import java.util.HashMap;
  22 +import java.util.Map;
  23 +
  24 +@Slf4j
  25 +@Component
  26 +public class AliPay implements PaymentStrategy{
  27 +
  28 + // 环境切换开关,"sandbox" 或 "prod",根据实际环境设置
  29 + private static final String environment = "sandbox";
  30 + // 定义环境配置
  31 + public static Map<String, Map<String, String>> CONFIG_MAG = new HashMap<String, Map<String, String>>();
  32 +
  33 + static {
  34 + // 沙箱环境配置
  35 + Map<String, String> sandbox = new HashMap<String, String>();
  36 + // AppId,对应配置项为“应用信息”。
  37 + sandbox.put("appId", "9021000123615361");
  38 + // 支付宝沙箱网关地址(sandboxAlipayGateway),对应配置项为“支付宝网关”。
  39 + sandbox.put("gatewayUrl", "https://openapi-sandbox.dl.alipaydev.com/gateway.do");
  40 + // 支付宝沙箱公钥(sandboxAlipayPublicKey),对应配置项为“密钥配置”。
  41 + sandbox.put("alipayPublicKey", "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkksZzpOGWoGMz9lz7y79DtaXuvsOs4d4ChdYir3/FSRHbYn/4G25LYw+7h00s8JCXIPhLoozWi9ZFN3hEMUHreT0/UPk0LgX7AS43VQHmR0cLhOOf2o5zxpzN9SjU/zK3ZQT0rEuNyoikPaY/UK4fqHvySBc7OfeKefDqdS/v/BchwzlzefEqXg4imz0uUMhn8micUuAeT7iF72NxlqCBmRXzOBx/CaUCOA2BMn2b3KDUix2qiy8FzkPQRj5dXQbDu8JFsHDAU/T9as8JY1ZRDI3wPgHverb5SuyFydeQsIEHSmC89k+TySa4bqbQRZ5+LOFzbkyrCXXQ9y1V/4jgQIDAQAB");
  42 + // 应用沙箱私钥(sandboxAppPrivateKey),对应配置项为“密钥配置”。
  43 + sandbox.put("appPrivateKey", "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCl7rLc1vU1YMij5nGna2Tiqw08TgCEoaviLgHaenBJi8JfUDndm01XAZ6Vfia53F4yvj8woPkiOzn/HYEfqs3+SAPbdQF/KyZ6GocAxwZBAhXTLD+c/D3pRX9lrOo+krw6H8JoJeVrDD2v4N1fWJBfh8U4x+pMcysYnDZbS9xquGNaPIpEnGZ7FHCvQo5Uq2xDv4iApwmiWdwNcgN9xatCy5deM9ot08cd+rL9NcnFnuWdxeQVBLvYS6w28kJUKKOIIUXKhWkpVTspZksUJRqKevlgw+wL2ORFB0CiAV9l7wugdt0V2KqLeroUNuOYkh9fBTwXTEv/NHZ+mUjmECETAgMBAAECggEAFdmm5/nOdGb4RMz1rmwv7sFDjutemhiH/9K7H7/7xVOIFvEDjbEDsC9gaKMiCluAdAIlZAvugpl1+6gw9rCRS6jECUQRHGPcPzv7BxkuM6VIpnQvNEbrCCrEqejpN7Au5SCFgx1qZnxIKz7bJ9ELweipCd8ZdVO9GxKG/eKxdcP3ZntujxkcSsgaYLKp7KozWmFYTFphGTsRjrJYofg6KpIONxDIigQfNWpyBHCre13lAsP+nxccOCw1Lx2/m1Xda0U7XWDeRePiutrsOV2KPZa59qv15tezjsIs0yhQnTPmDLMRQ2a5oJLRuPRreLLJwDGsG0FZXNUEQK6mWnD4gQKBgQDqXsNwmgTEPW/jcq2Myh4iLlY4Ag3kR7iVOsFn4PbZrqf+B52wSepp4uT9+10z6nuKADMWyUMS+jwPAeoWlhygUkhdB8L50KDcXztsz9NGQ+4LX9OGJncp616Lw3pdIFMkptPuCvjUISi/xy/24VbkgLzosfr2a1rIf7QwHpwI8wKBgQC1PwXaw1eEstzwK/+ov0ptIRQKQlw7frJHx3rRPFCu94G6pbNOLN2FhzZ5D0sIAHlQFhkUR34qOTw/bgS1WduUHWKH+pSGhW7g/hb2G6/gKsGgTUvdW8/jVQoTdS++iKJkI6q08lAdZG769c+PH1gb/RdArhHk979OTwUpmIuPYQKBgQC3/jE8ow1+6iR+0TbCQqKpfusdCS/SuSOFtBzF6mygtGcaHOyEy2KGQiQ36DBhY4Ic8s8o4lQP//dKaNxXYv2SN0asEj3VVR4UI4dJg1Z/4TBuKYqr1GLUu/z7iDj2Bzx+l6HlTSPPlUCiXkOfc4R92ztzCtbUwa8BnI/e/wjMVQKBgE1J+conIOwShgrZCGXCZQ+SWoRhdglc5tObKjGNoe5q12RrgsHkefaWCVGohtt7sD/JnJo8Pn3s+FcoKFPZZLyNp1XLMokG35iMYehDOEXEldsw+xgDLeyIeAqCH4EFrRI872/IIQfXq0fTiNZEjIMq8z5+vFzClU7kfZQWW/UhAoGBANjKR0NJKOMBTGJhSuSL4mL96ayoUoiYDXkQMVvP0oGAt6jdq34lyl+e4n02yhd5IHIDOXrIdx2xnd+HAOBI1YPAYMJ4gHC8WQM9dbjxtqyyNGSQnpVLCAdxvVInCrJdYR82+26oPIUbMtOHVkjtCr47wXlSHb82Ax9UgL5Bhl7N");
  44 + // 线上环境配置
  45 + Map<String, String> prod = new HashMap<String, String>();
  46 + // AppId,对应配置项为“应用信息”。
  47 + prod.put("appId", "${appId}");
  48 + // 支付宝线上网关地址(prodAlipayGateway),对应配置项为“支付宝网关”。
  49 + prod.put("gatewayUrl", "${prodAlipayGateway}");
  50 + // 支付宝线上公钥(prodAlipayPublicKey),对应配置项为“密钥配置”。
  51 + prod.put("alipayPublicKey", "${prodAlipayPublicKey}");
  52 + // 应用线上私钥(prodAppPrivateKey),需要替换为线上环境使用的应用私钥,对应配置项为“密钥配置”,注意 Java 语言需要 PKCS8 格式的密钥。
  53 + prod.put("appPrivateKey", "${prodAppPrivateKey}");
  54 + CONFIG_MAG.put("sandbox", sandbox);
  55 + CONFIG_MAG.put("prod", prod);
  56 + }
  57 +
  58 + @Override
  59 + public String pay(PayDto payDto) {
  60 + try {
  61 + AlipayConfig alipayConfig = new AlipayConfig();
  62 + ApiConfig config = payDto.getApiConfig();
  63 + alipayConfig.setServerUrl(config.getApiUrl());
  64 + alipayConfig.setAppId(config.getApiId());
  65 +
  66 + String path = this.getClass().getResource("/").getPath();
  67 + path = path.replace("/", "//");
  68 + path = path.replace("//target//classes//", "");
  69 + path = path.replace("//WEB-INF//classes//", "");
  70 + //获取秘钥内容
  71 + String publicKey = getKey(path+config.getPublicKeyPath());
  72 + String privateKey = getKey(path+config.getPrivateKeyPath());
  73 +
  74 + alipayConfig.setPrivateKey(privateKey);
  75 + alipayConfig.setAlipayPublicKey(publicKey);
  76 +
  77 + alipayConfig.setFormat("json");
  78 + alipayConfig.setCharset("UTF-8");
  79 + alipayConfig.setSignType("RSA2");
  80 + // 初始化SDK
  81 + AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);
  82 + // 构造请求参数以调用接口
  83 + AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
  84 + AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
  85 +
  86 + // 设置商户订单号
  87 + model.setOutTradeNo(payDto.getOrderId());
  88 +
  89 + model.setProductCode("QR_CODE_OFFLINE");
  90 + // 设置订单总金额
  91 + model.setTotalAmount(payDto.getAmount());
  92 + // 设置订单标题
  93 + model.setSubject(payDto.getOrderId());
  94 + // 设置商户的原始订单号
  95 + model.setMerchantOrderNo(payDto.getOrderId());
  96 +
  97 + request.setBizModel(model);
  98 + request.setNotifyUrl(config.getApiRedirectUri()+"/"+payDto.getCompanyId());
  99 + AlipayTradePrecreateResponse response = alipayClient.execute(request);
  100 + System.out.println(response.getBody());
  101 + if (response.isSuccess()) {
  102 + log.info("订单号:{}请求支付宝支付",response.getOutTradeNo());
  103 + return response.getQrCode();
  104 + } else {
  105 + System.out.println(response.getSubMsg());
  106 + System.out.println("调用失败");
  107 + String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response);
  108 + System.out.println(diagnosisUrl);
  109 + return response.getSubMsg();
  110 + }
  111 +
  112 + } catch (Exception e) {
  113 + e.printStackTrace();
  114 + return null;
  115 + }
  116 + }
  117 +
  118 + @Override
  119 + public void closeTrade(String orderId, String mchId, ApiConfig apiConfig) {
  120 +
  121 + }
  122 +
  123 + @Override
  124 + public void refund() {
  125 +
  126 + }
  127 +
  128 + @Override
  129 + public String getChannel() {
  130 + return "alipay";
  131 + }
  132 +
  133 + private String getKey(String filePath) throws IOException{
  134 + String fixed = filePath
  135 + .replaceAll("^/+([A-Za-z]):/+", "$1:/") // 去掉前导 // 并保留盘符
  136 + .replace('/', File.separatorChar); // 把 / 换成平台分隔符
  137 +
  138 + // 2. 读取
  139 + byte[] bytes = Files.readAllBytes(Paths.get(fixed));
  140 + return new String(bytes, StandardCharsets.UTF_8);
  141 + }
  142 +}
  1 +package com.aigeo.util.pay;
  2 +
  3 +
  4 +import javax.crypto.*;
  5 +import javax.crypto.spec.GCMParameterSpec;
  6 +import javax.crypto.spec.SecretKeySpec;
  7 +import java.nio.charset.StandardCharsets;
  8 +import java.nio.file.Files;
  9 +import java.nio.file.Paths;
  10 +import java.security.*;
  11 +import java.security.spec.X509EncodedKeySpec;
  12 +import java.util.Base64;
  13 +
  14 +/**
  15 + * 项目名称: 微信支付回调解密
  16 + * 版权所有: 深圳市科飞时速网络技术有限公司(0755-88843776)
  17 + * 程序版本: V3.0
  18 + * 技术支持: info@21gmail.com
  19 + * 单元名称: 支付接口
  20 + * 开始时间: 2025/08/04 15:13
  21 + * 最后修改: 2025/08/04 15:13
  22 + * 开发人员: 温志锋
  23 + * 备 注: 如需修改请联系开发人员
  24 + */
  25 +public class CheckUtil {
  26 + /**
  27 + * 验签
  28 + * @param wechatPublicKeyPath 公钥文件路径
  29 + * @param timestamp 时间戳
  30 + * @param nonce 验签的随机字符串
  31 + * @param body 请求体
  32 + * @param signature 签名值
  33 + * @return 验签成功
  34 + */
  35 + public static boolean verifySignature(String wechatPublicKeyPath, String timestamp, String nonce, String body, String signature){
  36 + try{
  37 + // 1. 构建验签串
  38 + String signMessage = buildSignMessage(timestamp, nonce, body);
  39 + // 2. 解码Base64签名
  40 + byte[] signatureBytes = Base64.getDecoder().decode(signature);
  41 + // 3. 加载微信支付公钥
  42 + PublicKey publicKey = loadPublicKey(wechatPublicKeyPath);
  43 + // 4. 执行验签
  44 + Signature signer = Signature.getInstance("SHA256withRSA");
  45 +
  46 + signer.initVerify(publicKey);
  47 +
  48 + signer.update(signMessage.getBytes(StandardCharsets.UTF_8));
  49 +
  50 + return signer.verify(signatureBytes);
  51 + }catch(Exception e){
  52 + e.printStackTrace();
  53 + System.out.println("---------验签异常---------"+e);
  54 + return false;
  55 + }
  56 + }
  57 +
  58 + /**
  59 + * 解密方法
  60 + * @param ciphertext 数据密文
  61 + * @param associatedData 附加数据
  62 + * @param nonce 随机串
  63 + * @param vkey v3key
  64 + * @return 解密数据
  65 + * @throws NoSuchPaddingException
  66 + * @throws NoSuchAlgorithmException
  67 + * @throws InvalidAlgorithmParameterException
  68 + * @throws InvalidKeyException
  69 + * @throws BadPaddingException
  70 + * @throws IllegalBlockSizeException
  71 + */
  72 + public static String decryptData(String ciphertext, String associatedData, String nonce,String vkey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
  73 + byte[] keyBytes = vkey.getBytes(StandardCharsets.UTF_8);
  74 + SecretKey key = new SecretKeySpec(keyBytes, "AES");
  75 +
  76 + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
  77 + GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));
  78 + cipher.init(Cipher.DECRYPT_MODE, key, spec);
  79 +
  80 + if (associatedData != null) {
  81 + cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
  82 + }
  83 +
  84 + byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
  85 + return new String(decrypted, StandardCharsets.UTF_8);
  86 + }
  87 +
  88 +
  89 +
  90 + /**
  91 + * 构建验签串
  92 + * 格式: timestamp + "\n" + nonce + "\n" + body + "\n"
  93 + */
  94 + private static String buildSignMessage(String timestamp, String nonce, String body) {
  95 + // 处理空body情况(如204响应)
  96 + if (body == null) {
  97 + body = "";
  98 + }
  99 + return timestamp + "\n" + nonce + "\n" + body + "\n";
  100 + }
  101 +
  102 + /**
  103 + * 从PEM格式字符串加载公钥
  104 + */
  105 + private static PublicKey loadPublicKey(String filePath) throws GeneralSecurityException {
  106 + try {
  107 + String pemContent = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
  108 + //1. 移除PEM头尾标记和空白字符
  109 + String publicKeyPEM = pemContent
  110 + .replace("-----BEGIN PUBLIC KEY-----", "")
  111 + .replace("-----END PUBLIC KEY-----", "")
  112 + .replaceAll("\\s", "");
  113 + System.out.println("----公钥:"+publicKeyPEM);
  114 +
  115 + // 2. Base64解码
  116 + byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
  117 +
  118 + // 3. 创建KeyFactory
  119 + KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  120 +
  121 + // 4. 生成公钥
  122 + return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
  123 + //byte[] certBytes = Files.readAllBytes(Paths.get(filePath));
  124 + //CertificateFactory cf = CertificateFactory.getInstance("X.509");
  125 + //X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certBytes));
  126 + //return cert.getPublicKey();
  127 + } catch (Exception e) {
  128 + e.printStackTrace();
  129 + throw new GeneralSecurityException("加载微信支付公钥失败", e);
  130 + }
  131 + }
  132 +}
  1 +package com.aigeo.util.pay;
  2 +
  3 +
  4 +import org.apache.http.HttpEntity;
  5 +import org.apache.http.HttpResponse;
  6 +import org.apache.http.ParseException;
  7 +import org.apache.http.client.ClientProtocolException;
  8 +import org.apache.http.client.HttpClient;
  9 +import org.apache.http.client.methods.HttpGet;
  10 +import org.apache.http.client.methods.HttpPost;
  11 +import org.apache.http.entity.StringEntity;
  12 +import org.apache.http.impl.client.DefaultHttpClient;
  13 +import org.apache.http.params.CoreConnectionPNames;
  14 +import org.apache.http.protocol.HTTP;
  15 +import org.apache.http.util.EntityUtils;
  16 +
  17 +import javax.net.ssl.HttpsURLConnection;
  18 +import javax.net.ssl.KeyManagerFactory;
  19 +import javax.net.ssl.SSLContext;
  20 +import javax.net.ssl.TrustManagerFactory;
  21 +import java.io.*;
  22 +import java.net.HttpURLConnection;
  23 +import java.net.URL;
  24 +import java.security.*;
  25 +import java.security.cert.Certificate;
  26 +import java.security.cert.CertificateException;
  27 +import java.security.cert.CertificateFactory;
  28 +import java.util.HashMap;
  29 +import java.util.Map;
  30 +
  31 +
  32 +public class HttpClientUtils
  33 +{
  34 + public static final String SunX509 = "SunX509";
  35 + public static final String JKS = "JKS";
  36 + public static final String PKCS12 = "PKCS12";
  37 + public static final String TLS = "TLS";
  38 +
  39 + public static HttpURLConnection getHttpURLConnection(String strUrl)
  40 + throws IOException
  41 + {
  42 + URL url = new URL(strUrl);
  43 + HttpURLConnection httpURLConnection = (HttpURLConnection)url
  44 + .openConnection();
  45 + return httpURLConnection;
  46 + }
  47 +
  48 +
  49 +
  50 + /**
  51 + * 发送HTTP_GET请求
  52 + *
  53 + * @see
  54 + * @param reqURL
  55 + * 请求地址(含参数)
  56 + * @param decodeCharset
  57 + * 解码字符集,解析响应数据时用之,其为null时默认采用UTF-8解码
  58 + * @return 远程主机响应正文
  59 + */
  60 + public static String sendGetRequest(String reqURL, String decodeCharset) {
  61 + long responseLength = 0; // 响应长度
  62 + String responseContent = null; // 响应内容
  63 + HttpClient httpClient = new DefaultHttpClient(); // 创建默认的httpClient实例
  64 + HttpGet httpGet = new HttpGet(reqURL); // 创建org.apache.http.client.methods.HttpGet
  65 + try {
  66 + HttpResponse response = httpClient.execute(httpGet); // 执行GET请求
  67 + HttpEntity entity = response.getEntity(); // 获取响应实体
  68 + if (null != entity) {
  69 + responseLength = entity.getContentLength();
  70 + responseContent = EntityUtils.toString(entity, decodeCharset == null ? "UTF-8" : decodeCharset);
  71 + EntityUtils.consume(entity); // Consume response content
  72 + }
  73 + System.out.println("请求地址: " + httpGet.getURI());
  74 + System.out.println("响应状态: " + response.getStatusLine());
  75 + System.out.println("响应长度: " + responseLength);
  76 + System.out.println("响应内容: " + responseContent);
  77 + } catch (ClientProtocolException e) {
  78 + System.out.println("该异常通常是协议错误导致,比如构造HttpGet对象时传入的协议不对(将'http'写成'htp')或者服务器端返回的内容不符合HTTP协议要求等,堆栈信息如下");
  79 + } catch (ParseException e) {
  80 + System.out.println(e.getMessage());
  81 + } catch (IOException e) {
  82 + System.out.println("该异常通常是网络原因引起的,如HTTP服务器未启动等,堆栈信息如下");
  83 + } finally {
  84 + httpClient.getConnectionManager().shutdown(); // 关闭连接,释放资源
  85 + }
  86 + return responseContent;
  87 + }
  88 +
  89 + public static String sendPostRequest(String reqURL, String sendData, boolean isEncoder) {
  90 + return sendPostRequest(reqURL, sendData, isEncoder, null, null,"application/json");
  91 + }
  92 +
  93 + public static String sendPostRequest(String reqURL, String sendData, boolean isEncoder, String encodeCharset,
  94 + String decodeCharset,String contentType) {
  95 + String responseContent = null;
  96 + HttpClient httpClient = new DefaultHttpClient();
  97 +
  98 + HttpPost httpPost = new HttpPost(reqURL);
  99 + // httpPost.setHeader(HTTP.CONTENT_TYPE,
  100 + // "application/x-www-form-urlencoded; charset=UTF-8");
  101 + httpPost.setHeader(HTTP.CONTENT_TYPE, contentType);
  102 + try {
  103 + if (isEncoder) {
  104 + httpPost.setEntity(new StringEntity(sendData, encodeCharset == null ? "UTF-8" : encodeCharset));
  105 + } else {
  106 + httpPost.setEntity(new StringEntity(sendData));
  107 + }
  108 + // 设置请求超时时间
  109 + httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 5000);
  110 + HttpResponse response = httpClient.execute(httpPost);
  111 + HttpEntity entity = response.getEntity();
  112 + if (null != entity) {
  113 + responseContent = EntityUtils.toString(entity, decodeCharset == null ? "UTF-8" : decodeCharset);
  114 + EntityUtils.consume(entity);
  115 + }
  116 + } catch (Exception e) {
  117 + System.out.println("与[" + reqURL + "]通信过程中发生异常,堆栈信息如下");
  118 + e.printStackTrace();
  119 + } finally {
  120 + httpClient.getConnectionManager().shutdown();
  121 + }
  122 + return responseContent;
  123 + }
  124 +
  125 +
  126 + public static HttpsURLConnection getHttpsURLConnection(String strUrl)
  127 + throws IOException
  128 + {
  129 + URL url = new URL(strUrl);
  130 + HttpsURLConnection httpsURLConnection = (HttpsURLConnection)url
  131 + .openConnection();
  132 + return httpsURLConnection;
  133 + }
  134 +
  135 +
  136 +
  137 +
  138 + public static String getURL(String strUrl)
  139 + {
  140 + if (strUrl != null) {
  141 + int indexOf = strUrl.indexOf("?");
  142 + if (-1 != indexOf) {
  143 + return strUrl.substring(0, indexOf);
  144 + }
  145 +
  146 + return strUrl;
  147 + }
  148 +
  149 + return strUrl;
  150 + }
  151 +
  152 +
  153 +
  154 + public static String getQueryString(String strUrl)
  155 + {
  156 + if (strUrl != null) {
  157 + int indexOf = strUrl.indexOf("?");
  158 + if (-1 != indexOf) {
  159 + return strUrl.substring(indexOf + 1, strUrl.length());
  160 + }
  161 +
  162 + return "";
  163 + }
  164 +
  165 + return strUrl;
  166 + }
  167 +
  168 +
  169 +
  170 + public static Map queryString2Map(String queryString)
  171 + {
  172 + if ((queryString == null) || ("".equals(queryString))) {
  173 + return null;
  174 + }
  175 +
  176 + Map m = new HashMap();
  177 + String[] strArray = queryString.split("&");
  178 + for (int index = 0; index < strArray.length; index++) {
  179 + String pair = strArray[index];
  180 + putMapByPair(pair, m);
  181 + }
  182 +
  183 + return m;
  184 + }
  185 +
  186 +
  187 +
  188 + public static void putMapByPair(String pair, Map m)
  189 + {
  190 + if ((pair == null) || ("".equals(pair))) {
  191 + return;
  192 + }
  193 +
  194 + int indexOf = pair.indexOf("=");
  195 + if (-1 != indexOf) {
  196 + String k = pair.substring(0, indexOf);
  197 + String v = pair.substring(indexOf + 1, pair.length());
  198 + if ((k != null) && (!"".equals(k))) {
  199 + m.put(k, v);
  200 + }
  201 + } else {
  202 + m.put(pair, "");
  203 + }
  204 + }
  205 +
  206 +
  207 +
  208 +
  209 + public static String bufferedReader2String(BufferedReader reader)
  210 + throws IOException
  211 + {
  212 + StringBuffer buf = new StringBuffer();
  213 + String line = null;
  214 + while ((line = reader.readLine()) != null) {
  215 + buf.append(line);
  216 + buf.append("\r\n");
  217 + }
  218 +
  219 + return buf.toString();
  220 + }
  221 +
  222 +
  223 +
  224 +
  225 + public static void doOutput(OutputStream out, byte[] data, int len)
  226 + throws IOException
  227 + {
  228 + int dataLen = data.length;
  229 + int off = 0;
  230 + while (off < dataLen) {
  231 + if (len >= dataLen) {
  232 + out.write(data, off, dataLen);
  233 + } else {
  234 + out.write(data, off, len);
  235 + }
  236 +
  237 +
  238 + out.flush();
  239 +
  240 + off += len;
  241 +
  242 + dataLen -= len;
  243 + }
  244 + }
  245 +
  246 +
  247 +
  248 +
  249 + public static SSLContext getSSLContext(FileInputStream trustFileInputStream, String trustPasswd, FileInputStream keyFileInputStream, String keyPasswd)
  250 + throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException, KeyManagementException
  251 + {
  252 + TrustManagerFactory tmf =
  253 + TrustManagerFactory.getInstance("SunX509");
  254 + KeyStore trustKeyStore = KeyStore.getInstance("JKS");
  255 + trustKeyStore.load(trustFileInputStream,
  256 + str2CharArray(trustPasswd));
  257 + tmf.init(trustKeyStore);
  258 +
  259 + char[] kp = str2CharArray(keyPasswd);
  260 + KeyManagerFactory kmf =
  261 + KeyManagerFactory.getInstance("SunX509");
  262 + KeyStore ks = KeyStore.getInstance("PKCS12");
  263 + ks.load(keyFileInputStream, kp);
  264 + kmf.init(ks, kp);
  265 +
  266 + SecureRandom rand = new SecureRandom();
  267 + SSLContext ctx = SSLContext.getInstance("TLS");
  268 + ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), rand);
  269 +
  270 + return ctx;
  271 + }
  272 +
  273 +
  274 +
  275 +
  276 +
  277 +
  278 + public static Certificate getCertificate(File cafile)
  279 + throws CertificateException, IOException
  280 + {
  281 + CertificateFactory cf = CertificateFactory.getInstance("X.509");
  282 + FileInputStream in = new FileInputStream(cafile);
  283 + Certificate cert = cf.generateCertificate(in);
  284 + in.close();
  285 + return cert;
  286 + }
  287 +
  288 +
  289 +
  290 +
  291 +
  292 +
  293 + public static char[] str2CharArray(String str)
  294 + {
  295 + if (str == null) {
  296 + return null;
  297 + }
  298 + return str.toCharArray();
  299 + }
  300 +
  301 +
  302 +
  303 +
  304 +
  305 +
  306 +
  307 + public static void storeCACert(Certificate cert, String alias, String password, OutputStream out)
  308 + throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException
  309 + {
  310 + KeyStore ks = KeyStore.getInstance("JKS");
  311 +
  312 + ks.load(null, null);
  313 +
  314 + ks.setCertificateEntry(alias, cert);
  315 +
  316 +
  317 + ks.store(out, str2CharArray(password));
  318 + }
  319 +
  320 + public static InputStream String2Inputstream(String str)
  321 + {
  322 + return new ByteArrayInputStream(str.getBytes());
  323 + }
  324 +}
  1 +package com.aigeo.util.pay;
  2 +
  3 +import cn.hutool.core.codec.Base64;
  4 +import cn.hutool.core.io.FileUtil;
  5 +import cn.hutool.core.util.CharsetUtil;
  6 +import cn.hutool.core.util.IdUtil;
  7 +import cn.hutool.core.util.StrUtil;
  8 +
  9 +import javax.crypto.BadPaddingException;
  10 +import javax.crypto.Cipher;
  11 +import javax.crypto.IllegalBlockSizeException;
  12 +import javax.crypto.NoSuchPaddingException;
  13 +import java.io.InputStream;
  14 +import java.io.UnsupportedEncodingException;
  15 +import java.net.URLEncoder;
  16 +import java.nio.charset.StandardCharsets;
  17 +import java.security.InvalidKeyException;
  18 +import java.security.NoSuchAlgorithmException;
  19 +import java.security.PrivateKey;
  20 +import java.security.cert.*;
  21 +import java.util.*;
  22 +
  23 +public class PayKit {
  24 +
  25 + /**
  26 + * 获取证书
  27 + *
  28 + * @param inputStream 证书文件
  29 + * @return {@link X509Certificate} 获取证书
  30 + */
  31 + public static X509Certificate getCertificate(InputStream inputStream) {
  32 + try {
  33 + CertificateFactory cf = CertificateFactory.getInstance("X509");
  34 + X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
  35 + cert.checkValidity();
  36 + return cert;
  37 + } catch (CertificateExpiredException e) {
  38 + throw new RuntimeException("证书已过期", e);
  39 + } catch (CertificateNotYetValidException e) {
  40 + throw new RuntimeException("证书尚未生效", e);
  41 + } catch (CertificateException e) {
  42 + throw new RuntimeException("无效的证书", e);
  43 + }
  44 + }
  45 + /**
  46 + * 简化的UUID,去掉了横线,使用性能更好的 ThreadLocalRandom 生成UUID
  47 + *
  48 + * @return 简化的 UUID,去掉了横线
  49 + */
  50 + public static String generateStr() {
  51 + return IdUtil.fastSimpleUUID();
  52 + }
  53 +
  54 + /**
  55 + * 构造签名串
  56 + *
  57 + * @param method {@link} GET,POST,PUT等
  58 + * @param url 请求接口 /v3/certificates
  59 + * @param timestamp 获取发起请求时的系统当前时间戳
  60 + * @param nonceStr 随机字符串
  61 + * @param body 请求报文主体
  62 + * @return 待签名字符串
  63 + */
  64 + public static String buildSignMessage(String method, String url, String timestamp, String nonceStr, String body) {
  65 + ArrayList<String> arrayList = new ArrayList<>();
  66 + arrayList.add(method);
  67 + arrayList.add(url);
  68 + arrayList.add(String.valueOf(timestamp));
  69 + arrayList.add(nonceStr);
  70 + arrayList.add(body);
  71 + return buildSignMessage(arrayList);
  72 + }
  73 + /**
  74 + * 构造签名串
  75 + *
  76 + * @param signMessage 待签名的参数
  77 + * @return 构造后带待签名串
  78 + */
  79 + public static String buildSignMessage(ArrayList<String> signMessage) {
  80 + if (signMessage == null || signMessage.size() <= 0) {
  81 + return null;
  82 + }
  83 + StringBuilder sbf = new StringBuilder();
  84 + for (String str : signMessage) {
  85 + sbf.append(str).append("\n");
  86 + }
  87 + System.out.println("待签名字符串内容:" + sbf.toString());
  88 + System.out.println("待签名字符串长度:" + sbf.toString().length());
  89 + return sbf.toString();
  90 + }
  91 + /**
  92 + * v3 接口创建签名
  93 + *
  94 + * @param signMessage 待签名的参数
  95 + * @param keyPath key.pem 证书路径
  96 + * @return 生成 v3 签名
  97 + * @throws Exception 异常信息
  98 + */
  99 + public static String createSign(String signMessage, String keyPath) throws Exception {
  100 + if (StrUtil.isEmpty(signMessage)) {
  101 + return null;
  102 + }
  103 + // 获取商户私钥
  104 + PrivateKey privateKey = PayKit.getPrivateKey(keyPath);
  105 + // 生成签名
  106 + return RsaKit.encryptByPrivateKey(signMessage, privateKey);
  107 + }
  108 +
  109 + /**
  110 + * 获取商户私钥
  111 + *
  112 + * @param keyPath 商户私钥证书路径
  113 + * @return {@link PrivateKey} 商户私钥
  114 + * @throws Exception 异常信息
  115 + */
  116 + public static PrivateKey getPrivateKey(String keyPath) throws Exception {
  117 + String originalKey = FileUtil.readUtf8String(keyPath);
  118 + String privateKey = originalKey
  119 + .replace("-----BEGIN PRIVATE KEY-----", "")
  120 + .replace("-----END PRIVATE KEY-----", "")
  121 + .replaceAll("\\s+", "");
  122 +
  123 + return RsaKit.loadPrivateKey(privateKey);
  124 + }
  125 +
  126 + /**
  127 + * 获取授权认证信息
  128 + *
  129 + * @param mchId 商户号
  130 + * @param serialNo 商户API证书序列号
  131 + * @param nonceStr 请求随机串
  132 + * @param timestamp 时间戳
  133 + * @param signature 签名值
  134 + * @param authType 认证类型,目前为WECHATPAY2-SHA256-RSA2048
  135 + * @return 请求头 Authorization
  136 + */
  137 + public static String getAuthorization(String mchId, String serialNo, String nonceStr, String timestamp, String signature, String authType) {
  138 + Map<String, String> params = new HashMap<>(5);
  139 + params.put("mchid", mchId);
  140 + params.put("serial_no", serialNo);
  141 + params.put("nonce_str", nonceStr);
  142 + params.put("timestamp", timestamp);
  143 + params.put("signature", signature);
  144 + return authType.concat(" ").concat(createLinkString(params, ",", false, true));
  145 + }
  146 + /**
  147 + * @param params 需要排序并参与字符拼接的参数组
  148 + * @param encode 是否进行URLEncoder
  149 + * @return 拼接后字符串
  150 + */
  151 + public static String createLinkString(Map<String, String> params, boolean encode) {
  152 + return createLinkString(params, "&", encode);
  153 + }
  154 + /**
  155 + * @param params 需要排序并参与字符拼接的参数组
  156 + * @param connStr 连接符号
  157 + * @param encode 是否进行URLEncoder
  158 + * @return 拼接后字符串
  159 + */
  160 + public static String createLinkString(Map<String, String> params, String connStr, boolean encode) {
  161 + return createLinkString(params, connStr, encode, false);
  162 + }
  163 +
  164 + public static String createLinkString(Map<String, String> params, String connStr, boolean encode, boolean quotes) {
  165 + List<String> keys = new ArrayList<>(params.keySet());
  166 + Collections.sort(keys);
  167 + StringBuilder content = new StringBuilder();
  168 + for (int i = 0; i < keys.size(); i++) {
  169 + String key = keys.get(i);
  170 + String value = params.get(key);
  171 + // 拼接时,不包括最后一个&字符
  172 + if (i == keys.size() - 1) {
  173 + if (quotes) {
  174 + content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"');
  175 + } else {
  176 + content.append(key).append("=").append(encode ? urlEncode(value) : value);
  177 + }
  178 + } else {
  179 + if (quotes) {
  180 + content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"').append(connStr);
  181 + } else {
  182 + content.append(key).append("=").append(encode ? urlEncode(value) : value).append(connStr);
  183 + }
  184 + }
  185 + }
  186 + return content.toString();
  187 + }
  188 +
  189 + /**
  190 + * URL 编码
  191 + *
  192 + * @param src 需要编码的字符串
  193 + * @return 编码后的字符串
  194 + */
  195 + public static String urlEncode(String src) {
  196 + try {
  197 + return URLEncoder.encode(src, CharsetUtil.UTF_8).replace("+", "%20");
  198 + } catch (UnsupportedEncodingException e) {
  199 + e.printStackTrace();
  200 + return null;
  201 + }
  202 + }
  203 +
  204 + /**
  205 + * 构造签名串
  206 + *
  207 + * @param timestamp 应答时间戳
  208 + * @param nonceStr 应答随机串
  209 + * @param body 应答报文主体
  210 + * @return 应答待签名字符串
  211 + */
  212 + public static String buildSignMessage(String timestamp, String nonceStr, String body) {
  213 + ArrayList<String> arrayList = new ArrayList<>();
  214 + arrayList.add(timestamp);
  215 + arrayList.add(nonceStr);
  216 + arrayList.add(body);
  217 + return buildSignMessage(arrayList);
  218 + }
  219 +
  220 + /**
  221 + * 公钥加密
  222 + *
  223 + * @param data 待加密数据
  224 + * @param certificate 平台公钥证书
  225 + * @return 加密后的数据
  226 + * @throws Exception 异常信息
  227 + */
  228 + public static String rsaEncryptOAEP(String data, X509Certificate certificate) throws Exception {
  229 + try {
  230 + Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
  231 + cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
  232 +
  233 + byte[] dataByte = data.getBytes(StandardCharsets.UTF_8);
  234 + byte[] cipherData = cipher.doFinal(dataByte);
  235 + return Base64.encode(cipherData);
  236 + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
  237 + throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
  238 + } catch (InvalidKeyException e) {
  239 + throw new IllegalArgumentException("无效的证书", e);
  240 + } catch (IllegalBlockSizeException | BadPaddingException e) {
  241 + throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
  242 + }
  243 + }
  244 +
  245 + /**
  246 + * 私钥解密
  247 + *
  248 + * @param cipherText 加密字符
  249 + * @param privateKey 私钥
  250 + * @return 解密后的数据
  251 + * @throws Exception 异常信息
  252 + */
  253 + public static String rsaDecryptOAEP(String cipherText, PrivateKey privateKey) throws Exception {
  254 + try {
  255 + Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
  256 + cipher.init(Cipher.DECRYPT_MODE, privateKey);
  257 + byte[] data = Base64.decode(cipherText);
  258 + return new String(cipher.doFinal(data), StandardCharsets.UTF_8);
  259 + } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
  260 + throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
  261 + } catch (InvalidKeyException e) {
  262 + throw new IllegalArgumentException("无效的私钥", e);
  263 + } catch (BadPaddingException | IllegalBlockSizeException e) {
  264 + throw new BadPaddingException("解密失败");
  265 + }
  266 + }
  267 +}
  1 +package com.aigeo.util.pay;
  2 +
  3 +import com.aigeo.entity.*;
  4 +import com.alibaba.fastjson.JSON;
  5 +import com.alibaba.fastjson.JSONArray;
  6 +import com.alibaba.fastjson.JSONObject;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import okhttp3.*;
  9 +import org.springframework.beans.factory.annotation.Autowired;
  10 +import org.springframework.stereotype.Component;
  11 +
  12 +import java.io.IOException;
  13 +import java.text.SimpleDateFormat;
  14 +import java.util.Base64;
  15 +import java.util.Date;
  16 +import java.util.Objects;
  17 +import java.util.UUID;
  18 +
  19 +@Slf4j
  20 +@Component
  21 +public class PayPal implements PaymentStrategy{
  22 +
  23 + private static final String TOKEN_URL = "/v1/oauth2/token";
  24 +
  25 + private static final String ORDER_URL = "/v2/checkout/orders";
  26 +
  27 +// @Autowired
  28 +// private PaypalOrderRecordDao paypalOrderRecordDao;
  29 +
  30 + @Override
  31 + public String pay(PayDto payDto) throws IOException {
  32 + ApiConfig config = payDto.getApiConfig();
  33 + String tokenUrl=config.getApiUrl()+TOKEN_URL;
  34 + String token = getAccessToken(tokenUrl,config.getApiId(),config.getApiKey());
  35 + OkHttpClient client = new OkHttpClient();
  36 + String url=config.getApiUrl()+ORDER_URL;
  37 +
  38 + PayPalDTO palDTO = new PayPalDTO();
  39 + palDTO.setIntent("CAPTURE");
  40 + PayPalDTO.PaymentSource paymentSource = new PayPalDTO.PaymentSource();
  41 + PayPalDTO.PaymentSource.Paypal paypal = new PayPalDTO.PaymentSource.Paypal();
  42 +
  43 + PayPalDTO.PaymentSource.Paypal.ExperienceContext experienceContext = new PayPalDTO.PaymentSource.Paypal.ExperienceContext();
  44 + experienceContext.setReturn_url(config.getApiRedirectUri()+"/"+payDto.getCompanyId());
  45 + experienceContext.setCancel_url(config.getApiRedirectUri());
  46 +
  47 + paypal.setExperience_context(experienceContext);
  48 + paymentSource.setPaypal(paypal);
  49 + palDTO.setPayment_source(paymentSource);
  50 +
  51 + PayPalDTO.PurchaseUnit.Amount amount = new PayPalDTO.PurchaseUnit.Amount();
  52 + amount.setCurrency_code("USD");
  53 + amount.setValue(payDto.getAmount());
  54 +
  55 + PayPalDTO.PurchaseUnit purchaseUnit = new PayPalDTO.PurchaseUnit();
  56 + purchaseUnit.setCustom_id(payDto.getOrderId());
  57 + purchaseUnit.setAmount(amount);
  58 + PayPalDTO.PurchaseUnit[] list = new PayPalDTO.PurchaseUnit[]{purchaseUnit};
  59 + palDTO.setPurchase_units(list);
  60 +
  61 + String toJson = JSONObject.toJSONString(palDTO);
  62 + RequestBody body = RequestBody.create(toJson, MediaType.parse("application/json; charset=utf-8"));
  63 + String string = UUID.randomUUID().toString();
  64 + Request request = new Request.Builder()
  65 + .url(url)
  66 + .post(body)
  67 + .addHeader("Content-Type", "application/json")
  68 + .addHeader("PayPal-Request-Id", string)
  69 + .addHeader("Authorization", "Bearer "+token)
  70 + .build();
  71 +
  72 + try (Response response = client.newCall(request).execute()) {
  73 + if (response.isSuccessful() && response.body() != null) {
  74 + String res = Objects.requireNonNull(response.body()).string();
  75 + log.info("响应内容:{}",res);
  76 + PayPalResponse payPalResponse = JSONObject.parseObject(res, PayPalResponse.class);
  77 + PayPalResponse.ResMsg[] link = payPalResponse.getLinks();
  78 + log.info("订单:{}支付地址:{}",payDto.getOrderId(),link[1].getHref());
  79 + Date now = new Date();
  80 + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
  81 + String formattedTime = formatter.format(now);
  82 + PayPalOrderRecord orderRecord = new PayPalOrderRecord();
  83 + orderRecord.setCompanyId(payDto.getCompanyId());
  84 + orderRecord.setOrderId(payDto.getOrderId());
  85 + orderRecord.setPaypalId(payPalResponse.getId());
  86 + orderRecord.setState(0);
  87 + orderRecord.setCreateTime(formattedTime);
  88 + orderRecord.setUpdateTime(formattedTime);
  89 +// paypalOrderRecordDao.addPojo(orderRecord);
  90 + return link[1].getHref();
  91 + } else {
  92 + System.out.println("Request failed: " + response.code());
  93 + System.out.println("Response: " + response.body().string());
  94 + return null;
  95 + }
  96 + } catch (IOException e) {
  97 + e.printStackTrace();
  98 + }
  99 + return null;
  100 + }
  101 +
  102 + @Override
  103 + public void closeTrade(String orderId, String mchId, ApiConfig apiConfig) {
  104 +
  105 + }
  106 +
  107 + @Override
  108 + public void refund() {
  109 +
  110 + }
  111 +
  112 + public static String getAccessToken(String tokenUrl,String clientId,String clientSecret) throws IOException {
  113 + OkHttpClient client = new OkHttpClient();
  114 +
  115 + // 创建请求体
  116 + RequestBody body = new FormBody.Builder()
  117 + .add("grant_type", "client_credentials")
  118 + .build();
  119 +
  120 + // 创建请求
  121 + Request request = new Request.Builder()
  122 + .url(tokenUrl)
  123 + .post(body)
  124 + .addHeader("Content-Type", "application/x-www-form-urlencoded")
  125 + .addHeader("Authorization", "Basic " + getBasicAuth(clientId,clientSecret))
  126 + .build();
  127 +
  128 + // 发送请求
  129 + try (Response response = client.newCall(request).execute()) {
  130 + if (response.isSuccessful() && response.body() != null) {
  131 + // 解析响应
  132 + String responseBody = response.body().string();
  133 + System.out.println("*-*--**--*-*-*-*-**--**-*-"+parseAccessToken(responseBody));
  134 + return parseAccessToken(responseBody);
  135 + } else {
  136 + throw new IOException("Failed to get access token: " + response.code());
  137 + }
  138 + }
  139 + }
  140 +
  141 + private static String getBasicAuth(String clientId,String clientSecret) {
  142 + return Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes());
  143 + }
  144 +
  145 + private static String parseAccessToken(String responseBody) {
  146 + // 简单解析 JSON 响应
  147 + String[] parts = responseBody.split(",");
  148 + for (String part : parts) {
  149 + if (part.contains("access_token")) {
  150 + return part.split(":")[1].trim().replace("\"", "");
  151 + }
  152 + }
  153 + throw new IllegalArgumentException("Access token not found in response: " + responseBody);
  154 + }
  155 +
  156 + public static String extractPayerActionUrl(String jsonResponse) {
  157 + // 将 JSON 字符串解析为 JSONObject
  158 + JSONObject jsonObject = JSON.parseObject(jsonResponse);
  159 +
  160 + // 获取 links 数组
  161 + JSONArray links = jsonObject.getJSONArray("links");
  162 +
  163 + // 遍历 links 数组
  164 + for (int i = 0; i < links.size(); i++) {
  165 + JSONObject link = links.getJSONObject(i);
  166 + // 检查 rel 是否为 payer-action
  167 + if ("payer-action".equals(link.getString("rel"))) {
  168 + // 返回 href 值
  169 + return link.getString("href");
  170 + }
  171 + }
  172 +
  173 + // 如果没有找到 payer-action 链接,返回 null
  174 + return null;
  175 + }
  176 +
  177 + @Override
  178 + public String getChannel() {
  179 + return "paypal";
  180 + }
  181 +}
  1 +package com.aigeo.util.pay;
  2 +
  3 +
  4 +import com.aigeo.entity.ApiConfig;
  5 +import com.aigeo.entity.PayDto;
  6 +import com.alipay.api.AlipayApiException;
  7 +
  8 +import java.io.IOException;
  9 +
  10 +public interface PaymentStrategy {
  11 + //支付
  12 + String pay(PayDto payDto) throws AlipayApiException, IOException;
  13 +
  14 + //根据系统订单号查询订单
  15 +
  16 + //根据微信支付订单号查询订单
  17 +
  18 + //关闭订单
  19 + void closeTrade(String orderId, String mchId, ApiConfig apiConfig);
  20 + //退款申请
  21 + void refund();
  22 + //查询退款申请
  23 +
  24 + // 支持的支付渠道标识
  25 + String getChannel();
  26 +}
  1 +package com.aigeo.util.pay;
  2 +
  3 +import org.springframework.beans.factory.annotation.Autowired;
  4 +import org.springframework.stereotype.Component;
  5 +
  6 +import javax.annotation.PostConstruct;
  7 +import java.util.List;
  8 +import java.util.Map;
  9 +import java.util.concurrent.ConcurrentHashMap;
  10 +
  11 +@Component
  12 +public class PaymentStrategyFactory {
  13 + @Autowired
  14 + private List<PaymentStrategy> strategies;
  15 +
  16 + private final Map<String, PaymentStrategy> strategyMap = new ConcurrentHashMap<>();
  17 +
  18 + @PostConstruct
  19 + public void init() {
  20 + strategies.forEach(strategy -> strategyMap.put(strategy.getChannel(), strategy));
  21 + }
  22 +
  23 + public PaymentStrategy getStrategy(String channel) {
  24 + PaymentStrategy strategy = strategyMap.get(channel);
  25 + if (strategy == null) {
  26 + throw new IllegalArgumentException("Unsupported payment channel: " + channel);
  27 + }
  28 + return strategy;
  29 + }
  30 +}
  1 +package com.aigeo.util.pay;
  2 +
  3 +import cn.hutool.core.codec.Base64;
  4 +import cn.hutool.core.util.StrUtil;
  5 +
  6 +import java.nio.charset.StandardCharsets;
  7 +import java.security.KeyFactory;
  8 +import java.security.NoSuchAlgorithmException;
  9 +import java.security.PrivateKey;
  10 +import java.security.PublicKey;
  11 +import java.security.spec.InvalidKeySpecException;
  12 +import java.security.spec.PKCS8EncodedKeySpec;
  13 +
  14 +public class RsaKit {
  15 +
  16 + /**
  17 + * 加密算法RSA
  18 + */
  19 + private static final String KEY_ALGORITHM = "RSA";
  20 +
  21 + /**
  22 + * 私钥签名
  23 + *
  24 + * @param data 需要加密的数据
  25 + * @param privateKey 私钥
  26 + * @return 加密后的数据
  27 + * @throws Exception 异常信息
  28 + */
  29 + public static String encryptByPrivateKey(String data, PrivateKey privateKey) throws Exception {
  30 + java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");
  31 + signature.initSign(privateKey);
  32 + signature.update(data.getBytes(StandardCharsets.UTF_8));
  33 + byte[] signed = signature.sign();
  34 + return StrUtil.str(Base64.encode(signed));
  35 + }
  36 +
  37 + /**
  38 + * 从字符串中加载私钥<br>
  39 + * 加载时使用的是PKCS8EncodedKeySpec(PKCS#8编码的Key指令)。
  40 + *
  41 + * @param privateKeyStr 私钥
  42 + * @return {@link PrivateKey}
  43 + * @throws Exception 异常信息
  44 + */
  45 + public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
  46 + try {
  47 + byte[] buffer = Base64.decode(privateKeyStr);
  48 + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
  49 + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
  50 + return keyFactory.generatePrivate(keySpec);
  51 + } catch (NoSuchAlgorithmException e) {
  52 + throw new Exception("无此算法");
  53 + } catch (InvalidKeySpecException e) {
  54 + throw new Exception("私钥非法");
  55 + } catch (NullPointerException e) {
  56 + throw new Exception("私钥数据为空");
  57 + }
  58 + }
  59 + /**
  60 + * 公钥验证签名
  61 + *
  62 + * @param data 需要加密的数据
  63 + * @param sign 签名
  64 + * @param publicKey 公钥
  65 + * @return 验证结果
  66 + * @throws Exception 异常信息
  67 + */
  68 + public static boolean checkByPublicKey(String data, String sign, PublicKey publicKey) throws Exception {
  69 + java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");
  70 + signature.initVerify(publicKey);
  71 + signature.update(data.getBytes(StandardCharsets.UTF_8));
  72 + return signature.verify(Base64.decode(sign.getBytes(StandardCharsets.UTF_8)));
  73 + }
  74 +}
  1 +package com.aigeo.util.pay;
  2 +
  3 +import com.aigeo.entity.ApiConfig;
  4 +import com.aigeo.entity.PayDto;
  5 +import com.wechat.pay.java.core.Config;
  6 +import com.wechat.pay.java.core.RSAAutoCertificateConfig;
  7 +import com.wechat.pay.java.service.payments.nativepay.NativePayService;
  8 +import com.wechat.pay.java.service.payments.nativepay.model.Amount;
  9 +import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
  10 +import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
  11 +import org.springframework.stereotype.Component;
  12 +
  13 +import java.math.BigDecimal;
  14 +import java.math.RoundingMode;
  15 +import java.time.ZoneId;
  16 +import java.time.ZonedDateTime;
  17 +import java.time.format.DateTimeFormatter;
  18 +
  19 +@Component
  20 +public class WechatPay implements PaymentStrategy{
  21 + /** 全局单例配置 */
  22 + //private static volatile Config globalConfig;
  23 +
  24 + //@Autowired
  25 + //private ApiConfigDao apiConfigDao;
  26 +
  27 + private static String HOST = "https://api.mch.weixin.qq.com";
  28 + private static String METHOD = "POST";
  29 + private static String PATH = "/v3/pay/partner/transactions/out-trade-no/{out_trade_no}/close";
  30 +
  31 + /**
  32 + * 初始化完毕(所有 @Value 已注入)后,构造一次并缓存
  33 + */
  34 + //@PostConstruct
  35 + //public void initConfig() throws Exception {
  36 + // if (globalConfig == null) {
  37 + // synchronized (WechatPay.class) {
  38 + // if (globalConfig == null) {
  39 + // ApiConfig wechatapy = apiConfigDao.findByApiCode(7,"wechatpay");
  40 + // if(wechatapy==null){
  41 + // throw new RuntimeException("微信支付初始化失败!");
  42 + // }
  43 + // globalConfig = new RSAAutoCertificateConfig.Builder()
  44 + // .merchantId(wechatapy.getMerchantId())
  45 + // .privateKeyFromPath(wechatapy.getPrivateKeyPath())
  46 + // .merchantSerialNumber(wechatapy.getApiCode())
  47 + // .apiV3Key(wechatapy.getApiKey())
  48 + // .build();
  49 + // }
  50 + // }
  51 + // }
  52 + //}
  53 +
  54 +
  55 + @Override
  56 + public String pay(PayDto payDto) {
  57 + ApiConfig apiConfig = payDto.getApiConfig();
  58 + String path = this.getClass().getResource("/").getPath();
  59 + path = path.replace("/", "//");
  60 + path = path.replace("//target//classes//", "");
  61 + path = path.replace("//WEB-INF//classes//", "");
  62 + Config globalConfig=new RSAAutoCertificateConfig.Builder()
  63 + .merchantId(apiConfig.getMerchantId())
  64 + .privateKeyFromPath(path+apiConfig.getPrivateKeyPath())
  65 + .merchantSerialNumber(apiConfig.getApiCode())
  66 + .apiV3Key(apiConfig.getApiKey())
  67 + .build();
  68 + NativePayService service = new NativePayService.Builder()
  69 + .config(globalConfig) // 复用全局配置
  70 + .build();
  71 + // request.setXxx(val)设置所需参数,具体参数可见Request定义
  72 + PrepayRequest request = new PrepayRequest();
  73 + Amount amount = new Amount();
  74 +
  75 + BigDecimal money = new BigDecimal(payDto.getAmount()).multiply(new BigDecimal("100"));
  76 + amount.setTotal(money.setScale(0, RoundingMode.FLOOR).intValueExact());
  77 + request.setAmount(amount);
  78 + request.setAppid(payDto.getApiConfig().getApiId());
  79 + request.setMchid(payDto.getApiConfig().getMerchantId());
  80 + request.setDescription(payDto.getDesc());
  81 + request.setNotifyUrl(payDto.getApiConfig().getApiRedirectUri()+"/"+payDto.getCompanyId());
  82 + request.setOutTradeNo(payDto.getOrderId());
  83 + request.setTimeExpire(getTime());
  84 + // 调用下单方法,得到应答
  85 + PrepayResponse response = service.prepay(request);
  86 + // 使用微信扫描 code_url 对应的二维码,即可体验Native支付
  87 + System.out.println("**-**-*--*-*-**--*-*-*-*-*-*-*"+response);
  88 + System.out.println("*-*-*-*-*-*-*-*-*-*-*-"+response.getCodeUrl()+"-*-**-*-*--**--*-*");
  89 + return response.getCodeUrl();
  90 + }
  91 +
  92 + /**
  93 + * 关闭订单
  94 + * @param orderId 订单号
  95 + * @param mchId 商户号
  96 + */
  97 + @Override
  98 + public void closeTrade(String orderId, String mchId,ApiConfig apiConfig) {
  99 +
  100 + }
  101 +
  102 + /**
  103 + * 退款申请
  104 + */
  105 + @Override
  106 + public void refund() {
  107 +
  108 + }
  109 +
  110 +
  111 + private String getTime(){
  112 + // 获取当前时间
  113 + ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault());
  114 +
  115 + // 加上十分钟
  116 + ZonedDateTime thirtyMinutesLater = now.plusMinutes(10);
  117 +
  118 + // 定义目标格式
  119 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX");
  120 +
  121 + // 格式化为字符串
  122 + String formattedTime = thirtyMinutesLater.format(formatter);
  123 +
  124 + System.out.println("订单结束时间:"+formattedTime);
  125 +
  126 + // 输出结果
  127 + return formattedTime;
  128 + }
  129 +
  130 + @Override
  131 + public String getChannel() {
  132 + return "wechatpay";
  133 + }
  134 +}
  135 +