378 lines
20 KiB
Java
378 lines
20 KiB
Java
package com.starry.admin.api;
|
|
|
|
import static org.mockito.ArgumentMatchers.anyString;
|
|
import static org.mockito.ArgumentMatchers.eq;
|
|
import static org.mockito.Mockito.reset;
|
|
import static org.mockito.Mockito.verify;
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
|
|
|
import com.starry.admin.common.apitest.ApiTestDataSeeder;
|
|
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
|
import com.starry.admin.modules.shop.module.constant.CouponUseState;
|
|
import com.starry.admin.modules.shop.module.entity.PlayCouponDetailsEntity;
|
|
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
|
|
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
|
import com.starry.admin.utils.SecurityUtils;
|
|
import com.starry.common.constant.Constants;
|
|
import com.starry.common.context.CustomSecurityContextHolder;
|
|
import com.starry.common.utils.IdUtils;
|
|
import java.math.BigDecimal;
|
|
import java.math.RoundingMode;
|
|
import java.util.Collections;
|
|
import org.assertj.core.api.Assertions;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
import org.springframework.http.MediaType;
|
|
|
|
class WxCustomSpecifiedOrderApiTest extends WxCustomOrderApiTestSupport {
|
|
|
|
@MockBean
|
|
private WxCustomMpService wxCustomMpService;
|
|
|
|
@org.springframework.beans.factory.annotation.Autowired
|
|
private com.starry.admin.modules.order.service.IPlayOrderInfoService orderInfoService;
|
|
|
|
@org.springframework.beans.factory.annotation.Autowired
|
|
private com.starry.admin.modules.weichat.service.WxTokenService clerkWxTokenService;
|
|
|
|
@Test
|
|
// 测试用例:指定单取消后优惠券应恢复为未使用
|
|
void specifiedOrderCancellationReleasesCoupon() throws Exception {
|
|
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
|
String remark = "API specified cancel " + IdUtils.getUuid();
|
|
BigDecimal discount = new BigDecimal("10.00");
|
|
try {
|
|
resetCustomerBalance();
|
|
String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
|
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
|
|
|
|
String couponInfoId = createCouponId("cpn-sc-");
|
|
ensureFixedReductionCoupon(
|
|
couponInfoId,
|
|
OrderConstant.PlaceType.SPECIFIED,
|
|
discount,
|
|
new BigDecimal("60.00"),
|
|
"0");
|
|
String couponDetailId = claimCouponForOrder(couponInfoId, OrderConstant.PlaceType.SPECIFIED, customerToken);
|
|
|
|
String payload = "{" +
|
|
"\"clerkId\":\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"," +
|
|
"\"commodityId\":\"" + ApiTestDataSeeder.DEFAULT_COMMODITY_ID + "\"," +
|
|
"\"commodityQuantity\":1," +
|
|
"\"weiChatCode\":\"apitest-customer-wx\"," +
|
|
"\"couponIds\":[\"" + couponDetailId + "\"]," +
|
|
"\"remark\":\"" + remark + "\"" +
|
|
"}";
|
|
|
|
mockMvc.perform(post("/wx/custom/order/commodity")
|
|
.header(USER_HEADER, DEFAULT_USER)
|
|
.header(TENANT_HEADER, DEFAULT_TENANT)
|
|
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
.content(payload))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.code").value(200));
|
|
|
|
ensureTenantContext();
|
|
PlayOrderInfoEntity order = playOrderInfoService.lambdaQuery()
|
|
.eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
|
|
.eq(PlayOrderInfoEntity::getRemark, remark)
|
|
.orderByDesc(PlayOrderInfoEntity::getCreatedTime)
|
|
.last("limit 1")
|
|
.one();
|
|
Assertions.assertThat(order).isNotNull();
|
|
|
|
PlayCouponDetailsEntity detailBeforeForceCancel = couponDetailsService.getById(couponDetailId);
|
|
Assertions.assertThat(detailBeforeForceCancel).isNotNull();
|
|
Assertions.assertThat(detailBeforeForceCancel.getUseState()).isEqualTo(CouponUseState.USED.getCode());
|
|
Assertions.assertThat(detailBeforeForceCancel.getUseTime()).isNotNull();
|
|
|
|
PlayCouponDetailsEntity detailBeforeCancel = couponDetailsService.getById(couponDetailId);
|
|
Assertions.assertThat(detailBeforeCancel).isNotNull();
|
|
Assertions.assertThat(detailBeforeCancel.getUseState()).isEqualTo(CouponUseState.USED.getCode());
|
|
Assertions.assertThat(detailBeforeCancel.getUseTime()).isNotNull();
|
|
|
|
String cancelPayload = "{" +
|
|
"\"orderId\":\"" + order.getId() + "\"," +
|
|
"\"refundReason\":\"测试取消\"," +
|
|
"\"images\":[]" +
|
|
"}";
|
|
mockMvc.perform(post("/wx/custom/order/cancellation")
|
|
.header(USER_HEADER, DEFAULT_USER)
|
|
.header(TENANT_HEADER, DEFAULT_TENANT)
|
|
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
.content(cancelPayload))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.code").value(200))
|
|
.andExpect(jsonPath("$.data").value("取消成功"));
|
|
|
|
ensureTenantContext();
|
|
PlayOrderInfoEntity cancelled = playOrderInfoService.selectOrderInfoById(order.getId());
|
|
Assertions.assertThat(cancelled.getOrderStatus()).isEqualTo(OrderConstant.OrderStatus.CANCELLED.getCode());
|
|
|
|
PlayCouponDetailsEntity detail = couponDetailsService.getById(couponDetailId);
|
|
Assertions.assertThat(detail).isNotNull();
|
|
Assertions.assertThat(detail.getUseState())
|
|
.as("取消指定单后优惠券应恢复为未使用")
|
|
.isEqualTo(CouponUseState.UNUSED.getCode());
|
|
Assertions.assertThat(detail.getUseTime()).isNull();
|
|
} finally {
|
|
CustomSecurityContextHolder.remove();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
// 测试用例:指定单使用满减券下单后,应按折后金额推送创建通知,
|
|
// 并将预计收益扣除优惠金额,同时把优惠券详情标记为已使用。
|
|
void specifiedOrderWithCouponAdjustsAmountAndRevenue() throws Exception {
|
|
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
|
String remark = "API specified coupon " + IdUtils.getUuid();
|
|
BigDecimal discount = new BigDecimal("15.00");
|
|
try {
|
|
reset(wxCustomMpService);
|
|
resetCustomerBalance();
|
|
|
|
String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
|
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
|
|
|
|
BigDecimal grossAmount = ApiTestDataSeeder.DEFAULT_COMMODITY_PRICE;
|
|
String couponInfoId = createCouponId("cpn-s-");
|
|
ensureFixedReductionCoupon(
|
|
couponInfoId,
|
|
OrderConstant.PlaceType.SPECIFIED,
|
|
discount,
|
|
new BigDecimal("60.00"),
|
|
"0");
|
|
String couponDetailId = claimCouponForOrder(couponInfoId, OrderConstant.PlaceType.SPECIFIED, customerToken);
|
|
|
|
String payload = "{" +
|
|
"\"clerkId\":\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"," +
|
|
"\"commodityId\":\"" + ApiTestDataSeeder.DEFAULT_COMMODITY_ID + "\"," +
|
|
"\"commodityQuantity\":1," +
|
|
"\"weiChatCode\":\"apitest-customer-wx\"," +
|
|
"\"couponIds\":[\"" + couponDetailId + "\"]," +
|
|
"\"remark\":\"" + remark + "\"" +
|
|
"}";
|
|
|
|
mockMvc.perform(post("/wx/custom/order/commodity")
|
|
.header(USER_HEADER, DEFAULT_USER)
|
|
.header(TENANT_HEADER, DEFAULT_TENANT)
|
|
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
.content(payload))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.code").value(200))
|
|
.andExpect(jsonPath("$.data").value("成功"));
|
|
|
|
ensureTenantContext();
|
|
PlayOrderInfoEntity order = playOrderInfoService.lambdaQuery()
|
|
.eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
|
|
.eq(PlayOrderInfoEntity::getRemark, remark)
|
|
.orderByDesc(PlayOrderInfoEntity::getCreatedTime)
|
|
.last("limit 1")
|
|
.one();
|
|
|
|
Assertions.assertThat(order).isNotNull();
|
|
Assertions.assertThat(order.getCouponIds()).contains(couponDetailId);
|
|
|
|
BigDecimal expectedNet = grossAmount.subtract(discount).setScale(2, RoundingMode.HALF_UP);
|
|
Assertions.assertThat(order.getFinalAmount()).isEqualByComparingTo(expectedNet);
|
|
Assertions.assertThat(order.getDiscountAmount()).isEqualByComparingTo(discount);
|
|
|
|
PlayCouponDetailsEntity detailAfterOrder = couponDetailsService.getById(couponDetailId);
|
|
Assertions.assertThat(detailAfterOrder).isNotNull();
|
|
Assertions.assertThat(detailAfterOrder.getUseState()).isEqualTo(CouponUseState.USED.getCode());
|
|
Assertions.assertThat(detailAfterOrder.getUseTime()).isNotNull();
|
|
|
|
verify(wxCustomMpService).sendCreateOrderMessage(
|
|
eq(ApiTestDataSeeder.DEFAULT_TENANT_ID),
|
|
eq(ApiTestDataSeeder.DEFAULT_CLERK_OPEN_ID),
|
|
anyString(),
|
|
eq(expectedNet.toString()),
|
|
eq(order.getCommodityName()),
|
|
eq(order.getId()));
|
|
|
|
int ratio = order.getEstimatedRevenueRatio();
|
|
BigDecimal baseRevenue = grossAmount
|
|
.multiply(BigDecimal.valueOf(ratio))
|
|
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
|
PlayCouponDetailsReturnVo detail = couponDetailsService.selectPlayCouponDetailsById(couponDetailId);
|
|
BigDecimal clerkDiscount = BigDecimal.ZERO;
|
|
if (detail != null && "0".equals(detail.getAttributionDiscounts())) {
|
|
BigDecimal discountAmount = detail.getDiscountAmount() == null ? BigDecimal.ZERO : detail.getDiscountAmount();
|
|
clerkDiscount = discountAmount;
|
|
}
|
|
BigDecimal expectedRevenue = baseRevenue.subtract(clerkDiscount).max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
|
|
Assertions.assertThat(order.getEstimatedRevenue()).isEqualByComparingTo(expectedRevenue);
|
|
|
|
assertCouponUsed(couponDetailId);
|
|
PlayCouponDetailsEntity detailAfterComplete = couponDetailsService.getById(couponDetailId);
|
|
Assertions.assertThat(detailAfterComplete).isNotNull();
|
|
Assertions.assertThat(detailAfterComplete.getUseState()).isEqualTo(CouponUseState.USED.getCode());
|
|
Assertions.assertThat(detailAfterComplete.getUseTime()).isNotNull();
|
|
} finally {
|
|
CustomSecurityContextHolder.remove();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
// 测试用例:指定单在接单后由管理员强制取消时,优惠券应恢复为未使用
|
|
void specifiedOrderForceCancelReleasesCoupon() throws Exception {
|
|
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
|
String remark = "API specified force cancel " + IdUtils.getUuid();
|
|
BigDecimal discount = new BigDecimal("8.00");
|
|
try {
|
|
reset(wxCustomMpService);
|
|
resetCustomerBalance();
|
|
|
|
String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
|
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
|
|
String clerkToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
|
clerkUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CLERK_ID, clerkToken);
|
|
|
|
String couponInfoId = createCouponId("cpn-sf-");
|
|
ensureFixedReductionCoupon(
|
|
couponInfoId,
|
|
OrderConstant.PlaceType.SPECIFIED,
|
|
discount,
|
|
new BigDecimal("60.00"),
|
|
"0");
|
|
String couponDetailId = claimCouponForOrder(couponInfoId, OrderConstant.PlaceType.SPECIFIED, customerToken);
|
|
|
|
String payload = "{" +
|
|
"\"clerkId\":\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"," +
|
|
"\"commodityId\":\"" + ApiTestDataSeeder.DEFAULT_COMMODITY_ID + "\"," +
|
|
"\"commodityQuantity\":1," +
|
|
"\"weiChatCode\":\"apitest-customer-wx\"," +
|
|
"\"couponIds\":[\"" + couponDetailId + "\"]," +
|
|
"\"remark\":\"" + remark + "\"" +
|
|
"}";
|
|
|
|
mockMvc.perform(post("/wx/custom/order/commodity")
|
|
.header(USER_HEADER, DEFAULT_USER)
|
|
.header(TENANT_HEADER, DEFAULT_TENANT)
|
|
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
.content(payload))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.code").value(200));
|
|
|
|
ensureTenantContext();
|
|
PlayOrderInfoEntity order = playOrderInfoService.lambdaQuery()
|
|
.eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
|
|
.eq(PlayOrderInfoEntity::getRemark, remark)
|
|
.orderByDesc(PlayOrderInfoEntity::getCreatedTime)
|
|
.last("limit 1")
|
|
.one();
|
|
Assertions.assertThat(order).isNotNull();
|
|
|
|
mockMvc.perform(get("/wx/clerk/order/accept")
|
|
.param("id", order.getId())
|
|
.header(USER_HEADER, DEFAULT_USER)
|
|
.header(TENANT_HEADER, DEFAULT_TENANT)
|
|
.header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.code").value(200))
|
|
.andExpect(jsonPath("$.data").value("成功"));
|
|
|
|
ensureTenantContext();
|
|
orderInfoService.forceCancelOngoingOrder(
|
|
"2",
|
|
ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID,
|
|
order.getId(),
|
|
order.getFinalAmount(),
|
|
"管理员强制取消",
|
|
java.util.Collections.emptyList());
|
|
|
|
ensureTenantContext();
|
|
PlayOrderInfoEntity cancelled = playOrderInfoService.selectOrderInfoById(order.getId());
|
|
Assertions.assertThat(cancelled.getOrderStatus()).isEqualTo(OrderConstant.OrderStatus.CANCELLED.getCode());
|
|
|
|
PlayCouponDetailsEntity detail = couponDetailsService.getById(couponDetailId);
|
|
Assertions.assertThat(detail).isNotNull();
|
|
Assertions.assertThat(detail.getUseState())
|
|
.as("强制取消指定单后优惠券应恢复为未使用")
|
|
.isEqualTo(CouponUseState.UNUSED.getCode());
|
|
Assertions.assertThat(detail.getUseTime()).isNull();
|
|
} finally {
|
|
CustomSecurityContextHolder.remove();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
// 测试用例:客户携带指定陪玩师和服务下单时,接口需返回成功,并生成待支付状态的指定订单,
|
|
// 验证订单金额与种子服务价格一致、陪玩师被正确指派,同时触发微信创建订单通知。
|
|
void specifiedOrderCreatesPendingOrder() throws Exception {
|
|
SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
|
try {
|
|
resetCustomerBalance();
|
|
String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
|
|
customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);
|
|
|
|
String remark = "API specified order " + IdUtils.getUuid();
|
|
ensureTenantContext();
|
|
long beforeCount = playOrderInfoService.lambdaQuery()
|
|
.eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
|
|
.eq(PlayOrderInfoEntity::getPlaceType, OrderConstant.PlaceType.SPECIFIED.getCode())
|
|
.count();
|
|
|
|
String payload = "{" +
|
|
"\"clerkId\":\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"," +
|
|
"\"commodityId\":\"" + ApiTestDataSeeder.DEFAULT_COMMODITY_ID + "\"," +
|
|
"\"commodityQuantity\":1," +
|
|
"\"weiChatCode\":\"apitest-customer-wx\"," +
|
|
"\"couponIds\":[]," +
|
|
"\"remark\":\"" + remark + "\"" +
|
|
"}";
|
|
|
|
reset(wxCustomMpService);
|
|
|
|
mockMvc.perform(post("/wx/custom/order/commodity")
|
|
.header(USER_HEADER, DEFAULT_USER)
|
|
.header(TENANT_HEADER, DEFAULT_TENANT)
|
|
.header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
.content(payload))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.code").value(200))
|
|
.andExpect(jsonPath("$.data").value("成功"));
|
|
|
|
ensureTenantContext();
|
|
PlayOrderInfoEntity order = playOrderInfoService.lambdaQuery()
|
|
.eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
|
|
.eq(PlayOrderInfoEntity::getRemark, remark)
|
|
.orderByDesc(PlayOrderInfoEntity::getCreatedTime)
|
|
.last("limit 1")
|
|
.one();
|
|
|
|
Assertions.assertThat(order).isNotNull();
|
|
Assertions.assertThat(order.getPlaceType()).isEqualTo(OrderConstant.PlaceType.SPECIFIED.getCode());
|
|
Assertions.assertThat(order.getOrderStatus()).isEqualTo(OrderConstant.OrderStatus.PENDING.getCode());
|
|
Assertions.assertThat(order.getAcceptBy()).isEqualTo(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
|
Assertions.assertThat(order.getFinalAmount()).isEqualByComparingTo(ApiTestDataSeeder.DEFAULT_COMMODITY_PRICE);
|
|
|
|
verify(wxCustomMpService).sendCreateOrderMessage(
|
|
eq(ApiTestDataSeeder.DEFAULT_TENANT_ID),
|
|
eq(ApiTestDataSeeder.DEFAULT_CLERK_OPEN_ID),
|
|
anyString(),
|
|
eq(order.getFinalAmount().toString()),
|
|
eq(order.getCommodityName()),
|
|
eq(order.getId()));
|
|
|
|
ensureTenantContext();
|
|
long afterCount = playOrderInfoService.lambdaQuery()
|
|
.eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
|
|
.eq(PlayOrderInfoEntity::getPlaceType, OrderConstant.PlaceType.SPECIFIED.getCode())
|
|
.count();
|
|
Assertions.assertThat(afterCount).isEqualTo(beforeCount + 1);
|
|
} finally {
|
|
CustomSecurityContextHolder.remove();
|
|
}
|
|
}
|
|
}
|