| | |
| | | package com.tourguide.modules.order.service; |
| | | |
| | | import com.tourguide.modules.order.domain.OrderStatus; |
| | | import com.tourguide.modules.order.domain.RefundStatus; |
| | | import com.tourguide.modules.order.domain.PaymentMethod; |
| | | import com.tourguide.modules.order.entity.TourOrder; |
| | | import com.tourguide.modules.order.entity.TourOrderRefund; |
| | | import com.tourguide.modules.order.mapper.TourOrderMapper; |
| | | import com.tourguide.modules.order.mapper.TourOrderRefundMapper; |
| | | import com.tourguide.modules.order.payment.PaymentRefundGateway; |
| | | import org.junit.jupiter.api.BeforeEach; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.mockito.ArgumentCaptor; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | import static org.assertj.core.api.Assertions.assertThat; |
| | | import static org.mockito.ArgumentMatchers.eq; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.never; |
| | | import static org.mockito.Mockito.times; |
| | |
| | | org.mockito.ArgumentMatchers.eq(new BigDecimal("36.00")))) |
| | | .thenAnswer(invocation -> order.getRefundOrderNo()); |
| | | |
| | | refundService.requestRefund(order); |
| | | refundService.requestRefund(order); |
| | | boolean accepted = refundService.requestRefund(order); |
| | | boolean duplicateAccepted = refundService.requestRefund(order); |
| | | |
| | | assertThat(accepted).isTrue(); |
| | | assertThat(duplicateAccepted).isTrue(); |
| | | assertThat(order.getRefundStatus()).isEqualTo(RefundStatus.REFUNDING.getCode()); |
| | | assertThat(order.getStatus()).isEqualTo(OrderStatus.CANCELLED.getCode()); |
| | | assertThat(order.getRefundOrderNo()).startsWith("RF").hasSize(32); |
| | | assertThat(order.getRefundAmount()).isEqualByComparingTo("36.00"); |
| | | assertThat(order.getRefundMethod()).isEqualTo("WECHAT"); |
| | | verify(refundGateway, times(1)).refund(order, new BigDecimal("36.00")); |
| | | verify(orderMapper, times(1)).updateById(order); |
| | | verify(orderMapper, times(2)).updateById(order); |
| | | } |
| | | |
| | | @Test |
| | |
| | | org.mockito.ArgumentMatchers.any(BigDecimal.class))) |
| | | .thenThrow(new IllegalStateException("gateway unavailable")); |
| | | |
| | | refundService.requestRefund(order); |
| | | boolean accepted = refundService.requestRefund(order); |
| | | |
| | | assertThat(accepted).isFalse(); |
| | | assertThat(order.getRefundStatus()).isEqualTo(RefundStatus.REFUND_FAILED.getCode()); |
| | | verify(orderMapper, times(2)).updateById(order); |
| | | } |
| | |
| | | refundService.requestRefund(order); |
| | | |
| | | assertThat(order.getRefundStatus()).isEqualTo(RefundStatus.REFUNDED.getCode()); |
| | | assertThat(order.getStatus()).isEqualTo(OrderStatus.REFUNDED.getCode()); |
| | | assertThat(order.getRefundTime()).isNotNull(); |
| | | assertThat(order.getRefundMethod()).isEqualTo(PaymentMethod.TEMPORARY_BYPASS.getCode()); |
| | | verify(refundGateway, never()).refund(org.mockito.ArgumentMatchers.any(), |
| | | org.mockito.ArgumentMatchers.any()); |
| | | verify(orderMapper).updateById(order); |
| | | } |
| | | |
| | | @Test |
| | | void adminRefundShouldSubmitRequestedAmount() { |
| | | TourOrder order = paidOrder(); |
| | | when(refundGateway.refund(eq(order), eq(new BigDecimal("20.00")))) |
| | | .thenAnswer(invocation -> order.getRefundOrderNo()); |
| | | |
| | | boolean accepted = refundService.requestRefund(order, new BigDecimal("20.00")); |
| | | |
| | | assertThat(accepted).isTrue(); |
| | | assertThat(order.getRefundAmount()).isEqualByComparingTo("20.00"); |
| | | verify(refundGateway).refund(order, new BigDecimal("20.00")); |
| | | } |
| | | |
| | | @Test |
| | | void followUpRefundShouldOnlyUseRemainingAmount() { |
| | | TourOrder order = paidOrder(); |
| | | order.setStatus(OrderStatus.CANCELLED.getCode()); |
| | | order.setRefundStatus(RefundStatus.PARTIAL.getCode()); |
| | | order.setRefundedAmount(new BigDecimal("20.00")); |
| | | when(refundGateway.refund(eq(order), eq(new BigDecimal("16.00")))) |
| | | .thenAnswer(invocation -> order.getRefundOrderNo()); |
| | | |
| | | boolean accepted = refundService.requestRefund(order, new BigDecimal("16.00")); |
| | | |
| | | assertThat(accepted).isTrue(); |
| | | assertThat(order.getRefundAmount()).isEqualByComparingTo("16.00"); |
| | | assertThat(order.getRefundStatus()).isEqualTo(RefundStatus.REFUNDING.getCode()); |
| | | verify(refundGateway).refund(order, new BigDecimal("16.00")); |
| | | } |
| | | |
| | | @Test |
| | | void eachSuccessfulLocalRefundShouldCreateAnIndependentCompletedRecord() { |
| | | TourOrderRefundMapper refundMapper = mock(TourOrderRefundMapper.class); |
| | | refundService = new OrderRefundService(orderMapper, refundGateway, refundMapper); |
| | | TourOrder order = paidOrder(); |
| | | order.setPaymentMethod(PaymentMethod.TEMPORARY_BYPASS.getCode()); |
| | | |
| | | assertThat(refundService.requestRefund(order, new BigDecimal("20.00"))).isTrue(); |
| | | assertThat(refundService.requestRefund(order, new BigDecimal("16.00"))).isTrue(); |
| | | |
| | | ArgumentCaptor<TourOrderRefund> insertCaptor = ArgumentCaptor.forClass(TourOrderRefund.class); |
| | | verify(refundMapper, times(2)).insert(insertCaptor.capture()); |
| | | assertThat(insertCaptor.getAllValues()).extracting(TourOrderRefund::getRefundAmount) |
| | | .containsExactly(new BigDecimal("20.00"), new BigDecimal("16.00")); |
| | | verify(refundMapper, times(2)).updateById(any(TourOrderRefund.class)); |
| | | assertThat(insertCaptor.getAllValues()).extracting(TourOrderRefund::getRefundStatus) |
| | | .containsOnly(RefundStatus.REFUNDED.getCode()); |
| | | } |
| | | |
| | | @Test |
| | | void refundShouldRejectAmountAboveRemainingBalance() { |
| | | TourOrder order = paidOrder(); |
| | | order.setRefundedAmount(new BigDecimal("20.00")); |
| | | |
| | | boolean accepted = refundService.requestRefund(order, new BigDecimal("17.00")); |
| | | |
| | | assertThat(accepted).isFalse(); |
| | | verify(refundGateway, never()).refund(org.mockito.ArgumentMatchers.any(), |
| | | org.mockito.ArgumentMatchers.any()); |
| | | } |
| | | |
| | | private TourOrder paidOrder() { |
| | |
| | | order.setAmount(new BigDecimal("40.00")); |
| | | order.setPaymentAmount(new BigDecimal("36.00")); |
| | | order.setPaymentTime(LocalDateTime.now()); |
| | | order.setStatus(OrderStatus.COMPLETED.getCode()); |
| | | order.setRefundStatus(RefundStatus.NONE.getCode()); |
| | | return order; |
| | | } |