正在显示
15 个修改的文件
包含
1521 行增加
和
0 行删除
@@ -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 | +} |
src/main/java/com/aigeo/entity/PayDto.java
0 → 100644
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 | +} |
src/main/java/com/aigeo/util/pay/AliPay.java
0 → 100644
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 | +} |
src/main/java/com/aigeo/util/pay/PayKit.java
0 → 100644
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 | +} |
src/main/java/com/aigeo/util/pay/PayPal.java
0 → 100644
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 | +} |
src/main/java/com/aigeo/util/pay/RsaKit.java
0 → 100644
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 | + |
-
请 注册 或 登录 后发表评论