v5pay
This commit is contained in:
@@ -511,6 +511,8 @@ public class Constant {
|
||||
public static final String razer = "razer";
|
||||
// give
|
||||
public static final String give = "give";
|
||||
// give
|
||||
public static final String v5pay = "v5pay";
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1460,7 +1460,9 @@ public enum RedisKey {
|
||||
first_charge_ip, //首充ip状态
|
||||
first_charge_device, //首充设备状态
|
||||
|
||||
lock_user_pack; //礼包锁
|
||||
lock_user_pack, //礼包锁
|
||||
|
||||
v5pay_lock, //v5pay支付锁
|
||||
;
|
||||
|
||||
public String getKey() {
|
||||
|
@@ -0,0 +1,42 @@
|
||||
package com.accompany.payment.v5pay;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class V5PayResponseVo {
|
||||
|
||||
// ============== 基础响应参数 ==============
|
||||
/** 响应码(4位) */
|
||||
private String code;
|
||||
|
||||
/** 响应描述 */
|
||||
private String message;
|
||||
|
||||
/** 页面支付地址 */
|
||||
private String checkoutUrl;
|
||||
|
||||
/** 过期时间(时间戳) */
|
||||
private Long expiresAt;
|
||||
|
||||
/** 签名(32位) */
|
||||
private String sign;
|
||||
|
||||
// ============== 换汇交易额外参数 ==============
|
||||
/** 换汇前币种(3位,如 USD) */
|
||||
private String fromCurrency;
|
||||
|
||||
/** 换汇后币种(3位,如 EUR) */
|
||||
private String toCurrency;
|
||||
|
||||
/** 原始金额(换汇前,下单币种) */
|
||||
private Number originPayinAmt;
|
||||
|
||||
/** 交易金额(换汇后,用户付款金额) */
|
||||
private Number payinAmt;
|
||||
|
||||
/** 汇率 */
|
||||
private Number exchangeRate;
|
||||
|
||||
/** 汇率时间(时间戳) */
|
||||
private Long rateDateTime;
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
package com.accompany.payment.strategy;
|
||||
|
||||
import com.accompany.common.constant.Constant;
|
||||
import com.accompany.common.status.BusiStatus;
|
||||
import com.accompany.core.base.UidContextHolder;
|
||||
import com.accompany.core.exception.ServiceException;
|
||||
import com.accompany.payment.annotation.PayChannelSupport;
|
||||
import com.accompany.payment.constant.ChargeUserLimitConstant;
|
||||
import com.accompany.payment.constant.PayConstant;
|
||||
import com.accompany.payment.model.ChargeProd;
|
||||
import com.accompany.payment.model.ChargeRecord;
|
||||
import com.accompany.payment.service.ChargeUserLimitService;
|
||||
import com.accompany.payment.v5pay.V5PayResponseVo;
|
||||
import com.accompany.payment.v5pay.V5PayService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@PayChannelSupport(Constant.ChargeChannel.v5pay)
|
||||
public class V5PayStrategy extends AbstractPayStrategy {
|
||||
|
||||
@Autowired
|
||||
private ChargeUserLimitService chargeUserLimitService;
|
||||
|
||||
@Autowired
|
||||
private V5PayService v5PayService;
|
||||
|
||||
@Override
|
||||
public Object pay(PayContext context) throws Exception {
|
||||
chargeUserLimitService.chargeLimitCheck(UidContextHolder.get(), ChargeUserLimitConstant.LIMIT_TYPE_OF_H5);
|
||||
ChargeRecord chargeRecord = context.getChargeRecord();
|
||||
ChargeProd chargeProd = context.getChargeProd();
|
||||
V5PayResponseVo orderRes = v5PayService.createOrder(chargeRecord, chargeProd, context.getSuccessUrl());
|
||||
if (!orderRes.getCode().equalsIgnoreCase("1000")) {
|
||||
throw new ServiceException(BusiStatus.SERVERERROR);
|
||||
}
|
||||
Map<String, Object> appMap = new HashMap<>();
|
||||
appMap.put(PayConstant.H5_PAY_URL_FIELD, orderRes.getCheckoutUrl());
|
||||
appMap.put(PayConstant.H5_PAY_NICK_FIELD, context.getNick());
|
||||
appMap.put(PayConstant.H5_PAY_ERBANNO_FIELD, context.getErbanNo());
|
||||
return appMap;
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package com.accompany.payment.v5pay;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties("v5pay")
|
||||
@Data
|
||||
public class V5PayConfig {
|
||||
private String appKey;
|
||||
private String secretKey;
|
||||
private String merchantNo;
|
||||
private String createUrl;
|
||||
private String callbackUrl;
|
||||
private String redirectUrl;
|
||||
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
package com.accompany.payment.v5pay;
|
||||
|
||||
import com.accompany.common.utils.HttpUtils;
|
||||
import com.accompany.payment.model.ChargeProd;
|
||||
import com.accompany.payment.model.ChargeRecord;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* https://api-doc.v5pay.com/#/zh-cn/common/payin-cashier
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class V5PayService {
|
||||
|
||||
@Autowired
|
||||
private V5PayConfig v5PayConfig;
|
||||
|
||||
public V5PayResponseVo createOrder(ChargeRecord chargeRecord, ChargeProd chargeProd, String successUrl) {
|
||||
try {
|
||||
Map<String, Object> formMap = new HashMap<>();
|
||||
formMap.put("merchantNo", v5PayConfig.getMerchantNo());
|
||||
formMap.put("appKey", v5PayConfig.getAppKey());
|
||||
formMap.put("sysCountryCode", chargeProd.getCountry());
|
||||
formMap.put("currency", chargeRecord.getLocalCurrencyCode());
|
||||
formMap.put("orderNo", chargeRecord.getChargeRecordId());
|
||||
formMap.put("amount", String.valueOf(BigDecimal.valueOf(chargeRecord.getLocalAmount())
|
||||
.divide(BigDecimal.valueOf(100)).setScale(2, RoundingMode.DOWN)));
|
||||
|
||||
formMap.put("sign", signature(buildPlainText(formMap)));
|
||||
|
||||
formMap.put("redirectUrl", successUrl);
|
||||
formMap.put("callbackUrl", v5PayConfig.getCallbackUrl());
|
||||
Map<String, String> headerMap = new HashMap<>();
|
||||
headerMap.put("Content-Type", "application/json");
|
||||
String jsonString = JSON.toJSONString(formMap);
|
||||
log.info("v5pay-post :{}", jsonString);
|
||||
String resultBody = HttpUtils.doPostForJson(v5PayConfig.getCreateUrl(), jsonString);
|
||||
log.info("V5PayService.createOrder resultBody:{}" , resultBody);
|
||||
JSONObject jsonObject = JSON.parseObject(resultBody);
|
||||
Map<String, Object> resultObject = convertJsonToMapExcludeSign(jsonObject);
|
||||
String responseSign = signature(buildPlainText(resultObject));
|
||||
|
||||
if (responseSign.equalsIgnoreCase(jsonObject.getString("sign"))) {
|
||||
return JSONObject.parseObject(resultBody, V5PayResponseVo.class);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("V5PayService.createOrder:e:{}", e);
|
||||
}
|
||||
return new V5PayResponseVo();
|
||||
}
|
||||
|
||||
public Map<String, Object> convertJsonToMapExcludeSign(JSONObject jsonObject) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (!"sign".equals(key)) { // 排除 sign 字段
|
||||
map.put(key, entry.getValue());
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public String buildPlainText(Map<String, Object> data) {
|
||||
return data.entrySet().stream()
|
||||
// 过滤掉 value 为 null 或空字符串的键值对
|
||||
.filter(entry -> entry.getValue() != null)
|
||||
// 按 key 的字典序排序
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
// 拼接成 key=value 格式
|
||||
.map(entry -> entry.getKey() + "=" + entry.getValue())
|
||||
// 用 & 连接所有键值对
|
||||
.collect(Collectors.joining("&"));
|
||||
}
|
||||
|
||||
public String signature(String signStr) {
|
||||
return DigestUtils.md5Hex(signStr + v5PayConfig.getSecretKey());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user