feat(currency): 添加货币转换功能和国家地址配置
- 创建CalculateCurrencyConversionRequestDTO用于货币转换请求 - 实现CountryAddressConfig工具类支持多国地址格式配置 - 添加CreatePayPalOrderResponseDTO和CurrencyConversionDTO响应对象 - 创建ExchangeRateService接口及其实现类提供实时汇率服务 - 集成ExchangeRate-API实现24小时缓存的汇率获取功能 - 添加HttpGet和MD5工具类支持HTTP请求和加密计算
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
package com.mtkj.mtpay.dto.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算货币转换请求DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CalculateCurrencyConversionRequestDTO implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单号
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "订单号不能为空")
|
||||||
|
private String orderNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始货币代码
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "原始货币代码不能为空")
|
||||||
|
private String originalCurrency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始金额
|
||||||
|
*/
|
||||||
|
@NotNull(message = "原始金额不能为空")
|
||||||
|
private BigDecimal originalAmount;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.mtkj.mtpay.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建PayPal订单响应DTO(包含货币转换信息)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CreatePayPalOrderResponseDTO implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayPal订单信息
|
||||||
|
*/
|
||||||
|
private PayPalOrderResponseDTO paypalOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 货币转换信息
|
||||||
|
*/
|
||||||
|
private CurrencyConversionDTO currencyConversion;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.mtkj.mtpay.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 货币转换信息DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CurrencyConversionDTO implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始货币代码
|
||||||
|
*/
|
||||||
|
private String originalCurrency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始金额
|
||||||
|
*/
|
||||||
|
private BigDecimal originalAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付货币代码
|
||||||
|
*/
|
||||||
|
private String paymentCurrency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付金额
|
||||||
|
*/
|
||||||
|
private BigDecimal paymentAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用的汇率
|
||||||
|
*/
|
||||||
|
private BigDecimal exchangeRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 汇率锁定时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime rateLockedAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要货币转换
|
||||||
|
*/
|
||||||
|
private Boolean conversionRequired;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 汇率说明文本
|
||||||
|
*/
|
||||||
|
private String rateDescription;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.mtkj.mtpay.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 汇率服务接口
|
||||||
|
*/
|
||||||
|
public interface ExchangeRateService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取汇率(从源货币转换为目标货币)
|
||||||
|
* @param fromCurrency 源货币代码
|
||||||
|
* @param toCurrency 目标货币代码
|
||||||
|
* @return 汇率(1单位源货币 = ?单位目标货币)
|
||||||
|
*/
|
||||||
|
Double getExchangeRate(String fromCurrency, String toCurrency);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换货币金额
|
||||||
|
* @param amount 原始金额
|
||||||
|
* @param fromCurrency 源货币代码
|
||||||
|
* @param toCurrency 目标货币代码
|
||||||
|
* @return 转换后的金额
|
||||||
|
*/
|
||||||
|
Double convertAmount(Double amount, String fromCurrency, String toCurrency);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package com.mtkj.mtpay.service.impl;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.mtkj.mtpay.service.ExchangeRateService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 汇率服务实现类
|
||||||
|
* 使用 ExchangeRate-API 获取实时汇率
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ExchangeRateServiceImpl implements ExchangeRateService {
|
||||||
|
|
||||||
|
private static final String API_KEY = "f0a6cdaf139b53113ee2eb22";
|
||||||
|
private static final String API_URL = "https://v6.exchangerate-api.com/v6/" + API_KEY + "/latest/USD";
|
||||||
|
|
||||||
|
// 汇率缓存(key: 货币代码, value: 相对于USD的汇率)
|
||||||
|
private final Map<String, Double> rateCache = new ConcurrentHashMap<>();
|
||||||
|
private long cacheExpireTime = 0;
|
||||||
|
private static final long CACHE_DURATION = 24 * 60 * 60 * 1000L; // 24小时缓存
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取汇率(从源货币转换为目标货币)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Double getExchangeRate(String fromCurrency, String toCurrency) {
|
||||||
|
// 如果相同货币,返回1.0
|
||||||
|
if (fromCurrency.equals(toCurrency)) {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新缓存(如果需要)
|
||||||
|
refreshCacheIfNeeded();
|
||||||
|
|
||||||
|
// 如果目标货币是USD,直接返回缓存中的汇率
|
||||||
|
if ("USD".equals(toCurrency)) {
|
||||||
|
Double rate = rateCache.get(fromCurrency);
|
||||||
|
if (rate != null) {
|
||||||
|
return 1.0 / rate; // 转换为USD的汇率
|
||||||
|
}
|
||||||
|
log.warn("货币 {} 的汇率未找到,使用默认汇率 1.0", fromCurrency);
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果源货币是USD,直接返回缓存中的汇率
|
||||||
|
if ("USD".equals(fromCurrency)) {
|
||||||
|
Double rate = rateCache.get(toCurrency);
|
||||||
|
if (rate != null) {
|
||||||
|
return rate; // USD转换为目标货币的汇率
|
||||||
|
}
|
||||||
|
log.warn("货币 {} 的汇率未找到,使用默认汇率 1.0", toCurrency);
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他情况:通过USD中转
|
||||||
|
// fromCurrency -> USD -> toCurrency
|
||||||
|
Double fromRate = rateCache.get(fromCurrency);
|
||||||
|
Double toRate = rateCache.get(toCurrency);
|
||||||
|
|
||||||
|
if (fromRate != null && toRate != null) {
|
||||||
|
return toRate / fromRate; // (1 USD / fromRate) * toRate = toRate / fromRate
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn("货币 {} 或 {} 的汇率未找到,使用默认汇率 1.0", fromCurrency, toCurrency);
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换货币金额
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Double convertAmount(Double amount, String fromCurrency, String toCurrency) {
|
||||||
|
Double rate = getExchangeRate(fromCurrency, toCurrency);
|
||||||
|
return amount * rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新汇率缓存
|
||||||
|
*/
|
||||||
|
private void refreshCacheIfNeeded() {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// 如果缓存未过期,直接返回
|
||||||
|
if (currentTime < cacheExpireTime && !rateCache.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("刷新汇率缓存...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
ResponseEntity<Map> response = restTemplate.getForEntity(API_URL, Map.class);
|
||||||
|
|
||||||
|
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
|
||||||
|
Map<String, Object> responseBody = response.getBody();
|
||||||
|
|
||||||
|
// 检查响应状态
|
||||||
|
String result = (String) responseBody.get("result");
|
||||||
|
if (!"success".equals(result)) {
|
||||||
|
log.error("汇率API返回失败: {}", responseBody);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取汇率数据
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> conversionRates = (Map<String, Object>) responseBody.get("conversion_rates");
|
||||||
|
|
||||||
|
if (conversionRates != null) {
|
||||||
|
// 清空旧缓存
|
||||||
|
rateCache.clear();
|
||||||
|
|
||||||
|
// 填充新缓存
|
||||||
|
conversionRates.forEach((currency, rate) -> {
|
||||||
|
if (rate instanceof Number) {
|
||||||
|
rateCache.put(currency, ((Number) rate).doubleValue());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置缓存过期时间(24小时后)
|
||||||
|
cacheExpireTime = currentTime + CACHE_DURATION;
|
||||||
|
|
||||||
|
log.info("汇率缓存刷新成功,共 {} 种货币", rateCache.size());
|
||||||
|
log.debug("示例汇率 - MYR: {}, CNY: {}, EUR: {}",
|
||||||
|
rateCache.get("MYR"), rateCache.get("CNY"), rateCache.get("EUR"));
|
||||||
|
} else {
|
||||||
|
log.error("汇率API响应中未找到conversion_rates字段");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.error("获取汇率失败,状态码: {}", response.getStatusCode());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("刷新汇率缓存异常", e);
|
||||||
|
// 如果API调用失败,使用默认汇率(不更新缓存)
|
||||||
|
if (rateCache.isEmpty()) {
|
||||||
|
log.warn("汇率API调用失败,使用默认汇率");
|
||||||
|
// 设置一些常用货币的默认汇率
|
||||||
|
rateCache.put("MYR", 4.078);
|
||||||
|
rateCache.put("CNY", 7.0465);
|
||||||
|
rateCache.put("EUR", 0.851);
|
||||||
|
rateCache.put("GBP", 0.7434);
|
||||||
|
cacheExpireTime = currentTime + 60 * 60 * 1000L; // 1小时后重试
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
package com.mtkj.mtpay.util;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国家地址配置工具类
|
||||||
|
* 定义各国地址字段规则和邮编格式
|
||||||
|
*/
|
||||||
|
public class CountryAddressConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国家地址配置
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class CountryConfig {
|
||||||
|
private String countryCode; // 国家代码
|
||||||
|
private String countryName; // 国家名称
|
||||||
|
private String countryNameEn; // 国家名称(英文)
|
||||||
|
private int postcodeLength; // 邮编长度
|
||||||
|
private String postcodePattern; // 邮编格式(正则表达式)
|
||||||
|
private String phoneCode; // 国际区号(如 +65, +66)
|
||||||
|
private List<String> requiredFields; // 必填字段列表
|
||||||
|
private List<String> specialFields; // 特殊字段列表
|
||||||
|
private Map<String, String> fieldLabels; // 字段标签(支持多语言)
|
||||||
|
private String addressFormat; // 地址格式说明
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取国家配置
|
||||||
|
*/
|
||||||
|
public static CountryConfig getCountryConfig(String countryCode) {
|
||||||
|
return COUNTRY_CONFIGS.getOrDefault(countryCode.toUpperCase(), DEFAULT_CONFIG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有支持的国家
|
||||||
|
*/
|
||||||
|
public static Set<String> getSupportedCountries() {
|
||||||
|
return COUNTRY_CONFIGS.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证邮编格式
|
||||||
|
*/
|
||||||
|
public static boolean validatePostcode(String countryCode, String postcode) {
|
||||||
|
if (postcode == null || postcode.trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CountryConfig config = getCountryConfig(countryCode);
|
||||||
|
if (config.getPostcodePattern() == null) {
|
||||||
|
return postcode.length() == config.getPostcodeLength();
|
||||||
|
}
|
||||||
|
return postcode.matches(config.getPostcodePattern());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取必填字段
|
||||||
|
*/
|
||||||
|
public static List<String> getRequiredFields(String countryCode) {
|
||||||
|
CountryConfig config = getCountryConfig(countryCode);
|
||||||
|
return config.getRequiredFields() != null ? new ArrayList<>(config.getRequiredFields()) : new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取特殊字段
|
||||||
|
*/
|
||||||
|
public static List<String> getSpecialFields(String countryCode) {
|
||||||
|
CountryConfig config = getCountryConfig(countryCode);
|
||||||
|
return config.getSpecialFields() != null ? new ArrayList<>(config.getSpecialFields()) : new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认配置
|
||||||
|
private static final CountryConfig DEFAULT_CONFIG = new CountryConfig();
|
||||||
|
static {
|
||||||
|
DEFAULT_CONFIG.setCountryCode("DEFAULT");
|
||||||
|
DEFAULT_CONFIG.setCountryName("默认");
|
||||||
|
DEFAULT_CONFIG.setCountryNameEn("Default");
|
||||||
|
DEFAULT_CONFIG.setPostcodeLength(6);
|
||||||
|
DEFAULT_CONFIG.setPostcodePattern(null);
|
||||||
|
DEFAULT_CONFIG.setPhoneCode("+86");
|
||||||
|
DEFAULT_CONFIG.setRequiredFields(Arrays.asList("shippingName", "shippingPhone", "shippingCountry",
|
||||||
|
"shippingCity", "shippingAddressLine1"));
|
||||||
|
DEFAULT_CONFIG.setSpecialFields(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 国家配置映射
|
||||||
|
private static final Map<String, CountryConfig> COUNTRY_CONFIGS = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// 新加坡配置
|
||||||
|
CountryConfig sg = new CountryConfig();
|
||||||
|
sg.setCountryCode("SG");
|
||||||
|
sg.setCountryName("新加坡");
|
||||||
|
sg.setCountryNameEn("Singapore");
|
||||||
|
sg.setPostcodeLength(6);
|
||||||
|
sg.setPostcodePattern("^\\d{6}$");
|
||||||
|
sg.setPhoneCode("+65");
|
||||||
|
sg.setRequiredFields(Arrays.asList("shippingName", "shippingPhone", "shippingCountry",
|
||||||
|
"shippingCity", "shippingAddressLine1", "shippingBlockNumber", "shippingUnitNumber", "shippingPostcode"));
|
||||||
|
sg.setSpecialFields(Arrays.asList("shippingBlockNumber", "shippingUnitNumber"));
|
||||||
|
Map<String, String> sgLabels = new HashMap<>();
|
||||||
|
sgLabels.put("shippingBlockNumber", "组屋号 (Block Number)");
|
||||||
|
sgLabels.put("shippingUnitNumber", "单元号 (Unit Number)");
|
||||||
|
sgLabels.put("shippingAddressLine1", "详细地址1 (Address Line 1)");
|
||||||
|
sgLabels.put("shippingAddressLine2", "详细地址2 (Address Line 2)");
|
||||||
|
sg.setFieldLabels(sgLabels);
|
||||||
|
sg.setAddressFormat("Blk 123 Jurong West St 41 #12-345, Singapore 640123");
|
||||||
|
COUNTRY_CONFIGS.put("SG", sg);
|
||||||
|
|
||||||
|
// 马来西亚配置
|
||||||
|
CountryConfig my = new CountryConfig();
|
||||||
|
my.setCountryCode("MY");
|
||||||
|
my.setCountryName("马来西亚");
|
||||||
|
my.setCountryNameEn("Malaysia");
|
||||||
|
my.setPostcodeLength(5);
|
||||||
|
my.setPostcodePattern("^\\d{5}$");
|
||||||
|
my.setPhoneCode("+60");
|
||||||
|
my.setRequiredFields(Arrays.asList("shippingName", "shippingPhone", "shippingCountry",
|
||||||
|
"shippingCity", "shippingStateMalaysia", "shippingAddressLine1", "shippingPostcode"));
|
||||||
|
my.setSpecialFields(Arrays.asList("shippingStateMalaysia"));
|
||||||
|
Map<String, String> myLabels = new HashMap<>();
|
||||||
|
myLabels.put("shippingStateMalaysia", "州属 (State)");
|
||||||
|
myLabels.put("shippingAddressLine1", "详细地址1 (Address Line 1)");
|
||||||
|
myLabels.put("shippingAddressLine2", "详细地址2 (Address Line 2)");
|
||||||
|
my.setFieldLabels(myLabels);
|
||||||
|
my.setAddressFormat("123 Jalan Abdullah, 05-01 Menara A, Kuala Lumpur, Selangor 50300");
|
||||||
|
COUNTRY_CONFIGS.put("MY", my);
|
||||||
|
|
||||||
|
// 菲律宾配置
|
||||||
|
CountryConfig ph = new CountryConfig();
|
||||||
|
ph.setCountryCode("PH");
|
||||||
|
ph.setCountryName("菲律宾");
|
||||||
|
ph.setCountryNameEn("Philippines");
|
||||||
|
ph.setPostcodeLength(4);
|
||||||
|
ph.setPostcodePattern("^\\d{4}$");
|
||||||
|
ph.setPhoneCode("+63");
|
||||||
|
ph.setRequiredFields(Arrays.asList("shippingName", "shippingPhone", "shippingCountry",
|
||||||
|
"shippingCity", "shippingState", "shippingBarangay", "shippingAddressLine1", "shippingPostcode"));
|
||||||
|
ph.setSpecialFields(Arrays.asList("shippingBarangay"));
|
||||||
|
Map<String, String> phLabels = new HashMap<>();
|
||||||
|
phLabels.put("shippingBarangay", "Barangay(社区编号)");
|
||||||
|
phLabels.put("shippingState", "省 (Province)");
|
||||||
|
phLabels.put("shippingCity", "市 (City)");
|
||||||
|
phLabels.put("shippingAddressLine1", "详细地址1 (Address Line 1)");
|
||||||
|
phLabels.put("shippingAddressLine2", "详细地址2 (Address Line 2)");
|
||||||
|
ph.setFieldLabels(phLabels);
|
||||||
|
ph.setAddressFormat("123 Main St, Barangay 12, Manila, Metro Manila 1000");
|
||||||
|
COUNTRY_CONFIGS.put("PH", ph);
|
||||||
|
|
||||||
|
// 泰国配置
|
||||||
|
CountryConfig th = new CountryConfig();
|
||||||
|
th.setCountryCode("TH");
|
||||||
|
th.setCountryName("泰国");
|
||||||
|
th.setCountryNameEn("Thailand");
|
||||||
|
th.setPostcodeLength(5);
|
||||||
|
th.setPostcodePattern("^\\d{5}$");
|
||||||
|
th.setPhoneCode("+66");
|
||||||
|
th.setRequiredFields(Arrays.asList("shippingName", "shippingPhone", "shippingCountry",
|
||||||
|
"shippingCity", "shippingState", "shippingAddressLine1", "shippingPostcode"));
|
||||||
|
th.setSpecialFields(Arrays.asList("shippingAddressThai"));
|
||||||
|
Map<String, String> thLabels = new HashMap<>();
|
||||||
|
thLabels.put("shippingAddressThai", "泰文地址 (Thai Address)");
|
||||||
|
thLabels.put("shippingAddressLine1", "英文地址 (English Address)");
|
||||||
|
thLabels.put("shippingAddressLine2", "详细地址2 (Address Line 2)");
|
||||||
|
thLabels.put("shippingState", "府 (Changwat)");
|
||||||
|
thLabels.put("shippingCity", "县 (Amphoe)");
|
||||||
|
thLabels.put("shippingAdministrativeArea", "区 (Tambon)");
|
||||||
|
th.setFieldLabels(thLabels);
|
||||||
|
th.setAddressFormat("123 Soi Sukhumvit 101, Khlong Toei, Bangkok 10110");
|
||||||
|
COUNTRY_CONFIGS.put("TH", th);
|
||||||
|
|
||||||
|
// 越南配置
|
||||||
|
CountryConfig vn = new CountryConfig();
|
||||||
|
vn.setCountryCode("VN");
|
||||||
|
vn.setCountryName("越南");
|
||||||
|
vn.setCountryNameEn("Vietnam");
|
||||||
|
vn.setPostcodeLength(5);
|
||||||
|
vn.setPostcodePattern("^\\d{5}$");
|
||||||
|
vn.setPhoneCode("+84");
|
||||||
|
vn.setRequiredFields(Arrays.asList("shippingName", "shippingPhone", "shippingCountry",
|
||||||
|
"shippingProvince", "shippingDistrict", "shippingWard", "shippingAddressLine1", "shippingPostcode"));
|
||||||
|
vn.setSpecialFields(Arrays.asList("shippingProvince", "shippingDistrict", "shippingWard"));
|
||||||
|
Map<String, String> vnLabels = new HashMap<>();
|
||||||
|
vnLabels.put("shippingProvince", "省 (Tỉnh)");
|
||||||
|
vnLabels.put("shippingDistrict", "市/郡 (Thành phố/Huyện)");
|
||||||
|
vnLabels.put("shippingWard", "区/坊 (Quận/Phường)");
|
||||||
|
vnLabels.put("shippingAddressLine1", "详细地址1 (Địa chỉ chi tiết 1)");
|
||||||
|
vnLabels.put("shippingAddressLine2", "详细地址2 (Địa chỉ chi tiết 2)");
|
||||||
|
vn.setFieldLabels(vnLabels);
|
||||||
|
vn.setAddressFormat("123 Đường Nguyễn Huệ, Phường Bến Nghé, Quận 1, Thành phố Hồ Chí Minh 70000");
|
||||||
|
COUNTRY_CONFIGS.put("VN", vn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
146
mt-pay/src/main/java/com/mtkj/mtpay/util/HttpGet.java
Normal file
146
mt-pay/src/main/java/com/mtkj/mtpay/util/HttpGet.java
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package com.mtkj.mtpay.util;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP GET请求工具类
|
||||||
|
*/
|
||||||
|
class HttpGet {
|
||||||
|
protected static final int SOCKET_TIMEOUT = 10000;
|
||||||
|
protected static final String GET = "GET";
|
||||||
|
private static TrustManager myX509TrustManager = new X509TrustManager() {
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static String get(String host, Map<String, String> params) {
|
||||||
|
try {
|
||||||
|
SSLContext sslcontext = SSLContext.getInstance("TLS");
|
||||||
|
sslcontext.init((KeyManager[])null, new TrustManager[]{myX509TrustManager}, (SecureRandom)null);
|
||||||
|
String sendUrl = getUrlWithQueryString(host, params);
|
||||||
|
URL uri = new URL(sendUrl);
|
||||||
|
HttpURLConnection conn = (HttpURLConnection)uri.openConnection();
|
||||||
|
if (conn instanceof HttpsURLConnection) {
|
||||||
|
((HttpsURLConnection)conn).setSSLSocketFactory(sslcontext.getSocketFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.setConnectTimeout(10000);
|
||||||
|
conn.setRequestMethod("GET");
|
||||||
|
int statusCode = conn.getResponseCode();
|
||||||
|
if (statusCode != 200) {
|
||||||
|
System.out.println("Http错误码:" + statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream is = conn.getInputStream();
|
||||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(is));
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
String line = null;
|
||||||
|
|
||||||
|
while((line = br.readLine()) != null) {
|
||||||
|
builder.append(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
String text = builder.toString();
|
||||||
|
close(br);
|
||||||
|
close(is);
|
||||||
|
conn.disconnect();
|
||||||
|
return text;
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (KeyManagementException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getUrlWithQueryString(String url, Map<String, String> params) {
|
||||||
|
if (url == null || url.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("URL cannot be null or empty");
|
||||||
|
}
|
||||||
|
if (params == null) {
|
||||||
|
return url;
|
||||||
|
} else {
|
||||||
|
StringBuilder builder = new StringBuilder(url);
|
||||||
|
if (url.contains("?")) {
|
||||||
|
builder.append("&");
|
||||||
|
} else {
|
||||||
|
builder.append("?");
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
for(String key : params.keySet()) {
|
||||||
|
String value = (String)params.get(key);
|
||||||
|
if (value != null) {
|
||||||
|
if (i != 0) {
|
||||||
|
builder.append('&');
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append(key);
|
||||||
|
builder.append('=');
|
||||||
|
builder.append(encode(value));
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void close(Closeable closeable) {
|
||||||
|
if (closeable != null) {
|
||||||
|
try {
|
||||||
|
closeable.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encode(String input) {
|
||||||
|
if (input == null) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return URLEncoder.encode(input, "utf-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
88
mt-pay/src/main/java/com/mtkj/mtpay/util/MD5.java
Normal file
88
mt-pay/src/main/java/com/mtkj/mtpay/util/MD5.java
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package com.mtkj.mtpay.util;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MD5工具类
|
||||||
|
*/
|
||||||
|
public class MD5 {
|
||||||
|
private static final char[] hexDigits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||||
|
|
||||||
|
public static String md5(String input) {
|
||||||
|
if (input == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
|
||||||
|
byte[] inputByteArray = input.getBytes();
|
||||||
|
messageDigest.update(inputByteArray);
|
||||||
|
byte[] resultByteArray = messageDigest.digest();
|
||||||
|
return byteArrayToHex(resultByteArray);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String md5(File file) {
|
||||||
|
try {
|
||||||
|
if (!file.isFile()) {
|
||||||
|
System.err.println("文件" + file.getAbsolutePath() + "不存在或者不是文件");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInputStream in = new FileInputStream(file);
|
||||||
|
String result = md5((InputStream)in);
|
||||||
|
in.close();
|
||||||
|
return result;
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String md5(InputStream in) {
|
||||||
|
try {
|
||||||
|
MessageDigest messagedigest = MessageDigest.getInstance("MD5");
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int read = 0;
|
||||||
|
|
||||||
|
while((read = in.read(buffer)) != -1) {
|
||||||
|
messagedigest.update(buffer, 0, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
in.close();
|
||||||
|
String result = byteArrayToHex(messagedigest.digest());
|
||||||
|
return result;
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String byteArrayToHex(byte[] byteArray) {
|
||||||
|
char[] resultCharArray = new char[byteArray.length * 2];
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
for(byte b : byteArray) {
|
||||||
|
resultCharArray[index++] = hexDigits[b >>> 4 & 15];
|
||||||
|
resultCharArray[index++] = hexDigits[b & 15];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(resultCharArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user