zwhui 1 gadu atpakaļ
vecāks
revīzija
348e949134

+ 95 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/V5payDto.java

@@ -0,0 +1,95 @@
+package com.cyksj.model.dto;
+
+import lombok.Data;
+
+/**
+ * @author zwhui
+ * @date 2024/7/26 15:44
+ */
+@Data
+public class V5payDto {
+    /**
+     * 请求时的商户订单号
+     */
+    private String orderNo;
+
+    /**
+     * 详情请查看附录-代收产品编码
+     */
+    private String productType;
+
+    /**
+     * 代收的金额
+     */
+    private String amount;
+
+    /**
+     * 0.代收、1.代扣、2.销售税订单、3.换汇订单
+     */
+    private String businessType;
+
+    /**
+     * 代收时收取的手续费
+     */
+    private String fee;
+
+    /**
+     * 代收请求时的透传参数
+     */
+    private String merchantParam;
+
+    /**
+     * 详情请查看附录-代收订单状态
+     */
+    private String status;
+
+    /**
+     * 手续费是否已经含税:1.手续费不含税,2.手续费已含税
+     */
+    private String vatType;
+
+    /**
+     * 增值税
+     */
+    private String vat;
+
+    /**
+     * 商户实际到账的金额 = 代收金额 - 手续费 - 增值税
+     */
+    private String accAmount;
+
+    /**
+     * 如果是通过referenceNo的方式发起交易,则该字段表示该referenceNo,否则值为空
+     */
+    private String referenceNo;
+
+    /**
+     * 币种
+     */
+    private String currency;
+
+    /**
+     * V5Pay的系统订单号
+     */
+    private String transactionId;
+
+    /**
+     * 支付成功时间(UTC)
+     */
+    private String successTime;
+
+    /**
+     * 通过收款码(referenceNo)触发的通知有该字段
+     */
+    private String uniqueId;
+
+    /**
+     * 如果订单为成功,就为success;如果订单为失败,就为失败原因
+     */
+    private String message;
+
+    /**
+     * 详情请查看签名算法
+     */
+    private String sign;
+}

+ 101 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/V5payPayment.java

@@ -0,0 +1,101 @@
+package com.cyksj.model.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.math.BigDecimal;
+
+/**
+ * @author zwhui
+ * @date 2024/7/26 14:09
+ */
+@Data
+@Accessors(chain = true)
+public class V5payPayment {
+    /**
+     * 商户号: V5pay分配给商户的唯一标识
+     */
+    private String merchantNo;
+
+    /**
+     * 商户应用key
+     */
+    private String appKey;
+
+    /**
+     * 国家编码
+     */
+    private String sysCountryCode;
+
+    /**
+     * 币种
+     */
+    private String currency;
+
+    /**
+     * 订单号: 商户生成的订单号,商户应该保证订单号不重复(数字+英文)
+     */
+    private String orderNo;
+
+    /**
+     * 代收金额: 整数最大10位,小数2位
+     */
+    private BigDecimal amount;
+
+    /**
+     * 手机号
+     */
+    private String mobile;
+
+    /**
+     * 邮箱
+     */
+    private String email;
+
+    /**
+     * 姓名
+     */
+    private String name;
+
+    /**
+     * 签名: 详情请查看 签名算法
+     */
+    private String sign;
+
+    /**
+     * 产品编码: 推荐不传入,使用V5pay收银台,若传入产品编码,则直接跳过收银台选择支付方式页面,
+     * 直接重定向到选择的产品收款页面,详情请查看 附录-代收产品编码
+     */
+    private String productType;
+
+    /**
+     * 交易摘要: 交易的说明信息
+     */
+    private String tradeSummary;
+
+    /**
+     * 回调通知地址: 交易结果回调通知地址,不传入则不通知
+     */
+    private String callbackUrl;
+
+    /**
+     * 支付成功后页面重定向地址: 支付成功后页面重定向地址,不传入则默认返回V5pay的支付成功页面
+     */
+    private String redirectUrl;
+
+    /**
+     * 商户参数: 商户自定义的参数,V5Pay会在回调和交易查询接口中透传给商户,
+     * 只能是字符串(String)类型,如果是json则需要将json转换为字符串(String)类型
+     */
+    private String merchantParam;
+
+    /**
+     * 语言: 英语: en-US, 西班牙语: es-ES, 葡萄牙语: pt-PT, 俄语: ru-RU, 简体中文: zh-CN, 繁體中文: zh-TC
+     */
+    private String language;
+
+    /**
+     * 原始下单币种(换汇前): 如果无需换汇交易,请勿传入,目前仅支持USD
+     */
+    private String originCurrency;
+}

+ 2 - 1
netflix-dao/src/main/java/com/cyksj/model/entity/OrderDon.java

@@ -302,7 +302,8 @@ public class OrderDon extends BaseEntity implements Serializable {
         BALANCE("balance", "余额支付"),
         PAYPAL("paypal","paypal支付"),
         STRIPE("stripe","stripe支付"),
-        CRYPTOMUS("cryptomus","cryptomus支付")
+        CRYPTOMUS("cryptomus","cryptomus支付"),
+        V5_PAY("v5_pay","v5_pay支付")
         ;
         String type;
         String desc;

+ 5 - 0
netflix-service/src/main/java/com/cyksj/service/order/OrderDonService.java

@@ -1,6 +1,7 @@
 package com.cyksj.service.order;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.cyksj.model.dto.V5payDto;
 import com.cyksj.model.entity.OrderDetailView;
 import com.cyksj.model.entity.OrderDon;
 import com.cyksj.model.request.OrderPayRequest;
@@ -32,4 +33,8 @@ public interface OrderDonService extends IService<OrderDon> {
     void zeroOrderConfirm(Long orderId);
 
     void orderNotify(OrderDon orderDon);
+
+    String v5Pay(Long orderId,String successUrl) throws Exception;
+
+    void v5PayNotiry(V5payDto v5payDto) throws Exception;
 }

+ 109 - 7
netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java

@@ -6,8 +6,13 @@ import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.RandomUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestAlgorithm;
+import cn.hutool.crypto.digest.Digester;
 import cn.hutool.extra.servlet.ServletUtil;
 import cn.hutool.http.Header;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.cyksj.common.EnvCommonService;
@@ -16,6 +21,7 @@ import com.cyksj.common.exception.BusinessRuntimeException;
 import com.cyksj.common.i18n.I18nMessageUtil;
 import com.cyksj.common.snowflake.Sequence;
 import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
+import com.cyksj.common.util.Codec;
 import com.cyksj.common.util.Jsons;
 import com.cyksj.common.util.SmsUtil;
 import com.cyksj.common.util.StringUtil;
@@ -27,12 +33,11 @@ import com.cyksj.mapper.manage.coupon.CouponMapper;
 import com.cyksj.mapper.manage.coupon.CouponSkuMapper;
 import com.cyksj.mapper.manage.coupon.CouponUserMapper;
 import com.cyksj.mapper.manage.distribute.*;
-import com.cyksj.model.dto.ChatGPTPlusPurchaseLimitDto;
-import com.cyksj.model.dto.CouponPopularizeDto;
-import com.cyksj.model.dto.OrderDonPostDataDto;
+import com.cyksj.model.dto.*;
 import com.cyksj.model.entity.*;
 import com.cyksj.model.manage.views.GroupsRelationView;
 import com.cyksj.model.manage.views.OrderDonView;
+import com.cyksj.model.request.OrderAttach;
 import com.cyksj.model.request.OrderPayRequest;
 import com.cyksj.model.views.CouponNewUserView;
 import com.cyksj.model.views.CouponUserView;
@@ -59,6 +64,7 @@ import com.ejlchina.searcher.util.MapUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -66,10 +72,8 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.servlet.http.HttpServletRequest;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * @author chan
@@ -1663,4 +1667,102 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 		userPlatDistributeRate.setSecondRate(secondRate);
 		return userPlatDistributeRate;
 	}
+
+	@Value("${v5pay.merchantNo}")
+	private String merchantNo;
+
+	@Value("${v5pay.appKey}")
+	private String appKey;
+
+	@Value("${v5pay.appSecret}")
+	private String appSecret;
+
+	@Value("${v5pay.apiUrl}")
+	private String apiUrl;
+
+	@Value("${v5pay.callbackUrl}")
+	private String callbackUrl;
+
+	@Override
+	public String v5Pay(Long orderId,String successUrl) throws Exception {
+		OrderDon orderDon = this.getById(orderId);
+		Objects.requireNonNull(orderDon, "订单不存在!");
+
+		orderDon.setType(OrderDon.Type.V5_PAY);
+		this.updateById(orderDon);
+
+		OrderAttach attach = new OrderAttach();
+		attach.setO(orderDon.getId());
+		String att = Codec.DoBase64.custom()
+				.setData(Jsons.toJson(attach)).encodeAsText();
+		callbackUrl = callbackUrl + "/"+envCommonService.getPort()+"/"+envCommonService.getApi()+"/applets/order/v5pay/notify";
+		V5payPayment v5payPayment = new V5payPayment().setMerchantNo(merchantNo).setAppKey(appKey).setSysCountryCode("MY").setCurrency("MYR")
+				.setOrderNo(orderDon.getOrderNo()).setAmount(orderDon.getMoney().divide(BigDecimal.valueOf(100),2, RoundingMode.HALF_DOWN))
+				.setMerchantParam(att).setCallbackUrl(callbackUrl).setRedirectUrl(successUrl);
+		v5payPayment.setSign(generateSignature(v5payPayment,appSecret));
+		String result = HttpUtil.post(apiUrl+"/cgi/cashier/v2/payin",Jsons.toJson(v5payPayment));
+		log.info("v5pay result:{}",result);
+		JSONObject jsonObject = JSONUtil.parseObj(result);
+		if (!"1000".equals(jsonObject.getStr("code"))){
+			throw new BusinessRuntimeException(GatewayApiCode.METADATA_SYSTEM_ERROR.getCode(),GatewayApiCode.METADATA_SYSTEM_ERROR.getMsg());
+		}
+		return jsonObject.getStr("checkoutUrl");
+	}
+
+	public static String generateSignature(V5payPayment payment, String secretKey) {
+		Map<String, String> params = new TreeMap<>();
+
+		params.put("merchantNo", payment.getMerchantNo());
+		params.put("appKey", payment.getAppKey());
+		params.put("orderNo", payment.getOrderNo());
+		params.put("amount", payment.getAmount().toPlainString());
+		params.put("merchantParam", payment.getMerchantParam());
+		params.put("email", payment.getEmail());
+		params.put("mobile", payment.getMobile());
+		params.put("name", payment.getName());
+		params.put("callbackUrl", payment.getCallbackUrl());
+		params.put("redirectUrl", payment.getRedirectUrl());
+
+		// 拼接非空参数
+		String plainText = params.entrySet().stream()
+				.filter(entry -> entry.getValue() != null && !entry.getValue().isEmpty())
+				.map(entry -> entry.getKey() + "=" + entry.getValue())
+				.collect(Collectors.joining("&"));
+
+		// 拼接 secretKey
+		String signTemp = plainText + secretKey;
+
+		// 使用 MD5 加密
+		Digester md5 = new Digester(DigestAlgorithm.MD5);
+
+		return md5.digestHex(signTemp);
+	}
+
+	@Override
+	public void v5PayNotiry(V5payDto v5payDto) throws Exception {
+		log.info("v5pay 回调成功 :{}",v5payDto);
+		if (!"success".equals(v5payDto.getMessage())){
+			return;
+		}
+		String attach = Codec.DoBase64.custom().setData(v5payDto.getMerchantParam()).decodeAsText();
+
+		OrderAttach orderV2Attach = Jsons.parseObject(attach, OrderAttach.class);
+		//更新订单支付状态
+		Long orderId = orderV2Attach.getO();
+		OrderDon orderDon = this.getById(orderId);
+		if (orderDon == null){
+			return;
+		}
+		if (orderDon.getStatus().equals(OrderDon.Status.hasPayment)) {
+			log.info("已回调,重复修改状态 id:{}", orderDon.getId());
+			return;
+		}
+		String transactionId = v5payDto.getTransactionId();
+		orderDon.setTransactionId(transactionId);
+		orderDon.setPayTime(new Date());
+		orderDon.setAbroadTransactionFee(new BigDecimal(v5payDto.getFee()));
+		orderNotify(orderDon);
+		log.info("支付成功订单:"+orderId+",支付交易号:"+transactionId);
+		log.info("支付成功订单:"+orderId);
+	}
 }

+ 16 - 0
netflix-web/src/main/java/com/cyksj/web/controller/payment/OrderController.java

@@ -14,6 +14,7 @@ import com.cyksj.enums.GatewayResponse;
 import com.cyksj.mapper.OrderDonMapper;
 import com.cyksj.mapper.UserMapper;
 import com.cyksj.mapper.market.UserBindDetailMapper;
+import com.cyksj.model.dto.V5payDto;
 import com.cyksj.model.entity.*;
 import com.cyksj.model.manage.views.OrderDonSearchView;
 import com.cyksj.model.manage.views.OrderDonView;
@@ -389,6 +390,21 @@ public class OrderController {
         cryptomusService.notify(cryptomusReturnRep);
     }
 
+    /**
+     * v5pay
+     */
+    @PostMapping("/v5pay/{orderId}")
+    public Result<String> v5Pay(@PathVariable Long orderId,String successUrl) throws Exception {
+        String url = orderDonService.v5Pay(orderId,successUrl);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(url);
+    }
 
+    /**
+     * v5pay支付回调
+     */
+    @PostMapping("/v5pay/notify")
+    public void v5payNotify(@RequestBody V5payDto v5payDto) throws Exception {
+        orderDonService.v5PayNotiry(v5payDto);
+    }
 
 }

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
netflix-web/src/main/resources/application-prd.yml


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels