|
@@ -0,0 +1,652 @@
|
|
|
|
|
+package com.izouma.nineth.service;
|
|
|
|
|
+
|
|
|
|
|
+import cn.com.sandpay.cashier.sdk.*;
|
|
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
+import com.izouma.nineth.config.GeneralProperties;
|
|
|
|
|
+import com.izouma.nineth.config.SandPayProperties;
|
|
|
|
|
+import com.izouma.nineth.domain.GiftOrder;
|
|
|
|
|
+import com.izouma.nineth.domain.MintOrder;
|
|
|
|
|
+import com.izouma.nineth.domain.Order;
|
|
|
|
|
+import com.izouma.nineth.enums.MintOrderStatus;
|
|
|
|
|
+import com.izouma.nineth.enums.OrderStatus;
|
|
|
|
|
+import com.izouma.nineth.exception.BusinessException;
|
|
|
|
|
+import com.izouma.nineth.repo.GiftOrderRepo;
|
|
|
|
|
+import com.izouma.nineth.repo.MintOrderRepo;
|
|
|
|
|
+import com.izouma.nineth.repo.OrderRepo;
|
|
|
|
|
+import com.izouma.nineth.utils.DateTimeUtils;
|
|
|
|
|
+import com.izouma.nineth.utils.SnowflakeIdWorker;
|
|
|
|
|
+import lombok.AllArgsConstructor;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.apache.commons.codec.binary.Base64;
|
|
|
|
|
+import org.springframework.cache.annotation.Cacheable;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
|
|
+import java.net.URLDecoder;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+import java.text.DecimalFormat;
|
|
|
|
|
+import java.text.DecimalFormatSymbols;
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.Locale;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.Optional;
|
|
|
|
|
+
|
|
|
|
|
+@Service
|
|
|
|
|
+@AllArgsConstructor
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+public class SandPayService {
|
|
|
|
|
+ private final OrderRepo orderRepo;
|
|
|
|
|
+ private final GiftOrderRepo giftOrderRepo;
|
|
|
|
|
+ private final SandPayProperties sandPayProperties;
|
|
|
|
|
+ private final MintOrderRepo mintOrderRepo;
|
|
|
|
|
+ private final SnowflakeIdWorker snowflakeIdWorker;
|
|
|
|
|
+ private final GeneralProperties generalProperties;
|
|
|
|
|
+
|
|
|
|
|
+ public String paddingOrderId(String orderId) {
|
|
|
|
|
+ if (orderId != null && orderId.length() < 12) {
|
|
|
|
|
+ StringBuilder orderIdBuilder = new StringBuilder(orderId);
|
|
|
|
|
+ for (int i = orderIdBuilder.length(); i < 12; i++) {
|
|
|
|
|
+ orderIdBuilder.insert(0, "0");
|
|
|
|
|
+ }
|
|
|
|
|
+ orderId = orderIdBuilder.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+ return orderId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public String getReqTime() {
|
|
|
|
|
+ return DateTimeUtils.format(LocalDateTime.now(), "yyyyMMddHHmmss");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static String getTimeout(int seconds) {
|
|
|
|
|
+ return DateTimeUtils.format(LocalDateTime.now().plusSeconds(seconds), "yyyyMMddHHmmss");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static String getTimeout(LocalDateTime createTime, int seconds) {
|
|
|
|
|
+ return DateTimeUtils.format(Optional.ofNullable(createTime).orElse(LocalDateTime.now())
|
|
|
|
|
+ .plusSeconds(seconds), "yyyyMMddHHmmss");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public String convertAmount(BigDecimal amount) {
|
|
|
|
|
+ DecimalFormat df = new DecimalFormat("000000000000", DecimalFormatSymbols.getInstance(Locale.US));
|
|
|
|
|
+ return df.format(amount.multiply(new BigDecimal("100")));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public JSONObject requestServer(JSONObject header, JSONObject body, String reqAddr) {
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, String> reqMap = new HashMap<String, String>();
|
|
|
|
|
+ JSONObject reqJson = new JSONObject();
|
|
|
|
|
+ reqJson.put("head", header);
|
|
|
|
|
+ reqJson.put("body", body);
|
|
|
|
|
+ String reqStr = reqJson.toJSONString();
|
|
|
|
|
+ String reqSign;
|
|
|
|
|
+ // 签名
|
|
|
|
|
+ try {
|
|
|
|
|
+ reqSign = new String(Base64.encodeBase64(CryptoUtil.digitalSign(reqStr.getBytes(StandardCharsets.UTF_8),
|
|
|
|
|
+ CertUtil.getPrivateKey(), "SHA1WithRSA")));
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error(e.getMessage());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ //整体报文格式
|
|
|
|
|
+ reqMap.put("charset", "UTF-8");
|
|
|
|
|
+ reqMap.put("data", reqStr);
|
|
|
|
|
+ reqMap.put("signType", "01");
|
|
|
|
|
+ reqMap.put("sign", reqSign);
|
|
|
|
|
+ reqMap.put("extend", "");
|
|
|
|
|
+
|
|
|
|
|
+ String result;
|
|
|
|
|
+ try {
|
|
|
|
|
+ log.info("请求报文:\n{}", JSONObject.toJSONString(reqJson, true));
|
|
|
|
|
+ result = HttpClient.doPost(reqAddr, reqMap, 30000, 30000);
|
|
|
|
|
+ result = URLDecoder.decode(result, StandardCharsets.UTF_8);
|
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
|
+ log.error(e.getMessage());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, String> respMap = SDKUtil.convertResultStringToMap(result);
|
|
|
|
|
+ String respData = respMap.get("data");
|
|
|
|
|
+ String respSign = respMap.get("sign");
|
|
|
|
|
+
|
|
|
|
|
+ // 验证签名
|
|
|
|
|
+ boolean valid;
|
|
|
|
|
+ try {
|
|
|
|
|
+ valid = CryptoUtil.verifyDigitalSign(respData.getBytes(StandardCharsets.UTF_8),
|
|
|
|
|
+ Base64.decodeBase64(respSign), CertUtil.getPublicKey(), "SHA1WithRSA");
|
|
|
|
|
+ if (!valid) {
|
|
|
|
|
+ log.error("verify sign fail.");
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("verify sign success");
|
|
|
|
|
+ JSONObject respJson = JSONObject.parseObject(respData);
|
|
|
|
|
+ if (respJson != null) {
|
|
|
|
|
+ log.info("响应码:[{}]", respJson.getJSONObject("head").getString("respCode"));
|
|
|
|
|
+ log.info("响应描述:[{}]", respJson.getJSONObject("head").getString("respMsg"));
|
|
|
|
|
+ log.info("响应报文:\n{}", JSONObject.toJSONString(respJson, true));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ log.error("服务器请求异常!!!");
|
|
|
|
|
+ }
|
|
|
|
|
+ return respJson;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error(e.getMessage());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public String requestAlipay(String orderId, BigDecimal amount, String subject, String desc,
|
|
|
|
|
+ String timeout, String extend) {
|
|
|
|
|
+ JSONObject res = requestAlipayRaw(orderId, amount, subject, desc, timeout, extend);
|
|
|
|
|
+ if ("000000".equals(res.getJSONObject("head").getString("respCode"))) {
|
|
|
|
|
+ return "alipays://platformapi/startapp?saId=10000007&qrcode=" + res.getJSONObject("body")
|
|
|
|
|
+ .getString("qrCode");
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new BusinessException("绿洲宇宙冷却系统已启动,请稍后支付");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public JSONObject requestAlipayRaw(String orderId, BigDecimal amount, String subject, String desc,
|
|
|
|
|
+ String timeout, String extend) {
|
|
|
|
|
+ if (orderId.length() < 12) {
|
|
|
|
|
+ for (int i = orderId.length(); i < 12; i++) {
|
|
|
|
|
+ orderId = "0" + orderId;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject header = new JSONObject();
|
|
|
|
|
+ header.put("version", "1.0"); //版本号
|
|
|
|
|
+ header.put("method", "sandpay.trade.precreate"); //接口名称:统一下单并支付
|
|
|
|
|
+ header.put("productId", "00000006"); //产品编码
|
|
|
|
|
+ header.put("mid", sandPayProperties.getMid()); //商户号
|
|
|
|
|
+ header.put("accessType", "1"); //接入类型设置为普通商户接入
|
|
|
|
|
+ header.put("channelType", "07"); //渠道类型:07-互联网 08-移动端
|
|
|
|
|
+ header.put("reqTime", getReqTime()); //请求时间
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject body = new JSONObject();
|
|
|
|
|
+ body.put("payTool", "0401"); //支付工具: 固定填写0401
|
|
|
|
|
+ body.put("orderCode", orderId); //商户订单号
|
|
|
|
|
+ body.put("totalAmount", convertAmount(amount)); //订单金额 12位长度,精确到分
|
|
|
|
|
+ //body.put("limitPay", "5"); //限定支付方式 送1-限定不能使用贷记卡 送4-限定不能使用花呗 送5-限定不能使用贷记卡+花呗
|
|
|
|
|
+ body.put("subject", subject); //订单标题
|
|
|
|
|
+ body.put("body", desc); //订单描述
|
|
|
|
|
+ body.put("txnTimeOut", timeout); //订单超时时间
|
|
|
|
|
+ body.put("notifyUrl", sandPayProperties.getNotifyUrl()); //异步通知地址
|
|
|
|
|
+ body.put("bizExtendParams", ""); //业务扩展参数
|
|
|
|
|
+ body.put("merchExtendParams", ""); //商户扩展参数
|
|
|
|
|
+ body.put("extend", extend); //扩展域
|
|
|
|
|
+
|
|
|
|
|
+ return requestServer(header, body, "https://cashier.sandpay.com.cn/qr/api/order/create");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public JSONObject requestQuick(String orderId, BigDecimal amount, String subject, String desc,
|
|
|
|
|
+ int timeout, String extend, String frontUrl) {
|
|
|
|
|
+ if (orderId.length() < 12) {
|
|
|
|
|
+ for (int i = orderId.length(); i < 12; i++) {
|
|
|
|
|
+ orderId = "0" + orderId;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject header = new JSONObject();
|
|
|
|
|
+ header.put("version", "1.0"); //版本号
|
|
|
|
|
+ header.put("method", "sandpay.trade.pay"); //接口名称:统一下单
|
|
|
|
|
+ header.put("mid", sandPayProperties.getMid()); //商户号
|
|
|
|
|
+ header.put("accessType", "1"); //接入类型设置为平台商户接入 //接入类型设置为普通商户接入
|
|
|
|
|
+ header.put("channelType", "07"); //渠道类型:07-互联网 08-移动端
|
|
|
|
|
+ header.put("reqTime", getReqTime()); //请求时间
|
|
|
|
|
+ header.put("productId", "00000008"); //产品编码
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject body = new JSONObject();
|
|
|
|
|
+ body.put("orderCode", orderId); //商户订单号
|
|
|
|
|
+ body.put("totalAmount", convertAmount(amount)); //订单金额
|
|
|
|
|
+ body.put("subject", subject); //订单标题
|
|
|
|
|
+ body.put("body", desc); //订单描述
|
|
|
|
|
+ body.put("txnTimeOut", getTimeout(timeout)); //订单超时时间
|
|
|
|
|
+ body.put("clientIp", "192.168.22.55"); //客户端IP
|
|
|
|
|
+ body.put("limitPay", ""); //限定支付方式 送1-限定不能使用贷记卡送 4-限定不能使用花呗 送5-限定不能使用贷记卡+花呗
|
|
|
|
|
+ body.put("notifyUrl", sandPayProperties.getNotifyUrl()); //异步通知地址
|
|
|
|
|
+ body.put("frontUrl", frontUrl); //前台通知地址
|
|
|
|
|
+ body.put("storeId", ""); //商户门店编号
|
|
|
|
|
+ body.put("terminalId", ""); //商户终端编号
|
|
|
|
|
+ body.put("operatorId", ""); //操作员编号
|
|
|
|
|
+ body.put("clearCycle", ""); //清算模式
|
|
|
|
|
+ body.put("royaltyInfo", ""); //分账信息
|
|
|
|
|
+ body.put("riskRateInfo", ""); //风控信息域
|
|
|
|
|
+ body.put("bizExtendParams", ""); //业务扩展参数
|
|
|
|
|
+ body.put("merchExtendParams", ""); //商户扩展参数
|
|
|
|
|
+ body.put("extend", extend); //扩展域
|
|
|
|
|
+ body.put("payMode", "sand_h5"); //支付模式
|
|
|
|
|
+
|
|
|
|
|
+ return requestServer(header, body, "https://cashier.sandpay.com.cn/gateway/api/order/pay");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public JSONObject query(String orderId) {
|
|
|
|
|
+ JSONObject header = new JSONObject();
|
|
|
|
|
+ header.put("version", "1.0"); //版本号
|
|
|
|
|
+ header.put("method", "sandpay.trade.query"); //接口名称:订单查询
|
|
|
|
|
+ header.put("productId", "00000006"); //产品编码
|
|
|
|
|
+ header.put("mid", sandPayProperties.getMid()); //商户号
|
|
|
|
|
+ header.put("accessType", "1"); //接入类型设置为普通商户接入
|
|
|
|
|
+ header.put("channelType", "07"); //渠道类型:07-互联网 08-移动端
|
|
|
|
|
+ header.put("reqTime", getReqTime()); //请求时间
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject body = new JSONObject();
|
|
|
|
|
+ body.put("orderCode", orderId);
|
|
|
|
|
+ body.put("extend", ""); //扩展域
|
|
|
|
|
+
|
|
|
|
|
+ return requestServer(header, body, "https://cashier.sandpay.com.cn/qr/api/order/query");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public JSONObject refund(String orderId, BigDecimal amount) {
|
|
|
|
|
+ JSONObject header = new JSONObject();
|
|
|
|
|
+ header.put("version", "1.0"); //版本号
|
|
|
|
|
+ header.put("method", "sandpay.trade.refund"); //接口名称:退货
|
|
|
|
|
+ header.put("productId", "00000006"); //产品编码
|
|
|
|
|
+ header.put("mid", sandPayProperties.getMid()); //商户号
|
|
|
|
|
+ header.put("accessType", "1"); //接入类型设置为普通商户接入
|
|
|
|
|
+ header.put("channelType", "07"); //渠道类型:07-互联网 08-移动端
|
|
|
|
|
+ header.put("reqTime", getReqTime()); //请求时间
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject body = new JSONObject();
|
|
|
|
|
+ body.put("orderCode", snowflakeIdWorker.nextId()); //商户订单号
|
|
|
|
|
+ body.put("oriOrderCode", paddingOrderId(orderId)); //原交易订单号
|
|
|
|
|
+ body.put("refundAmount", convertAmount(amount)); //退货金额
|
|
|
|
|
+ body.put("refundReason", "退款"); //退货原因
|
|
|
|
|
+ body.put("notifyUrl", sandPayProperties.getNotifyUrl()); //异步通知地址
|
|
|
|
|
+ body.put("extend", "");
|
|
|
|
|
+
|
|
|
|
|
+ return requestServer(header, body, "https://cashier.sandpay.com.cn/qr/api/order/refund");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Cacheable(value = "sandPay", key = "#orderId")
|
|
|
|
|
+ public String payOrder(Long orderId) {
|
|
|
|
|
+ Order order = orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
|
|
|
|
|
+ if (order.getStatus() != OrderStatus.NOT_PAID) {
|
|
|
|
|
+ throw new BusinessException("订单状态错误");
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject extend = new JSONObject();
|
|
|
|
|
+ extend.put("type", "order");
|
|
|
|
|
+ extend.put("id", orderId);
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject res = requestAlipayRaw(orderId.toString(), order.getTotalPrice(), order.getName(), order.getName(),
|
|
|
|
|
+ getTimeout(order.getCreatedAt(), 180), extend.toJSONString());
|
|
|
|
|
+ if (res == null)
|
|
|
|
|
+ throw new BusinessException("下单失败,请稍后再试");
|
|
|
|
|
+
|
|
|
|
|
+ if (!"000000".equals(res.getJSONObject("head").getString("respCode"))) {
|
|
|
|
|
+ String msg = res.getJSONObject("head").getString("respMsg");
|
|
|
|
|
+ if (msg.contains("超限")) {
|
|
|
|
|
+ throw new BusinessException("超过商户单日额度");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (msg.contains("商户状态")) {
|
|
|
|
|
+ throw new BusinessException("超过商户单日额度");
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new BusinessException(msg);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return res.getJSONObject("body").getString("qrCode");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Cacheable(value = "sandPayQuick", key = "#orderId")
|
|
|
|
|
+ public String payOrderQuick(Long orderId) {
|
|
|
|
|
+ Order order = orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
|
|
|
|
|
+ if (order.getStatus() != OrderStatus.NOT_PAID) {
|
|
|
|
|
+ throw new BusinessException("订单状态错误");
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject extend = new JSONObject();
|
|
|
|
|
+ extend.put("type", "order");
|
|
|
|
|
+ extend.put("id", orderId);
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject res = requestQuick(orderId.toString(), order.getTotalPrice(), order.getName(), order.getName(),
|
|
|
|
|
+ 180, extend.toJSONString(), generalProperties.getHost() + "/9th/orderDetail?id=" + orderId);
|
|
|
|
|
+ if (res == null)
|
|
|
|
|
+ throw new BusinessException("下单失败,请稍后再试");
|
|
|
|
|
+
|
|
|
|
|
+ if (!"000000".equals(res.getJSONObject("head").getString("respCode"))) {
|
|
|
|
|
+ String msg = res.getJSONObject("head").getString("respMsg");
|
|
|
|
|
+ if (msg.contains("超限")) {
|
|
|
|
|
+ throw new BusinessException("超过商户单日额度");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (msg.contains("商户状态")) {
|
|
|
|
|
+ throw new BusinessException("超过商户单日额度");
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new BusinessException(msg);
|
|
|
|
|
+ }
|
|
|
|
|
+ return res.getJSONObject("body").getString("credential");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Cacheable(value = "sandPayQuick", key = "#orderId")
|
|
|
|
|
+ public String payGiftQuick(Long orderId) {
|
|
|
|
|
+ GiftOrder order = giftOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
|
|
|
|
|
+ if (order.getStatus() != OrderStatus.NOT_PAID) {
|
|
|
|
|
+ throw new BusinessException("订单状态错误");
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject extend = new JSONObject();
|
|
|
|
|
+ extend.put("type", "gift");
|
|
|
|
|
+ extend.put("id", orderId);
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject res = requestQuick(orderId.toString(), order.getGasPrice(), "转增" + order.getAssetId(),
|
|
|
|
|
+ "转增" + order.getAssetId(), 180, extend.toJSONString(),
|
|
|
|
|
+ generalProperties.getHost() + "/9th/");
|
|
|
|
|
+ if (res == null)
|
|
|
|
|
+ throw new BusinessException("下单失败,请稍后再试");
|
|
|
|
|
+
|
|
|
|
|
+ if (!"000000".equals(res.getJSONObject("head").getString("respCode"))) {
|
|
|
|
|
+ String msg = res.getJSONObject("head").getString("respMsg");
|
|
|
|
|
+ if (msg.contains("超限")) {
|
|
|
|
|
+ throw new BusinessException("超过商户单日额度");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (msg.contains("商户状态")) {
|
|
|
|
|
+ throw new BusinessException("超过商户单日额度");
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new BusinessException(msg);
|
|
|
|
|
+ }
|
|
|
|
|
+ return res.getJSONObject("body").getString("credential");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Cacheable(value = "sandPayQuick", key = "#orderId")
|
|
|
|
|
+ public String payMintQuick(Long orderId) {
|
|
|
|
|
+ MintOrder order = mintOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
|
|
|
|
|
+ if (order.getStatus() != MintOrderStatus.NOT_PAID) {
|
|
|
|
|
+ throw new BusinessException("订单状态错误");
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject extend = new JSONObject();
|
|
|
|
|
+ extend.put("type", "mintOrder");
|
|
|
|
|
+ extend.put("id", orderId);
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject res = requestQuick(orderId.toString(), order.getGasPrice(),
|
|
|
|
|
+ "铸造活动:" + order.getMintActivityId(), "铸造活动:" + order.getMintActivityId(),
|
|
|
|
|
+ 180, extend.toJSONString(), generalProperties.getHost() + "/9th/");
|
|
|
|
|
+ if (res == null)
|
|
|
|
|
+ throw new BusinessException("下单失败,请稍后再试");
|
|
|
|
|
+
|
|
|
|
|
+ if (!"000000".equals(res.getJSONObject("head").getString("respCode"))) {
|
|
|
|
|
+ String msg = res.getJSONObject("head").getString("respMsg");
|
|
|
|
|
+ if (msg.contains("超限")) {
|
|
|
|
|
+ throw new BusinessException("超过商户单日额度");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (msg.contains("商户状态")) {
|
|
|
|
|
+ throw new BusinessException("超过商户单日额度");
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new BusinessException(msg);
|
|
|
|
|
+ }
|
|
|
|
|
+ return res.getJSONObject("body").getString("credential");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Cacheable(value = "sandPay", key = "#orderId")
|
|
|
|
|
+ public String payGiftOrder(Long orderId) {
|
|
|
|
|
+ GiftOrder order = giftOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
|
|
|
|
|
+ if (order.getStatus() != OrderStatus.NOT_PAID) {
|
|
|
|
|
+ throw new BusinessException("订单状态错误");
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject extend = new JSONObject();
|
|
|
|
|
+ extend.put("type", "gift");
|
|
|
|
|
+ extend.put("id", orderId);
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject res = requestAlipayRaw(orderId.toString(), order.getGasPrice(), "转增" + order.getAssetId(),
|
|
|
|
|
+ "转增" + order.getAssetId(),
|
|
|
|
|
+ getTimeout(order.getCreatedAt(), 180), extend.toJSONString());
|
|
|
|
|
+ if (res == null)
|
|
|
|
|
+ throw new BusinessException("下单失败,请稍后再试");
|
|
|
|
|
+
|
|
|
|
|
+ if (!"000000".equals(res.getJSONObject("head").getString("respCode"))) {
|
|
|
|
|
+ String msg = res.getJSONObject("head").getString("respMsg");
|
|
|
|
|
+ if (msg.contains("超限")) {
|
|
|
|
|
+ throw new BusinessException("超过商户单日额度");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (msg.contains("商户状态")) {
|
|
|
|
|
+ throw new BusinessException("超过商户单日额度");
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new BusinessException(msg);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return res.getJSONObject("body").getString("qrCode");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Cacheable(value = "sandPay", key = "#orderId")
|
|
|
|
|
+ public String payMintOrder(Long orderId) {
|
|
|
|
|
+ MintOrder order = mintOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
|
|
|
|
|
+ if (order.getStatus() != MintOrderStatus.NOT_PAID) {
|
|
|
|
|
+ throw new BusinessException("订单状态错误");
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject extend = new JSONObject();
|
|
|
|
|
+ extend.put("type", "mintOrder");
|
|
|
|
|
+ extend.put("id", orderId);
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject res = requestAlipayRaw(orderId.toString(), order.getGasPrice(), "铸造活动:" + order.getMintActivityId(),
|
|
|
|
|
+ "铸造活动:" + order.getMintActivityId(), getTimeout(order.getCreatedAt(), 180), extend.toJSONString());
|
|
|
|
|
+ if (res == null)
|
|
|
|
|
+ throw new BusinessException("下单失败,请稍后再试");
|
|
|
|
|
+
|
|
|
|
|
+ if (!"000000".equals(res.getJSONObject("head").getString("respCode"))) {
|
|
|
|
|
+ String msg = res.getJSONObject("head").getString("respMsg");
|
|
|
|
|
+ if (msg.contains("超限")) {
|
|
|
|
|
+ throw new BusinessException("超过商户单日额度");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (msg.contains("商户状态")) {
|
|
|
|
|
+ throw new BusinessException("超过商户单日额度");
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new BusinessException(msg);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return res.getJSONObject("body").getString("qrCode");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ public JSONObject transfer(String id, String name, String bank, BigDecimal amount) {
|
|
|
|
|
+ JSONObject request = new JSONObject();
|
|
|
|
|
+ DecimalFormat df = new DecimalFormat("000000000000", DecimalFormatSymbols.getInstance(Locale.US));
|
|
|
|
|
+ request.put("version", "01"); //版本号
|
|
|
|
|
+ request.put("productId", "00000004"); //产品ID
|
|
|
|
|
+ request.put("tranTime", getReqTime()); //交易时间
|
|
|
|
|
+ request.put("orderCode", id); //订单号
|
|
|
|
|
+ request.put("timeOut", getTimeout(180)); //订单超时时间
|
|
|
|
|
+ request.put("tranAmt", df.format(amount.multiply(new BigDecimal("100")))); //金额
|
|
|
|
|
+ request.put("currencyCode", "156"); //币种
|
|
|
|
|
+ request.put("accAttr", "0"); //账户属性 0-对私 1-对公
|
|
|
|
|
+ request.put("accType", "4"); //账号类型 3-公司账户 4-银行卡
|
|
|
|
|
+ request.put("accNo", bank); //收款人账户号
|
|
|
|
|
+ request.put("accName", name); //收款人账户名
|
|
|
|
|
+ request.put("provNo", ""); //收款人开户省份编码
|
|
|
|
|
+ request.put("cityNo", ""); //收款人开会城市编码
|
|
|
|
|
+ request.put("bankName", ""); //收款账户开户行名称
|
|
|
|
|
+ request.put("bankType", ""); //收款人账户联行号
|
|
|
|
|
+ request.put("remark", "消费"); //摘要
|
|
|
|
|
+ request.put("payMode", ""); //付款模式
|
|
|
|
|
+ request.put("channelType", ""); //渠道类型
|
|
|
|
|
+ request.put("extendParams", ""); //业务扩展参数
|
|
|
|
|
+ request.put("reqReserved", ""); //请求方保留域
|
|
|
|
|
+ request.put("extend", ""); //扩展域
|
|
|
|
|
+ request.put("phone", ""); //手机号
|
|
|
|
|
+
|
|
|
|
|
+ String reqData = request.toJSONString();
|
|
|
|
|
+ log.info("请求数据:{}", reqData);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+
|
|
|
|
|
+ String aesKey = RandomStringGenerator.getRandomStringByLength(16);
|
|
|
|
|
+ byte[] aesKeyBytes = aesKey.getBytes(StandardCharsets.UTF_8);
|
|
|
|
|
+
|
|
|
|
|
+ byte[] plainBytes = reqData.getBytes(StandardCharsets.UTF_8);
|
|
|
|
|
+ String encryptData = new String(Base64.encodeBase64(
|
|
|
|
|
+ CryptoUtil.AESEncrypt(plainBytes, aesKeyBytes, "AES",
|
|
|
|
|
+ "AES/ECB/PKCS5Padding", null)), StandardCharsets.UTF_8);
|
|
|
|
|
+
|
|
|
|
|
+ String sign = new String(Base64.encodeBase64(
|
|
|
|
|
+ CryptoUtil.digitalSign(plainBytes, CertUtil.getPrivateKey(),
|
|
|
|
|
+ "SHA1WithRSA")), StandardCharsets.UTF_8);
|
|
|
|
|
+
|
|
|
|
|
+ String encryptKey = new String(Base64.encodeBase64(
|
|
|
|
|
+ CryptoUtil.RSAEncrypt(aesKeyBytes, CertUtil.getPublicKey(), 2048, 11,
|
|
|
|
|
+ "RSA/ECB/PKCS1Padding")), StandardCharsets.UTF_8);
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, String> reqMap = new HashMap<String, String>();
|
|
|
|
|
+ //整体报文格式
|
|
|
|
|
+ reqMap.put("transCode", "RTPM"); // 交易码
|
|
|
|
|
+ reqMap.put("accessType", "0"); // 接入类型
|
|
|
|
|
+ reqMap.put("merId", sandPayProperties.getMid()); // 合作商户ID 杉德系统分配,唯一标识
|
|
|
|
|
+ reqMap.put("encryptKey", encryptKey); // 加密后的AES秘钥
|
|
|
|
|
+ reqMap.put("encryptData", encryptData); // 加密后的请求/应答报文
|
|
|
|
|
+ reqMap.put("sign", sign); // 签名
|
|
|
|
|
+ reqMap.put("extend", ""); // 扩展域
|
|
|
|
|
+
|
|
|
|
|
+ String result;
|
|
|
|
|
+ try {
|
|
|
|
|
+ log.info("请求报文:{}", reqMap);
|
|
|
|
|
+ result = HttpClient.doPost("https://caspay.sandpay.com.cn/agent-main/openapi/agentpay",
|
|
|
|
|
+ reqMap, 300000, 300000);
|
|
|
|
|
+ result = URLDecoder.decode(result, StandardCharsets.UTF_8);
|
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
|
+ log.error(e.getMessage());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ log.info("响应报文:{}", result);
|
|
|
|
|
+ Map<String, String> responseMap = SDKUtil.convertResultStringToMap(result);
|
|
|
|
|
+
|
|
|
|
|
+ String retEncryptKey = responseMap.get("encryptKey");
|
|
|
|
|
+ String retEncryptData = responseMap.get("encryptData");
|
|
|
|
|
+ String retSign = responseMap.get("sign");
|
|
|
|
|
+
|
|
|
|
|
+ log.debug("retEncryptKey:[{}]", retEncryptKey);
|
|
|
|
|
+ log.debug("retEncryptData:[{}]", retEncryptData);
|
|
|
|
|
+ log.debug("retSign:[{}]", retSign);
|
|
|
|
|
+
|
|
|
|
|
+ byte[] decodeBase64KeyBytes = Base64.decodeBase64(retEncryptKey
|
|
|
|
|
+ .getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+
|
|
|
|
|
+ byte[] merchantAESKeyBytes = CryptoUtil.RSADecrypt(
|
|
|
|
|
+ decodeBase64KeyBytes, CertUtil.getPrivateKey(), 2048, 11,
|
|
|
|
|
+ "RSA/ECB/PKCS1Padding");
|
|
|
|
|
+
|
|
|
|
|
+ byte[] decodeBase64DataBytes = Base64.decodeBase64(retEncryptData.getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+
|
|
|
|
|
+ byte[] respDataBytes = CryptoUtil.AESDecrypt(decodeBase64DataBytes,
|
|
|
|
|
+ merchantAESKeyBytes, "AES", "AES/ECB/PKCS5Padding", null);
|
|
|
|
|
+
|
|
|
|
|
+ String respData = new String(respDataBytes, StandardCharsets.UTF_8);
|
|
|
|
|
+ log.info("retData:[" + respData + "]");
|
|
|
|
|
+ System.out.println("响应data数据:" + respData);
|
|
|
|
|
+
|
|
|
|
|
+ byte[] signBytes = Base64.decodeBase64(retSign.getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+
|
|
|
|
|
+ boolean isValid = CryptoUtil.verifyDigitalSign(respDataBytes, signBytes,
|
|
|
|
|
+ CertUtil.getPublicKey(), "SHA1WithRSA");
|
|
|
|
|
+
|
|
|
|
|
+ if (!isValid) {
|
|
|
|
|
+ log.error("verify sign fail.");
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("verify sign success");
|
|
|
|
|
+ System.out.println("verify sign success");
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject respJson = JSONObject.parseObject(respData);
|
|
|
|
|
+ return respJson;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+
|
|
|
|
|
+ log.error(e.getMessage());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public JSONObject queryTransfer(String tranTime, String orderId) {
|
|
|
|
|
+ JSONObject request = new JSONObject();
|
|
|
|
|
+ request.put("version", "01"); // 版本号
|
|
|
|
|
+ request.put("productId", "00000004"); // 产品ID
|
|
|
|
|
+ request.put("tranTime", tranTime); // 查询订单的交易时间
|
|
|
|
|
+ request.put("orderCode", orderId); // 要查询的订单号
|
|
|
|
|
+
|
|
|
|
|
+ String reqData = request.toJSONString();
|
|
|
|
|
+ log.info("请求数据:{}", reqData);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+
|
|
|
|
|
+ String aesKey = RandomStringGenerator.getRandomStringByLength(16);
|
|
|
|
|
+ byte[] aesKeyBytes = aesKey.getBytes(StandardCharsets.UTF_8);
|
|
|
|
|
+
|
|
|
|
|
+ byte[] plainBytes = reqData.getBytes(StandardCharsets.UTF_8);
|
|
|
|
|
+ String encryptData = new String(Base64.encodeBase64(
|
|
|
|
|
+ CryptoUtil.AESEncrypt(plainBytes, aesKeyBytes, "AES",
|
|
|
|
|
+ "AES/ECB/PKCS5Padding", null)), StandardCharsets.UTF_8);
|
|
|
|
|
+
|
|
|
|
|
+ String sign = new String(Base64.encodeBase64(
|
|
|
|
|
+ CryptoUtil.digitalSign(plainBytes, CertUtil.getPrivateKey(),
|
|
|
|
|
+ "SHA1WithRSA")), StandardCharsets.UTF_8);
|
|
|
|
|
+
|
|
|
|
|
+ String encryptKey = new String(Base64.encodeBase64(
|
|
|
|
|
+ CryptoUtil.RSAEncrypt(aesKeyBytes, CertUtil.getPublicKey(), 2048, 11,
|
|
|
|
|
+ "RSA/ECB/PKCS1Padding")), StandardCharsets.UTF_8);
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, String> reqMap = new HashMap<String, String>();
|
|
|
|
|
+ //整体报文格式
|
|
|
|
|
+ reqMap.put("transCode", "ODQU"); // 交易码
|
|
|
|
|
+ reqMap.put("accessType", "0"); // 接入类型
|
|
|
|
|
+ reqMap.put("merId", sandPayProperties.getMid()); // 合作商户ID 杉德系统分配,唯一标识
|
|
|
|
|
+ reqMap.put("plId", null);
|
|
|
|
|
+ reqMap.put("encryptKey", encryptKey); // 加密后的AES秘钥
|
|
|
|
|
+ reqMap.put("encryptData", encryptData); // 加密后的请求/应答报文
|
|
|
|
|
+ reqMap.put("sign", sign); // 签名
|
|
|
|
|
+ reqMap.put("extend", ""); // 扩展域
|
|
|
|
|
+
|
|
|
|
|
+ String result;
|
|
|
|
|
+ try {
|
|
|
|
|
+ log.info("请求报文:{}", reqMap);
|
|
|
|
|
+ result = HttpClient.doPost("https://caspay.sandpay.com.cn/agent-main/openapi/queryOrder",
|
|
|
|
|
+ reqMap, 300000, 300000);
|
|
|
|
|
+ result = URLDecoder.decode(result, StandardCharsets.UTF_8);
|
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
|
+ log.error(e.getMessage());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ log.info("响应报文:{}", result);
|
|
|
|
|
+ Map<String, String> responseMap = SDKUtil.convertResultStringToMap(result);
|
|
|
|
|
+
|
|
|
|
|
+ String retEncryptKey = responseMap.get("encryptKey");
|
|
|
|
|
+ String retEncryptData = responseMap.get("encryptData");
|
|
|
|
|
+ String retSign = responseMap.get("sign");
|
|
|
|
|
+
|
|
|
|
|
+ log.debug("retEncryptKey:[{}]", retEncryptKey);
|
|
|
|
|
+ log.debug("retEncryptData:[{}]", retEncryptData);
|
|
|
|
|
+ log.debug("retSign:[{}]", retSign);
|
|
|
|
|
+
|
|
|
|
|
+ byte[] decodeBase64KeyBytes = Base64.decodeBase64(retEncryptKey
|
|
|
|
|
+ .getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+
|
|
|
|
|
+ byte[] merchantAESKeyBytes = CryptoUtil.RSADecrypt(
|
|
|
|
|
+ decodeBase64KeyBytes, CertUtil.getPrivateKey(), 2048, 11,
|
|
|
|
|
+ "RSA/ECB/PKCS1Padding");
|
|
|
|
|
+
|
|
|
|
|
+ byte[] decodeBase64DataBytes = Base64.decodeBase64(retEncryptData.getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+
|
|
|
|
|
+ byte[] respDataBytes = CryptoUtil.AESDecrypt(decodeBase64DataBytes,
|
|
|
|
|
+ merchantAESKeyBytes, "AES", "AES/ECB/PKCS5Padding", null);
|
|
|
|
|
+
|
|
|
|
|
+ String respData = new String(respDataBytes, StandardCharsets.UTF_8);
|
|
|
|
|
+ log.info("retData:[" + respData + "]");
|
|
|
|
|
+ System.out.println("响应data数据:" + respData);
|
|
|
|
|
+
|
|
|
|
|
+ byte[] signBytes = Base64.decodeBase64(retSign.getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+
|
|
|
|
|
+ boolean isValid = CryptoUtil.verifyDigitalSign(respDataBytes, signBytes,
|
|
|
|
|
+ CertUtil.getPublicKey(), "SHA1WithRSA");
|
|
|
|
|
+
|
|
|
|
|
+ if (!isValid) {
|
|
|
|
|
+ log.error("verify sign fail.");
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("verify sign success");
|
|
|
|
|
+ System.out.println("verify sign success");
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject respJson = JSONObject.parseObject(respData);
|
|
|
|
|
+ return respJson;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+
|
|
|
|
|
+ log.error(e.getMessage());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|