package com.talentcard.server.business.payment; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import java.time.LocalDateTime; import java.util.Map; import com.talentcard.server.business.merchant.MerchantRevokeVerificationRequest; import com.talentcard.server.business.merchant.MerchantWorkService; import com.talentcard.server.business.merchant.VerifyRequest; import com.talentcard.server.business.merchant.auth.MerchantPrincipal; import com.talentcard.server.business.payment.huifu.HuifuDelayAccountGateway; import com.talentcard.server.common.BusinessException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; /** * 撤销核销的资金侧:钱必须真的回到通道延迟户,订单才允许翻回「待核销」。 * *
核销时钱已经从延迟户划给商户(进了基本户),撤销核销要靠通道的交易确认退款把它退回来。
* 回退没成功就不许翻单——否则订单显示「待核销」,钱却已经结算给商户,商户还能拿着这张单再核销一次;
* 等 D1 自动结算把钱划到商户银行卡之后,通道那边也退不回来了。
*/
@SpringBootTest
@ActiveProfiles("test")
class RevokeVerificationFundTests {
private static final String HUIFU_ID = "6666000226048687";
@Autowired
private JdbcTemplate jdbc;
@Autowired
private MerchantWorkService merchantWorkService;
@MockitoBean
private HuifuDelayAccountGateway delayGateway;
private Long merchantId;
private Long storeId;
private Long talentId;
private MerchantPrincipal principal;
@BeforeEach
void prepareMerchant() {
String suffix = String.valueOf(System.nanoTime());
jdbc.update("insert into merchant(merchant_name, status, can_login_merchant_app, deleted) values (?, 1, 1, 0)",
"撤销核销商户 " + suffix);
merchantId = jdbc.queryForObject("select max(id) from merchant", Long.class);
jdbc.update("insert into merchant_store(merchant_id, store_name, status, deleted) values (?, ?, 1, 0)",
merchantId, "撤销核销门店 " + suffix);
storeId = jdbc.queryForObject("select max(id) from merchant_store", Long.class);
jdbc.update("insert into talent_user(openid, name, mobile, status, deleted) values (?, ?, ?, 1, 0)",
"revoke-fund-" + suffix, "撤销核销用户", "13900000010");
talentId = jdbc.queryForObject("select max(id) from talent_user", Long.class);
principal = new MerchantPrincipal(9101L, merchantId, storeId, "operator");
}
/** 撤销核销要把钱退回延迟户:退回成功才翻单,这笔钱才算重新变成不可结算。 */
@Test
void revokesOnlyAfterTheMoneyComesBackToTheDelayAccount() {
long orderId = verifiedOrderWithConfirmedMoney();
when(delayGateway.refund(any())).thenReturn(revert(HuifuDelayAccountGateway.HuifuDelayRevert.Outcome.SUCCEEDED,
"00000000", "交易成功"));
Map 核销走真实服务:它会写下核销记录并调用交易确认,撤销时才有核销记录可撤、有确认可退。
*/
private long verifiedOrderWithConfirmedMoney() {
String orderNo = "ORDER-REVOKE-" + System.nanoTime();
jdbc.update("""
insert into benefit_order(order_no, talent_id, merchant_id, store_id, order_type, order_amount,
subsidy_amount, pay_amount, order_status, verify_status, created_at, deleted)
values (?, ?, ?, ?, 'GROUP_BUY', 10000, 1000, 9000, 1, 0, ?, 0)
""", orderNo, talentId, merchantId, storeId, java.sql.Timestamp.valueOf(LocalDateTime.now()));
Long orderId = jdbc.queryForObject("select id from benefit_order where order_no = ?", Long.class, orderNo);
jdbc.update("""
insert into payment_transaction(payment_no, provider, order_id, order_no, talent_id, merchant_id,
provider_huifu_id, amount, status, idempotency_key, attempt_no,
paid_at, funds_confirmation_status)
values (?, 'HUIFU', ?, ?, ?, ?, ?, 9000, 'SUCCEEDED', ?, 1, now(), 'pending')
""", "P-VERIFY-" + System.nanoTime(), orderId, orderNo, talentId, merchantId, HUIFU_ID,
"verify-payment-" + orderNo);
when(delayGateway.confirm(any())).thenReturn(new HuifuDelayAccountGateway.HuifuDelayConfirmation(
HuifuDelayAccountGateway.HuifuDelayConfirmation.Outcome.SUCCEEDED,
"00000000", "交易成功", "0.00", "90.00", "HF-C-" + System.nanoTime()));
merchantWorkService.verify(orderNo, principal, new VerifyRequest("merchant", "test verification"),
"verify-" + orderNo);
return orderId;
}
private String fundsStatus(long orderId) {
return jdbc.queryForObject("select funds_confirmation_status from payment_transaction where order_id = ?",
String.class, orderId);
}
}