Merge branch 'dev/dev_xp' into dev/dev
This commit is contained in:
@@ -113,4 +113,10 @@ public class PaymentTask {
|
||||
}
|
||||
}
|
||||
|
||||
// @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
|
||||
public void calcCouponsCommission(){
|
||||
log.info("优惠券佣金计算定时器");
|
||||
affiliateService.calcCouponsCommission();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,8 +4,13 @@ import com.ai.da.common.response.Response;
|
||||
import com.ai.da.common.utils.DateUtil;
|
||||
import com.ai.da.common.utils.RedisUtil;
|
||||
import com.ai.da.common.utils.SendEmailUtil;
|
||||
import com.ai.da.mapper.primary.entity.ProductCoupons;
|
||||
import com.ai.da.model.dto.CreateCouponDTO;
|
||||
import com.ai.da.model.dto.ProductPurchaseDTO;
|
||||
import com.ai.da.model.dto.QueryCouponsPageDTO;
|
||||
import com.ai.da.model.vo.CheckCouponsVO;
|
||||
import com.ai.da.service.StripeService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.paypal.http.HttpResponse;
|
||||
import com.paypal.payments.Refund;
|
||||
import com.stripe.exception.StripeException;
|
||||
@@ -99,6 +104,36 @@ public class StripeController {
|
||||
stripeService.cancelSubscription(subscriptionId, reason);
|
||||
return Response.success("success");
|
||||
}
|
||||
|
||||
@ApiOperation("创建推广码")
|
||||
@PostMapping("/createCoupon")
|
||||
public Response<String> createCoupon(@Valid @RequestBody CreateCouponDTO createCouponDTO){
|
||||
return Response.success(stripeService.createCoupon(createCouponDTO));
|
||||
}
|
||||
|
||||
@ApiOperation("检查推广码")
|
||||
@GetMapping("/checkCoupon")
|
||||
public Response<CheckCouponsVO> checkCoupon(@RequestParam String promotionCode, @RequestParam Long price){
|
||||
return Response.success(stripeService.checkProductCoupon(promotionCode, price));
|
||||
}
|
||||
|
||||
@ApiOperation("获取所有推广码")
|
||||
@PostMapping("/getAllCoupons")
|
||||
public Response<IPage<ProductCoupons>> getAllCoupons(@RequestBody QueryCouponsPageDTO queryCouponsPageDTO){
|
||||
return Response.success(stripeService.getAllCoupons(queryCouponsPageDTO));
|
||||
}
|
||||
|
||||
@ApiOperation("检索优惠券")
|
||||
@GetMapping("/retrieveCoupon")
|
||||
public Response<String> retrieveCoupon(@RequestParam String couponId){
|
||||
return Response.success(stripeService.retrieveCoupon(couponId));
|
||||
}
|
||||
|
||||
@ApiOperation("检索推广码")
|
||||
@GetMapping("/retrievePromotionCode")
|
||||
public Response<String> retrievePromotionCode(@RequestParam String retrievePromotionCode){
|
||||
return Response.success(stripeService.retrievePromotionCode(retrievePromotionCode));
|
||||
}
|
||||
/*@ApiOperation("临时 取消订阅")
|
||||
@GetMapping("/cancelSubscriptionTemp")
|
||||
public Response<String> cancelSubscriptionTemp(@RequestParam String subscriptionId) {
|
||||
|
||||
@@ -29,4 +29,6 @@ public interface PaymentInfoMapper extends BaseMapper<PaymentInfo> {
|
||||
List<Map<String, String>> getCountries();
|
||||
|
||||
int insertIgnore(@Param("paymentInfo")PaymentInfo paymentInfo);
|
||||
|
||||
List<PaymentInfo> selectPaidPaymentsByAccountAndPromotion(Long accountId, String promotionCode);
|
||||
}
|
||||
|
||||
@@ -47,4 +47,6 @@ public class PaymentInfo extends BaseEntity{
|
||||
private String country;
|
||||
|
||||
private String city;
|
||||
|
||||
private String promotionCode;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public class ProductCoupons extends BaseEntity{
|
||||
// 对应的推广码
|
||||
private String promotionCode;
|
||||
// 最大兑换次数
|
||||
private Integer maxRedemptions;
|
||||
private Long maxRedemptions;
|
||||
// 优惠券的折扣
|
||||
private float percentOff;
|
||||
// 佣金比例
|
||||
@@ -38,11 +38,12 @@ public class ProductCoupons extends BaseEntity{
|
||||
public ProductCoupons() {
|
||||
}
|
||||
|
||||
public ProductCoupons(String couponId, Long redeemBy, String promotionCodeId, String promotionCode, float percentOff, float commissionRate) {
|
||||
public ProductCoupons(String couponId, Long redeemBy, String promotionCodeId, String promotionCode, Long maxRedemptions, float percentOff, float commissionRate) {
|
||||
this.couponId = couponId;
|
||||
this.redeemBy = redeemBy;
|
||||
this.promotionCodeId = promotionCodeId;
|
||||
this.promotionCode = promotionCode;
|
||||
this.maxRedemptions = maxRedemptions;
|
||||
this.percentOff = percentOff;
|
||||
this.commissionRate = commissionRate;
|
||||
}
|
||||
|
||||
20
src/main/java/com/ai/da/model/dto/CreateCouponDTO.java
Normal file
20
src/main/java/com/ai/da/model/dto/CreateCouponDTO.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.ai.da.model.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Data
|
||||
public class CreateCouponDTO {
|
||||
@ApiModelProperty("折扣率")
|
||||
@NotNull(message = "Please set the percentOff")
|
||||
private Float percentOff;
|
||||
@ApiModelProperty("佣金比例")
|
||||
@NotNull(message = "Please set the commissionRate.")
|
||||
private Float commissionRate;
|
||||
@ApiModelProperty("推广码到期时间 秒级时间戳")
|
||||
private Long timestamp;
|
||||
@ApiModelProperty("推广码最大使用次数")
|
||||
private Long maxRedemptions;
|
||||
}
|
||||
@@ -30,4 +30,7 @@ public class ProductPurchaseDTO {
|
||||
|
||||
@ApiModelProperty("使用Alipay-HK时需要选择 ALIPAYHK || ALIPAYCN")
|
||||
private String wallet;
|
||||
|
||||
@ApiModelProperty("优惠码")
|
||||
private String promotionCode;
|
||||
}
|
||||
|
||||
18
src/main/java/com/ai/da/model/vo/CheckCouponsVO.java
Normal file
18
src/main/java/com/ai/da/model/vo/CheckCouponsVO.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.ai.da.model.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class CheckCouponsVO {
|
||||
|
||||
@ApiModelProperty("expired || invalid || valid")
|
||||
private String status;
|
||||
|
||||
private String message;
|
||||
|
||||
private Float discountedPrice;
|
||||
}
|
||||
@@ -29,4 +29,6 @@ public interface AffiliateService extends IService<Affiliate> {
|
||||
Affiliate getByAccountId(Long accountId);
|
||||
|
||||
void commissionCalculation(Integer year, Integer month);
|
||||
|
||||
void calcCouponsCommission();
|
||||
}
|
||||
|
||||
@@ -32,4 +32,6 @@ public interface PaymentInfoService extends IService<PaymentInfo> {
|
||||
void updatePaymentStatusById(Long id, String status, String content);
|
||||
|
||||
PageBaseResponse<OrderListVO> getPaymentInfo(QueryPageByTimeDTO queryPageByTimeDTO);
|
||||
|
||||
List<PaymentInfo> getPaymentInfoByPromCode(Long accountId, String promCode);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package com.ai.da.service;
|
||||
|
||||
import com.ai.da.mapper.primary.entity.ProductCoupons;
|
||||
import com.ai.da.mapper.primary.entity.SubscriptionInfo;
|
||||
import com.ai.da.model.dto.CreateCouponDTO;
|
||||
import com.ai.da.model.dto.ProductPurchaseDTO;
|
||||
import com.ai.da.model.dto.QueryCouponsPageDTO;
|
||||
import com.ai.da.model.vo.CheckCouponsVO;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.stripe.exception.StripeException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -51,4 +56,16 @@ public interface StripeService {
|
||||
// Map getIp(HttpServletRequest request);
|
||||
|
||||
String getStackTrace(Exception e, int maxLines);
|
||||
|
||||
String createCoupon(CreateCouponDTO createCouponDTO);
|
||||
|
||||
CheckCouponsVO checkProductCoupon(String promotionCode, Long price);
|
||||
|
||||
ProductCoupons getProductCoupon(String promotionCode, String promotionCodeId);
|
||||
|
||||
String retrieveCoupon(String couponId);
|
||||
|
||||
String retrievePromotionCode(String promotionCode);
|
||||
|
||||
IPage<ProductCoupons> getAllCoupons(QueryCouponsPageDTO queryCouponsPageDTO);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.ai.da.common.utils.RedisUtil;
|
||||
import com.ai.da.common.utils.SendEmailUtil;
|
||||
import com.ai.da.mapper.primary.AffiliateIncomeMapper;
|
||||
import com.ai.da.mapper.primary.AffiliateMapper;
|
||||
import com.ai.da.mapper.primary.ProductCouponsMapper;
|
||||
import com.ai.da.mapper.primary.SubscriptionInfoMapper;
|
||||
import com.ai.da.mapper.primary.entity.*;
|
||||
import com.ai.da.model.dto.AffiliateEmailParamsDTO;
|
||||
@@ -17,10 +18,7 @@ import com.ai.da.model.dto.AffiliateQueryDTO;
|
||||
import com.ai.da.model.vo.AffiliateInvitationDetailsVO;
|
||||
import com.ai.da.model.vo.AffiliateVO;
|
||||
import com.ai.da.model.vo.AuthPrincipalVo;
|
||||
import com.ai.da.service.AccountService;
|
||||
import com.ai.da.service.AffiliateService;
|
||||
import com.ai.da.service.OrderInfoService;
|
||||
import com.ai.da.service.PaymentInfoService;
|
||||
import com.ai.da.service.*;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
@@ -34,9 +32,7 @@ import org.springframework.util.CollectionUtils;
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Service
|
||||
@@ -45,19 +41,18 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
|
||||
|
||||
@Resource
|
||||
private OrderInfoService orderInfoService;
|
||||
|
||||
@Resource
|
||||
private AccountService accountService;
|
||||
|
||||
@Resource
|
||||
private PaymentInfoService paymentInfoService;
|
||||
|
||||
@Resource
|
||||
private SubscriptionInfoMapper subscriptionInfoMapper;
|
||||
|
||||
@Resource
|
||||
private AffiliateIncomeMapper affiliateIncomeMapper;
|
||||
|
||||
@Resource
|
||||
private StripeService stripeService;
|
||||
@Resource
|
||||
private ProductCouponsMapper productCouponsMapper;
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@@ -328,5 +323,47 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
|
||||
return baseMapper.selectOne(queryWrapper);
|
||||
}
|
||||
|
||||
public void calcCouponsCommission(){
|
||||
// id存redis
|
||||
String lastTime = redisUtil.getFromString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME);
|
||||
String currentTime = LocalDateTime.now().toString();
|
||||
// 1、查上次更新之后有无使用了优惠券的新订单
|
||||
QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
|
||||
if (!StringUtil.isNullOrEmpty(lastTime)){
|
||||
queryWrapper.gt("create_time", lastTime)
|
||||
.lt("create_time", currentTime)
|
||||
.isNotNull("promotion_code");
|
||||
}
|
||||
List<PaymentInfo> paymentInfos = paymentInfoService.getBaseMapper().selectList(queryWrapper);
|
||||
|
||||
// key:推广码, value:用户支付的金额
|
||||
HashMap<String, Float> codeAmount = new HashMap<>();
|
||||
if (!paymentInfos.isEmpty()){
|
||||
for (PaymentInfo paymentInfo : paymentInfos){
|
||||
String promotionCode = paymentInfo.getPromotionCode();
|
||||
Float sum = codeAmount.get(promotionCode);
|
||||
if (sum == null || sum == 0.0f){
|
||||
codeAmount.put(promotionCode, paymentInfo.getPayerTotal());
|
||||
}else {
|
||||
codeAmount.put(promotionCode, sum + paymentInfo.getPayerTotal());
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, Float> entry : codeAmount.entrySet()){
|
||||
String promotionCode = entry.getKey();
|
||||
ProductCoupons productCoupons = stripeService.getProductCoupon(promotionCode, null);
|
||||
if (!Objects.isNull(productCoupons)){
|
||||
// 2、计算支付金额的总和,更新totalEarnings,commission,unpaidCommission
|
||||
float sum = productCoupons.getTotalEarnings() + entry.getValue();
|
||||
productCoupons.setTotalEarnings(sum);
|
||||
float commission = sum * productCoupons.getCommissionRate() / 100;
|
||||
productCoupons.setCommission(commission);
|
||||
productCoupons.setUnpaidCommission(commission - productCoupons.getPaidCommission());
|
||||
productCouponsMapper.updateById(productCoupons);
|
||||
}
|
||||
}
|
||||
}
|
||||
redisUtil.addToString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME, currentTime);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.ai.da.common.response.PageBaseResponse;
|
||||
import com.ai.da.mapper.primary.PaymentInfoMapper;
|
||||
import com.ai.da.mapper.primary.entity.OrderInfo;
|
||||
import com.ai.da.mapper.primary.entity.PaymentInfo;
|
||||
import com.ai.da.mapper.primary.entity.ProductCoupons;
|
||||
import com.ai.da.model.dto.AlipayHKCallbackDTO;
|
||||
import com.ai.da.model.dto.QueryPageByTimeDTO;
|
||||
import com.ai.da.model.vo.OrderListVO;
|
||||
@@ -232,20 +233,27 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
|
||||
qw.eq("transaction_id", invoiceId);
|
||||
PaymentInfo paymentInfo = baseMapper.selectOne(qw);
|
||||
String status = invoice.getStatus();
|
||||
// 判断是否有优惠码
|
||||
String promotionCode = null;
|
||||
if (Objects.nonNull(invoice.getDiscount()) && !StringUtil.isNullOrEmpty(invoice.getDiscount().getPromotionCode())){
|
||||
ProductCoupons productCoupon = stripeService.getProductCoupon(null, invoice.getDiscount().getPromotionCode());
|
||||
promotionCode = productCoupon.getPromotionCode();
|
||||
}
|
||||
// 判断当前支付是否已经被记录,确保同一个支付不会被重复记录
|
||||
if (Objects.isNull(paymentInfo)){
|
||||
String orderNo;
|
||||
try {
|
||||
String chargeId = invoice.getCharge();
|
||||
orderNo = Charge.retrieve(chargeId).getDescription().replace("AiDA - ", "");
|
||||
// if (invoice.getBillingReason().equals("manual")){
|
||||
// // 手动创建的发票,针对one-time支付
|
||||
//// orderNo = invoice.getLines().getData().get(0).getPrice().getMetadata().get("orderId");
|
||||
// }else {
|
||||
// String subscriptionId = invoice.getSubscription();
|
||||
// // 从subscription中获取orderNo
|
||||
// orderNo = Subscription.retrieve(subscriptionId).getDescription().replace("AiDA - ", "");
|
||||
// }
|
||||
if (invoice.getBillingReason().equals("manual")){
|
||||
// 手动创建的发票,针对one-time支付
|
||||
// orderNo = invoice.getLines().getData().get(0).getPrice().getMetadata().get("orderId");
|
||||
// 当支付失败时,chargeId为空
|
||||
String chargeId = invoice.getCharge();
|
||||
orderNo = Charge.retrieve(chargeId).getDescription().replace("AiDA - ", "");
|
||||
}else {
|
||||
String subscriptionId = invoice.getSubscription();
|
||||
// 从subscription中获取orderNo
|
||||
orderNo = Subscription.retrieve(subscriptionId).getDescription().replace("AiDA - ", "");
|
||||
}
|
||||
} catch (StripeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -281,6 +289,7 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
|
||||
paymentInfo.setPaymentMethod(paymentMethod.get("paymentMethod"));
|
||||
paymentInfo.setLast4(paymentMethod.get("last4"));
|
||||
paymentInfo.setHostedInvoiceUrl(invoice.getHostedInvoiceUrl());
|
||||
paymentInfo.setPromotionCode(promotionCode);
|
||||
paymentInfo.setCreateTime(LocalDateTime.now());
|
||||
if (!Objects.isNull(orderByOrderNo)){
|
||||
paymentInfo.setCountry(orderByOrderNo.getCountry());
|
||||
@@ -291,6 +300,7 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
|
||||
log.info("Payment Info insert affect rows:{}", row);
|
||||
}else {
|
||||
paymentInfo.setTradeState(status);
|
||||
paymentInfo.setPromotionCode(promotionCode);
|
||||
paymentInfo.setUpdateTime(LocalDateTime.now());
|
||||
baseMapper.updateById(paymentInfo);
|
||||
}
|
||||
@@ -421,4 +431,8 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
|
||||
return PageBaseResponse.success(orderListVOIPage);
|
||||
}
|
||||
}
|
||||
|
||||
public List<PaymentInfo> getPaymentInfoByPromCode(Long accountId, String promCode){
|
||||
return baseMapper.selectPaidPaymentsByAccountAndPromotion(accountId, promCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,20 @@ import com.ai.da.common.utils.DateUtil;
|
||||
import com.ai.da.common.utils.SendEmailUtil;
|
||||
import com.ai.da.mapper.primary.AccountMapper;
|
||||
import com.ai.da.mapper.primary.PaymentInfoMapper;
|
||||
import com.ai.da.mapper.primary.ProductCouponsMapper;
|
||||
import com.ai.da.mapper.primary.SubscriptionInfoMapper;
|
||||
import com.ai.da.mapper.primary.entity.*;
|
||||
import com.ai.da.model.dto.CreateCouponDTO;
|
||||
import com.ai.da.model.dto.ProductPurchaseDTO;
|
||||
import com.ai.da.model.dto.QueryCouponsPageDTO;
|
||||
import com.ai.da.model.dto.SubscriptionEmailParamsDTO;
|
||||
import com.ai.da.model.vo.CheckCouponsVO;
|
||||
import com.ai.da.service.*;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.google.gson.Gson;
|
||||
import com.stripe.Stripe;
|
||||
import com.stripe.exception.SignatureVerificationException;
|
||||
@@ -67,6 +73,8 @@ public class StripeServiceImpl implements StripeService {
|
||||
private SubscriptionInfoMapper subscriptionInfoMapper;
|
||||
@Resource
|
||||
private PaymentInfoMapper paymentInfoMapper;
|
||||
@Resource
|
||||
private ProductCouponsMapper productCouponsMapper;
|
||||
|
||||
@Value("${stripe.private-key}")
|
||||
private String privateKey;
|
||||
@@ -85,6 +93,9 @@ public class StripeServiceImpl implements StripeService {
|
||||
public String pay(ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request) {
|
||||
Stripe.apiKey = privateKey;
|
||||
|
||||
//创建支付信息得到url
|
||||
// 一次性支付和周期扣款,需要区分mode: payment || subscription || setup
|
||||
SessionCreateParams.Builder sessionBuilder = new SessionCreateParams.Builder();
|
||||
ProductEnum productEnum;
|
||||
switch (productPurchaseDTO.getProductName()){
|
||||
case "CreditsPurchase":
|
||||
@@ -105,6 +116,8 @@ public class StripeServiceImpl implements StripeService {
|
||||
default:
|
||||
throw new BusinessException("unknown subscription type");
|
||||
}
|
||||
// 只有订阅时才允许使用推广码优惠
|
||||
// sessionBuilder.setAllowPromotionCodes(true);
|
||||
break;
|
||||
default:
|
||||
throw new BusinessException("unknown product type");
|
||||
@@ -131,12 +144,11 @@ public class StripeServiceImpl implements StripeService {
|
||||
String priceId = getPrice(productEnum.getPrice(), productId, payType, productPurchaseDTO.getSubscribeType());
|
||||
// 获取或创建customer
|
||||
String customerId = getCustomer(account.getUserName(), account.getUserEmail());
|
||||
log.info("customerId:{}", customerId);
|
||||
// 获取自定义订单号
|
||||
String orderId = orderInfo.getOrderNo();
|
||||
|
||||
//创建支付信息得到url
|
||||
// 一次性支付和周期扣款,需要区分mode: payment || subscription || setup
|
||||
SessionCreateParams.Builder sessionBuilder = new SessionCreateParams.Builder();
|
||||
|
||||
if (payType.equals("recurring")){
|
||||
sessionBuilder.setMode(SessionCreateParams.Mode.SUBSCRIPTION);
|
||||
sessionBuilder.setSubscriptionData(SessionCreateParams.SubscriptionData.builder().setDescription("AiDA - " + orderId).build());
|
||||
@@ -148,8 +160,6 @@ public class StripeServiceImpl implements StripeService {
|
||||
}
|
||||
|
||||
sessionBuilder.setPaymentMethodConfiguration(paymentMethodConfigurationId);
|
||||
// sessionBuilder.addPaymentMethodType(SessionCreateParams.PaymentMethodType.ALIPAY);
|
||||
// sessionBuilder.addPaymentMethodType(SessionCreateParams.PaymentMethodType.CARD);
|
||||
sessionBuilder.setCustomer(customerId);
|
||||
sessionBuilder.setSuccessUrl(productPurchaseDTO.getReturnUrl());//可自定义成功页面
|
||||
sessionBuilder.setLocale(account.getLanguage().equals("CHINESE_SIMPLIFIED") ? SessionCreateParams.Locale.ZH : SessionCreateParams.Locale.EN);
|
||||
@@ -160,6 +170,16 @@ public class StripeServiceImpl implements StripeService {
|
||||
.build());
|
||||
sessionBuilder.putMetadata("orderId", orderId); //通过订单号关联用于检索支付信息(可选)
|
||||
|
||||
// 添加优惠券
|
||||
String promotionCode = productPurchaseDTO.getPromotionCode();
|
||||
if (!StringUtil.isNullOrEmpty(promotionCode)){
|
||||
ProductCoupons productCoupon = checkProductCoupon(promotionCode);;
|
||||
if (productCoupon != null){
|
||||
sessionBuilder.addDiscount(SessionCreateParams.Discount.builder()
|
||||
.setPromotionCode(productCoupon.getPromotionCodeId()).build());
|
||||
}
|
||||
}
|
||||
|
||||
Session session = Session.create(sessionBuilder.build());
|
||||
List<String> paymentMethodTypes = session.getPaymentMethodTypes();
|
||||
log.info("paymentMethodTypes: {}", paymentMethodTypes);
|
||||
@@ -171,6 +191,8 @@ public class StripeServiceImpl implements StripeService {
|
||||
// 更新order信息
|
||||
orderInfoService.updateOrderNoById(orderInfo.getId(), orderId);
|
||||
return session.getUrl();
|
||||
} catch (BusinessException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("创建支付会话出现异常:", e);
|
||||
}
|
||||
@@ -1346,4 +1368,192 @@ public class StripeServiceImpl implements StripeService {
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 优惠券实施:
|
||||
* 1、由管理员创建优惠券,手动设置该优惠券对应的折扣
|
||||
* 2、用户在订阅时,手动输入优惠码
|
||||
* 3、使用stripe的字段 redeem_by 管理优惠券的有效期
|
||||
* 4、promotion code与 coupons需要绑定
|
||||
* 5、用户只能使用一次优惠券
|
||||
*
|
||||
* 问题:对于一次付款和订阅,优惠码是否可以混用
|
||||
*/
|
||||
public String createCoupon(CreateCouponDTO createCouponDTO){
|
||||
Stripe.apiKey = privateKey;
|
||||
CouponCreateParams.Builder couponParams = CouponCreateParams.builder()
|
||||
// 任何客户只能用一次这个优惠券
|
||||
.setDuration(CouponCreateParams.Duration.ONCE)
|
||||
.setPercentOff(BigDecimal.valueOf(createCouponDTO.getPercentOff()));
|
||||
if (Objects.nonNull(createCouponDTO.getTimestamp())){
|
||||
couponParams.setRedeemBy(createCouponDTO.getTimestamp());
|
||||
}
|
||||
try {
|
||||
// 1、创建优惠券
|
||||
Coupon coupon = Coupon.create(couponParams.build());
|
||||
// 2、创建一个推广码
|
||||
PromotionCode promotionCode = createPromotionCode(coupon.getId(), createCouponDTO.getMaxRedemptions());
|
||||
// 3、落库
|
||||
ProductCoupons productCoupons = new ProductCoupons(coupon.getId(), createCouponDTO.getTimestamp(), promotionCode.getId(),
|
||||
promotionCode.getCode(), createCouponDTO.getMaxRedemptions(), createCouponDTO.getPercentOff(), createCouponDTO.getCommissionRate());
|
||||
productCoupons.setCreateTime(LocalDateTime.now());
|
||||
productCouponsMapper.insert(productCoupons);
|
||||
|
||||
return promotionCode.getCode();
|
||||
} catch (StripeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public PromotionCode createPromotionCode(String couponId, Long maxRedemption){
|
||||
Stripe.apiKey = privateKey;
|
||||
PromotionCodeCreateParams.Builder promotionCodeParams = PromotionCodeCreateParams.builder()
|
||||
.setCoupon(couponId)
|
||||
.setRestrictions(PromotionCodeCreateParams.Restrictions.builder().build());
|
||||
if (Objects.nonNull(maxRedemption)){
|
||||
promotionCodeParams.setMaxRedemptions(maxRedemption);
|
||||
}
|
||||
|
||||
try {
|
||||
return PromotionCode.create(promotionCodeParams.build());
|
||||
} catch (StripeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public ProductCoupons checkProductCoupon(String promotionCode){
|
||||
Stripe.apiKey = privateKey;
|
||||
Long accountId = UserContext.getUserHolder().getId();
|
||||
// 1、从数据库查找promotionCode对应的promotionCodeId
|
||||
ProductCoupons productCoupons = productCouponsMapper.selectOne(new QueryWrapper<ProductCoupons>().eq("promotion_code", promotionCode));
|
||||
if (Objects.nonNull(productCoupons)){
|
||||
// 2、查绑定的Coupons是否存在以及是否过期
|
||||
long epochSecondNow = Instant.now().getEpochSecond();
|
||||
Long redeemBy = productCoupons.getRedeemBy();
|
||||
if (redeemBy < epochSecondNow){
|
||||
throw new BusinessException("this.promotion.code.has.expired");
|
||||
} else {
|
||||
// 判断该用户是否有成功使用过这个推广码
|
||||
List<PaymentInfo> paymentInfoByPromCode = paymentInfoService.getPaymentInfoByPromCode(accountId, promotionCode);
|
||||
if (!paymentInfoByPromCode.isEmpty()) {
|
||||
// 已使用过推广码,状态无效
|
||||
if (paymentInfoByPromCode.size() > 1) {
|
||||
log.error("用户[{}]多次成功使用优惠码[{}]", accountId, promotionCode);
|
||||
}
|
||||
log.info("用户[{}]已成功使用过优惠码[{}]", accountId, promotionCode);
|
||||
throw new BusinessException("one.time.limit.per.customer");
|
||||
}
|
||||
}
|
||||
}else {
|
||||
throw new BusinessException("this.promotion.code.is.invalid");
|
||||
}
|
||||
return productCoupons;
|
||||
}
|
||||
|
||||
public CheckCouponsVO checkProductCoupon(String promotionCode, Long price){
|
||||
Stripe.apiKey = privateKey;
|
||||
Long accountId = UserContext.getUserHolder().getId();
|
||||
CheckCouponsVO checkCouponsVO = new CheckCouponsVO();
|
||||
// 1、从数据库查找promotionCode对应的promotionCodeId
|
||||
ProductCoupons productCoupons = productCouponsMapper.selectOne(new QueryWrapper<ProductCoupons>().eq("promotion_code", promotionCode));
|
||||
if (Objects.nonNull(productCoupons)){
|
||||
// 2、查绑定的Coupons是否存在以及是否过期
|
||||
long epochSecondNow = Instant.now().getEpochSecond();
|
||||
Long redeemBy = productCoupons.getRedeemBy();
|
||||
if (redeemBy < epochSecondNow){
|
||||
String msg = BusinessException.getMessageFromResource("this.promotion.code.has.expired");
|
||||
checkCouponsVO.setMessage(msg);
|
||||
checkCouponsVO.setStatus("expired");
|
||||
}else {
|
||||
// 判断该用户是否有成功使用过这个推广码
|
||||
List<PaymentInfo> paymentInfoByPromCode = paymentInfoService.getPaymentInfoByPromCode(accountId, promotionCode);
|
||||
if (paymentInfoByPromCode.isEmpty()) {
|
||||
// 未使用过推广码,状态有效
|
||||
checkCouponsVO.setStatus("valid");
|
||||
checkCouponsVO.setDiscountedPrice(price * (1 - productCoupons.getPercentOff() / 100));
|
||||
} else {
|
||||
// 已使用过推广码,状态无效
|
||||
if (paymentInfoByPromCode.size() > 1) {
|
||||
log.error("用户[{}]多次成功使用优惠码[{}]", accountId, promotionCode);
|
||||
}
|
||||
log.info("用户[{}]已成功使用过优惠码[{}]", accountId, promotionCode);
|
||||
String msg = BusinessException.getMessageFromResource("one.time.limit.per.customer");
|
||||
checkCouponsVO.setMessage(msg);
|
||||
checkCouponsVO.setStatus("invalid");
|
||||
}
|
||||
}
|
||||
}else {
|
||||
String msg = BusinessException.getMessageFromResource("this.promotion.code.is.invalid");
|
||||
checkCouponsVO.setMessage(msg);
|
||||
checkCouponsVO.setStatus("invalid");
|
||||
}
|
||||
return checkCouponsVO;
|
||||
}
|
||||
|
||||
public ProductCoupons getProductCoupon(String promotionCode, String promotionCodeId){
|
||||
Stripe.apiKey = privateKey;
|
||||
QueryWrapper<ProductCoupons> qw = new QueryWrapper<>();
|
||||
// 1、从数据库查找promotionCode对应的promotionCodeId
|
||||
if (!StringUtil.isNullOrEmpty(promotionCode)){
|
||||
qw.eq("promotion_code", promotionCode);
|
||||
}
|
||||
if (!StringUtil.isNullOrEmpty(promotionCodeId)){
|
||||
qw.eq("promotion_code_id", promotionCodeId);
|
||||
}
|
||||
return productCouponsMapper.selectOne(qw);
|
||||
}
|
||||
|
||||
public String retrieveCoupon(String couponId){
|
||||
Stripe.apiKey = privateKey;
|
||||
try {
|
||||
Coupon coupon = Coupon.retrieve(couponId);
|
||||
log.info("retrieve的coupon: {}", coupon);
|
||||
return JSON.toJSONString(coupon);
|
||||
} catch (StripeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String retrievePromotionCode(String promotionCode){
|
||||
Stripe.apiKey = privateKey;
|
||||
try {
|
||||
ProductCoupons productCoupon = getProductCoupon(promotionCode, null);
|
||||
PromotionCode retrieve = PromotionCode.retrieve(productCoupon.getPromotionCodeId());
|
||||
log.info("retrieve的promotionCode: {}", retrieve);
|
||||
return JSON.toJSONString(retrieve);
|
||||
} catch (StripeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public IPage<ProductCoupons> getAllCoupons(QueryCouponsPageDTO queryCouponsPageDTO){
|
||||
// 分页 + 按条件查询
|
||||
QueryWrapper<ProductCoupons> queryWrapper = new QueryWrapper<>();
|
||||
if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getOrderById()) && queryCouponsPageDTO.getOrderById().equals("DESC")){
|
||||
queryWrapper.orderByDesc("id");
|
||||
}
|
||||
if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getPromotionCode())){
|
||||
queryWrapper.like("promotion_code", queryCouponsPageDTO.getPromotionCode());
|
||||
}
|
||||
if (Objects.nonNull(queryCouponsPageDTO.getIsExpired())){
|
||||
long epochSecond = Instant.now().getEpochSecond();
|
||||
if (queryCouponsPageDTO.getIsExpired()){
|
||||
queryWrapper.lt("redeem_by", epochSecond);
|
||||
}else {
|
||||
queryWrapper.gt("redeem_by", epochSecond);
|
||||
}
|
||||
}
|
||||
if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getCooperator())){
|
||||
queryWrapper.like("cooperator", queryCouponsPageDTO.getCooperator());
|
||||
}
|
||||
if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getStartTime())){
|
||||
queryWrapper.gt("create_time", queryCouponsPageDTO.getStartTime());
|
||||
}
|
||||
if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getEndTime())){
|
||||
queryWrapper.lt("create_time", queryCouponsPageDTO.getEndTime());
|
||||
}
|
||||
|
||||
return productCouponsMapper.selectPage(new Page<>(queryCouponsPageDTO.getPage(), queryCouponsPageDTO.getSize()), queryWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,11 +167,21 @@
|
||||
INSERT IGNORE INTO
|
||||
t_payment_info (order_no, transaction_id, payment_type, trade_state, payer_total,
|
||||
type, content, notified, payment_method, last4, hosted_invoice_url,
|
||||
country, city, ip_address, create_time)
|
||||
country, city, ip_address, promotion_code, create_time)
|
||||
VALUES (#{paymentInfo.orderNo}, #{paymentInfo.transactionId}, #{paymentInfo.paymentType},
|
||||
#{paymentInfo.tradeState}, #{paymentInfo.payerTotal},#{paymentInfo.type}, #{paymentInfo.content},
|
||||
#{paymentInfo.notified},#{paymentInfo.paymentMethod}, #{paymentInfo.last4},#{paymentInfo.hostedInvoiceUrl},
|
||||
#{paymentInfo.country},#{paymentInfo.city},#{paymentInfo.ipAddress},#{paymentInfo.createTime});
|
||||
#{paymentInfo.country},#{paymentInfo.city},#{paymentInfo.ipAddress},#{paymentInfo.promotionCode},#{paymentInfo.createTime});
|
||||
</insert>
|
||||
|
||||
<select id="selectPaidPaymentsByAccountAndPromotion" resultType="com.ai.da.mapper.primary.entity.PaymentInfo">
|
||||
SELECT pi.*
|
||||
FROM t_order_info oi
|
||||
LEFT JOIN t_payment_info pi ON oi.order_no = pi.order_no
|
||||
WHERE oi.account_id = #{accountId}
|
||||
AND pi.promotion_code = #{promotionCode}
|
||||
AND pi.trade_state = 'paid'
|
||||
</select>
|
||||
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -153,6 +153,9 @@ generate.result.below.standard=The quality of the generated images currently fal
|
||||
partial.design.failed=Partial design failed, Please try again later.
|
||||
email.count.limit=Rate limit reached. Retry in 1 hour.
|
||||
model.path.cannot.be.empty=Model path cannot be empty.
|
||||
this.promotion.code.has.expired=This promotion code has expired.
|
||||
this.promotion.code.is.invalid=This promotion code is invalid.
|
||||
one.time.limit.per.customer=This code has already been redeemed. Promo codes are limited to one-time use per customer.
|
||||
|
||||
# 可能会报异常
|
||||
# Informative:
|
||||
|
||||
@@ -149,6 +149,9 @@ generate.result.below.standard=当前生成的图像质量低于标准。请考
|
||||
partial.design.failed=局部设计失败。请稍后重试。
|
||||
email.count.limit=您的账号触发邮件发送频率限制,请一小时后重试。
|
||||
model.path.cannot.be.empty=模特路径不能为空
|
||||
this.promotion.code.has.expired=该促销码已过期。
|
||||
this.promotion.code.is.invalid=该促销码无效。
|
||||
one.time.limit.per.customer=该码已兑换。每个促销码每位用户仅限使用一次。
|
||||
|
||||
# 可能会报异常
|
||||
# Informative:
|
||||
|
||||
Reference in New Issue
Block a user