recharge接入实时汇率
This commit is contained in:
parent
860ebd5a21
commit
6b78840f5e
@ -26,9 +26,11 @@ import com.red.circle.tool.core.num.ArithmeticUtils;
|
|||||||
import com.red.circle.tool.core.sequence.IdWorkerUtils;
|
import com.red.circle.tool.core.sequence.IdWorkerUtils;
|
||||||
import com.red.circle.tool.core.text.StringUtils;
|
import com.red.circle.tool.core.text.StringUtils;
|
||||||
import com.red.circle.other.inner.endpoint.user.region.UserRegionClient;
|
import com.red.circle.other.inner.endpoint.user.region.UserRegionClient;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,6 +38,7 @@ import org.springframework.stereotype.Component;
|
|||||||
*
|
*
|
||||||
* @author pengliang on 2021/7/9
|
* @author pengliang on 2021/7/9
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class PayWebPlaceAnOrderCmdExe {
|
public class PayWebPlaceAnOrderCmdExe {
|
||||||
@ -82,9 +85,11 @@ public class PayWebPlaceAnOrderCmdExe {
|
|||||||
details.setSysOrigin(application.getAppCode());
|
details.setSysOrigin(application.getAppCode());
|
||||||
details.setAcceptUserId(cmd.getUserId());
|
details.setAcceptUserId(cmd.getUserId());
|
||||||
details.setCreateUserId(cmd.getUserId());
|
details.setCreateUserId(cmd.getUserId());
|
||||||
|
// 获取实时汇率,失败时使用配置的汇率作为备选
|
||||||
|
BigDecimal exchangeRate = getRealTimeExchangeRate(payCountry);
|
||||||
details.setAmount(PayAmountArithmeticUtils.multiplyUpNotPoint(
|
details.setAmount(PayAmountArithmeticUtils.multiplyUpNotPoint(
|
||||||
commodity.getAmountUsd(),
|
commodity.getAmountUsd(),
|
||||||
payCountry.getUsdExchangeRate()
|
exchangeRate
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
details.setAmountUsd(commodity.getAmountUsd());
|
details.setAmountUsd(commodity.getAmountUsd());
|
||||||
@ -120,6 +125,33 @@ public class PayWebPlaceAnOrderCmdExe {
|
|||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实时汇率,失败时返回配置的汇率
|
||||||
|
* @param payCountry 支付国家配置
|
||||||
|
* @return 汇率
|
||||||
|
*/
|
||||||
|
private BigDecimal getRealTimeExchangeRate(PayCountry payCountry) {
|
||||||
|
BigDecimal realTimeRate = null;
|
||||||
|
try {
|
||||||
|
// 调用实时汇率查询 - 这里假设目标币种是payCountry对应的币种
|
||||||
|
// 您可以根据实际需要调整币种参数
|
||||||
|
realTimeRate = payPlaceAnOrderService.getRealTimeExchangeRate("USD", payCountry.getCurrency());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to get real-time exchange rate for country {}, will use configured rate as fallback",
|
||||||
|
payCountry.getId(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果实时汇率获取失败,使用配置的汇率作为备选
|
||||||
|
BigDecimal exchangeRate = realTimeRate != null ? realTimeRate : payCountry.getUsdExchangeRate();
|
||||||
|
|
||||||
|
// 记录使用的汇率来源
|
||||||
|
log.info("Using {} exchange rate: {} for country {} amount calculation",
|
||||||
|
realTimeRate != null ? "real-time" : "configured",
|
||||||
|
exchangeRate, payCountry.getId());
|
||||||
|
|
||||||
|
return exchangeRate;
|
||||||
|
}
|
||||||
|
|
||||||
private void rechargeRecord(PayPlaceAnOrderDetailsV2 details) {
|
private void rechargeRecord(PayPlaceAnOrderDetailsV2 details) {
|
||||||
if (Objects.equals(details.getNewVersion(), Boolean.TRUE)) {
|
if (Objects.equals(details.getNewVersion(), Boolean.TRUE)) {
|
||||||
userFreightRechargeRecordGateway.inrNowMonthAmount(
|
userFreightRechargeRecordGateway.inrNowMonthAmount(
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
package com.red.circle.order.app.command.pay.web.strategy;
|
package com.red.circle.order.app.command.pay.web.strategy;
|
||||||
|
|
||||||
import com.google.api.client.util.Maps;
|
import com.google.api.client.util.Maps;
|
||||||
|
import com.red.circle.component.pay.paymax.PayMaxUtils;
|
||||||
import com.red.circle.framework.core.request.RequestClientEnum;
|
import com.red.circle.framework.core.request.RequestClientEnum;
|
||||||
import com.red.circle.framework.web.props.EnvProperties;
|
import com.red.circle.framework.web.props.EnvProperties;
|
||||||
|
import com.red.circle.order.app.common.PayerMaxProperties;
|
||||||
|
import com.red.circle.order.app.common.QuoteQueryParam;
|
||||||
|
import com.red.circle.order.app.common.QuoteQueryResponse;
|
||||||
import com.red.circle.order.app.dto.clientobject.pay.PlaceAnOrderResponseCO;
|
import com.red.circle.order.app.dto.clientobject.pay.PlaceAnOrderResponseCO;
|
||||||
import com.red.circle.order.infra.database.mongo.order.InAppPurchaseDetailsService;
|
import com.red.circle.order.infra.database.mongo.order.InAppPurchaseDetailsService;
|
||||||
import com.red.circle.order.infra.database.mongo.order.entity.InAppPurchaseDetails;
|
import com.red.circle.order.infra.database.mongo.order.entity.InAppPurchaseDetails;
|
||||||
@ -11,13 +15,19 @@ import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurcha
|
|||||||
import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseStatusStep;
|
import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseStatusStep;
|
||||||
import com.red.circle.order.inner.model.enums.inapp.InAppPurchaseStatus;
|
import com.red.circle.order.inner.model.enums.inapp.InAppPurchaseStatus;
|
||||||
import com.red.circle.tool.core.date.TimestampUtils;
|
import com.red.circle.tool.core.date.TimestampUtils;
|
||||||
|
import com.red.circle.tool.core.http.RcHttpClient;
|
||||||
|
import com.red.circle.tool.core.json.JacksonUtils;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author pengliang on 2022/11/2
|
* @author pengliang on 2022/11/2
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class PayPlaceAnOrderService {
|
public class PayPlaceAnOrderService {
|
||||||
@ -25,6 +35,8 @@ public class PayPlaceAnOrderService {
|
|||||||
private final EnvProperties envProperties;
|
private final EnvProperties envProperties;
|
||||||
private final InAppPurchaseDetailsService inAppPurchaseDetailsService;
|
private final InAppPurchaseDetailsService inAppPurchaseDetailsService;
|
||||||
private final FactoryPlaceAnOrderStrategyV2 factoryPlaceAnOrderStrategyV2;
|
private final FactoryPlaceAnOrderStrategyV2 factoryPlaceAnOrderStrategyV2;
|
||||||
|
private final PayerMaxProperties payerMaxProperties;
|
||||||
|
private final RcHttpClient rcHttpClient = RcHttpClient.builder().build();
|
||||||
|
|
||||||
public PlaceAnOrderResponseCO doRequest(PayPlaceAnOrderDetailsV2 param) {
|
public PlaceAnOrderResponseCO doRequest(PayPlaceAnOrderDetailsV2 param) {
|
||||||
// 第三方:请求下单
|
// 第三方:请求下单
|
||||||
@ -85,4 +97,59 @@ public class PayPlaceAnOrderService {
|
|||||||
return envProperties.isProd() ? PayEnv.PROD.name() : PayEnv.TEST.name();
|
return envProperties.isProd() ? PayEnv.PROD.name() : PayEnv.TEST.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询实时汇率
|
||||||
|
* @param baseCurrency 基础币种(如USD)
|
||||||
|
* @param targetCurrency 目标币种(如CNY)
|
||||||
|
* @return 汇率值
|
||||||
|
*/
|
||||||
|
public BigDecimal getRealTimeExchangeRate(String baseCurrency, String targetCurrency) {
|
||||||
|
try {
|
||||||
|
QuoteQueryParam param = new QuoteQueryParam();
|
||||||
|
QuoteQueryParam.QuoteQueryData data = new QuoteQueryParam.QuoteQueryData();
|
||||||
|
|
||||||
|
// 币种对格式:基础币种+目标币种,如USDCNY
|
||||||
|
String ccyPair = baseCurrency + targetCurrency;
|
||||||
|
data.setCcyPairList(Arrays.asList(ccyPair));
|
||||||
|
data.setPriceTypeList(Arrays.asList("OFFER")); // 使用卖出价
|
||||||
|
|
||||||
|
param.setAppId(payerMaxProperties.getAppId());
|
||||||
|
param.setMerchantNo(payerMaxProperties.getMerchantId());
|
||||||
|
param.setData(data);
|
||||||
|
|
||||||
|
String url = payerMaxProperties.getGatewayBaseUrl() + "/aggregate-pay/api/gateway/quoteQuery";
|
||||||
|
String jsonParam = JacksonUtils.toJson(param);
|
||||||
|
String signature = PayMaxUtils.signRsa(jsonParam, payerMaxProperties.getRsaPrivateKey());
|
||||||
|
|
||||||
|
if (!envProperties.isProd()) {
|
||||||
|
log.info("Exchange Rate Query Request: {}\nParam:{}\nSign:{}", url, jsonParam, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
QuoteQueryResponse response = rcHttpClient.post()
|
||||||
|
.uri(url)
|
||||||
|
.header("sign", signature)
|
||||||
|
.contentType("application/json")
|
||||||
|
.bodyValue(jsonParam)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToEntity(QuoteQueryResponse.class)
|
||||||
|
.block();
|
||||||
|
|
||||||
|
if (response != null && response.isSuccess() && response.getData() != null
|
||||||
|
&& response.getData().getRateList() != null && !response.getData().getRateList().isEmpty()) {
|
||||||
|
// 使用offerRate(卖出价)作为汇率
|
||||||
|
BigDecimal rate = response.getData().getRateList().get(0).getOfferRate();
|
||||||
|
log.info("Successfully got real-time exchange rate from {} to {}: {}", baseCurrency, targetCurrency, rate);
|
||||||
|
return rate;
|
||||||
|
} else {
|
||||||
|
log.error("Failed to get real-time exchange rate, response: {}",
|
||||||
|
response != null ? JacksonUtils.toJson(response) : "null");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error occurred while querying real-time exchange rate from {} to {}",
|
||||||
|
baseCurrency, targetCurrency, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,71 @@
|
|||||||
|
package com.red.circle.order.app.common;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayerMax配置属性
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "red-circle.pay.payer-max")
|
||||||
|
public class PayerMaxProperties {
|
||||||
|
|
||||||
|
private String secretKey;
|
||||||
|
private String merchantId;
|
||||||
|
private String gatewayBaseUrl;
|
||||||
|
private String rsaPrivateKey;
|
||||||
|
private String platformRsaPublicKey;
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
public String getSecretKey() {
|
||||||
|
return secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecretKey(String secretKey) {
|
||||||
|
this.secretKey = secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMerchantId() {
|
||||||
|
return merchantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMerchantId(String merchantId) {
|
||||||
|
this.merchantId = merchantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGatewayBaseUrl() {
|
||||||
|
return gatewayBaseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGatewayBaseUrl(String gatewayBaseUrl) {
|
||||||
|
this.gatewayBaseUrl = gatewayBaseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRsaPrivateKey() {
|
||||||
|
return rsaPrivateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRsaPrivateKey(String rsaPrivateKey) {
|
||||||
|
this.rsaPrivateKey = rsaPrivateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPlatformRsaPublicKey() {
|
||||||
|
return platformRsaPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlatformRsaPublicKey(String platformRsaPublicKey) {
|
||||||
|
this.platformRsaPublicKey = platformRsaPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppId() {
|
||||||
|
return appId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppId(String appId) {
|
||||||
|
this.appId = appId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
package com.red.circle.order.app.common;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayerMax汇率查询请求参数
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class QuoteQueryParam {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接口版本 当前值为:1.2
|
||||||
|
*/
|
||||||
|
@JsonProperty("version")
|
||||||
|
private String version = "1.2";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密钥版本 当前值为:1
|
||||||
|
*/
|
||||||
|
@JsonProperty("keyVersion")
|
||||||
|
private String keyVersion = "1";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求时间,符合rfc3339规范,格式:yyyy-MM-dd'T'HH:mm:ss.SSSXXX
|
||||||
|
*/
|
||||||
|
@JsonProperty("requestTime")
|
||||||
|
private String requestTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户应用Id
|
||||||
|
*/
|
||||||
|
@JsonProperty("appId")
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户号
|
||||||
|
*/
|
||||||
|
@JsonProperty("merchantNo")
|
||||||
|
private String merchantNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据部分
|
||||||
|
*/
|
||||||
|
@JsonProperty("data")
|
||||||
|
private QuoteQueryData data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class QuoteQueryData {
|
||||||
|
/**
|
||||||
|
* 币种对列表,基于美金标价法拼接,USD在前,例如:USDIDR
|
||||||
|
*/
|
||||||
|
@JsonProperty("ccyPairList")
|
||||||
|
private List<String> ccyPairList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 价格类型列表,买入价BID,卖出价OFFER,默认查询全部
|
||||||
|
*/
|
||||||
|
@JsonProperty("priceTypeList")
|
||||||
|
private List<String> priceTypeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QuoteQueryParam() {
|
||||||
|
// 设置当前时间,格式:yyyy-MM-dd'T'HH:mm:ss.SSSXXX(示例:2022-06-10T20:30:53.732+08:00)
|
||||||
|
this.requestTime = OffsetDateTime.now(ZoneId.of("+08:00"))
|
||||||
|
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
package com.red.circle.order.app.common;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayerMax汇率查询响应
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class QuoteQueryResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回码,'APPLY_SUCCESS'代表成功
|
||||||
|
*/
|
||||||
|
@JsonProperty("code")
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回描述
|
||||||
|
*/
|
||||||
|
@JsonProperty("msg")
|
||||||
|
private String msg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应数据
|
||||||
|
*/
|
||||||
|
@JsonProperty("data")
|
||||||
|
private ResponseData data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否成功
|
||||||
|
*/
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return "APPLY_SUCCESS".equals(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class ResponseData {
|
||||||
|
/**
|
||||||
|
* 汇率列表
|
||||||
|
*/
|
||||||
|
@JsonProperty("rateList")
|
||||||
|
private List<RateInfo> rateList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class RateInfo {
|
||||||
|
/**
|
||||||
|
* 币种对
|
||||||
|
*/
|
||||||
|
@JsonProperty("ccyPair")
|
||||||
|
private String ccyPair;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 买入价,默认8位小数
|
||||||
|
*/
|
||||||
|
@JsonProperty("bidRate")
|
||||||
|
private BigDecimal bidRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卖出价,默认8位小数
|
||||||
|
*/
|
||||||
|
@JsonProperty("offerRate")
|
||||||
|
private BigDecimal offerRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 汇率时间,格式yyyy-MM-dd'T'HH:mm:ss.SSSXXX
|
||||||
|
*/
|
||||||
|
@JsonProperty("rateTime")
|
||||||
|
private String rateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本
|
||||||
|
*/
|
||||||
|
@JsonProperty("version")
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过期时间,格式yyyy-MM-dd'T'HH:mm:ss.SSSXXX
|
||||||
|
*/
|
||||||
|
@JsonProperty("expiryTime")
|
||||||
|
private String expiryTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user