package com.talentcard.server.business.merchant; import java.util.Map; import com.talentcard.server.business.PageResult; import com.talentcard.server.business.merchant.auth.MerchantAuthService; import com.talentcard.server.business.merchant.auth.MerchantPrincipal; import com.talentcard.server.common.ApiResponse; import com.talentcard.server.service.MerchantSettlementStatisticsService; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/merchant") public class MerchantWorkController { private final MerchantWorkService merchantWorkService; private final DashboardSummaryService dashboardSummaryService; private final MerchantVerificationService merchantVerificationService; private final ManualConsumptionService manualConsumptionService; private final MerchantManagementService merchantManagementService; private final MerchantSettlementService merchantSettlementService; private final MerchantSettlementStatisticsService merchantSettlementStatisticsService; public MerchantWorkController(MerchantWorkService merchantWorkService, DashboardSummaryService dashboardSummaryService, MerchantVerificationService merchantVerificationService, ManualConsumptionService manualConsumptionService, MerchantManagementService merchantManagementService, MerchantSettlementService merchantSettlementService, MerchantSettlementStatisticsService merchantSettlementStatisticsService) { this.merchantWorkService = merchantWorkService; this.dashboardSummaryService = dashboardSummaryService; this.merchantVerificationService = merchantVerificationService; this.manualConsumptionService = manualConsumptionService; this.merchantManagementService = merchantManagementService; this.merchantSettlementService = merchantSettlementService; this.merchantSettlementStatisticsService = merchantSettlementStatisticsService; } @GetMapping("/onboarding/status") public ApiResponse> onboardingStatus(@RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal) { return ApiResponse.success(merchantSettlementService.onboardingStatus(principal)); } @PostMapping("/onboarding/refresh") public ApiResponse> refreshOnboarding(@RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @RequestParam(defaultValue = "false") boolean retry) { return ApiResponse.success(merchantSettlementService.refreshOnboarding(principal, retry)); } /** 微信实名认证:业务开通确认生效后商户主动推进的下一步。未决申请不会重复提交。 */ @PostMapping("/onboarding/realname") public ApiResponse> submitRealname(@RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @RequestParam(defaultValue = "false") boolean retry) { return ApiResponse.success(merchantSettlementService.submitRealname(principal, retry)); } @PostMapping("/onboarding") public ApiResponse> submitOnboarding(@RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @Valid @RequestBody MerchantOnboardingRequest request, @RequestHeader(value = "X-Idempotency-Key", required = false) String key) { return ApiResponse.success(merchantSettlementService.submitOnboarding(principal, request, key)); } /** 收款能力自查:与下单闸门同源同文案的判定结果与最近核实时间,只读不查通道。 */ @GetMapping("/payment-readiness") public ApiResponse> paymentReadiness(@RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal) { return ApiResponse.success(merchantSettlementService.paymentReadiness(principal)); } /** 立即核实:主动查一次通道刷新收款能力快照,受冷却保护。 */ @PostMapping("/payment-readiness/verify") public ApiResponse> verifyPaymentReadiness(@RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal) { return ApiResponse.success(merchantSettlementService.verifyPaymentReadiness(principal)); } @GetMapping("/bank-cards") public ApiResponse>> bankCards(@RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal) { return ApiResponse.success(merchantSettlementService.bankCards(principal)); } @PostMapping("/bank-cards") public ApiResponse> addBankCard(@RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @Valid @RequestBody MerchantBankCardRequest request, @RequestHeader(value = "X-Idempotency-Key", required = false) String key) { return ApiResponse.success(merchantSettlementService.addBankCard(principal, request, key)); } @GetMapping("/bank-branches") public ApiResponse>> bankBranches(@RequestParam(required = false) String cityCode, @RequestParam(required = false) String cityName, @RequestParam(required = false) String keyword, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int size) { return ApiResponse.success(merchantSettlementService.bankBranches(cityCode, cityName, keyword, page, size)); } @GetMapping("/dashboard/summary") public ApiResponse> dashboardSummary(@RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal) { return ApiResponse.success(merchantSettlementStatisticsService.dashboardCards(principal.merchantId())); } @GetMapping("/system/business-date") public ApiResponse> businessDate(@RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal) { return ApiResponse.success(Map.of( "businessDate", java.time.LocalDate.now(java.time.ZoneId.of("Asia/Shanghai")).toString(), "timeZone", "Asia/Shanghai", "serverTime", java.time.OffsetDateTime.now(java.time.ZoneId.of("Asia/Shanghai")).toString() )); } @GetMapping("/profile") public ApiResponse> profile(@RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal) { return ApiResponse.success(merchantManagementService.profile(principal)); } @org.springframework.web.bind.annotation.PatchMapping("/business-status") public ApiResponse> updateBusinessStatus( @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @Valid @RequestBody MerchantBusinessStatusRequest request ) { return ApiResponse.success(merchantManagementService.updateBusinessStatus(principal, request)); } @GetMapping("/business-hours") public ApiResponse> businessHours(@RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal) { return ApiResponse.success(merchantManagementService.businessHours(principal)); } @org.springframework.web.bind.annotation.PutMapping("/business-hours") public ApiResponse> updateBusinessHours( @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @Valid @RequestBody MerchantBusinessHoursRequest request ) { return ApiResponse.success(merchantManagementService.updateBusinessHours(principal, request)); } @GetMapping("/products") public ApiResponse>> products( @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @RequestParam(defaultValue = "online") String scope, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size ) { return ApiResponse.success(merchantManagementService.products(principal, scope, page, size)); } @GetMapping("/products/{productId}") public ApiResponse> product( @PathVariable Long productId, @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal ) { return ApiResponse.success(merchantManagementService.product(principal, productId)); } @org.springframework.web.bind.annotation.PutMapping("/products/{productId}") public ApiResponse> updateProduct( @PathVariable Long productId, @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @Valid @RequestBody MerchantProductUpdateRequest request ) { return ApiResponse.success(merchantManagementService.updateProduct(principal, productId, request)); } @org.springframework.web.bind.annotation.PatchMapping("/products/{productId}/status") public ApiResponse> updateProductStatus( @PathVariable Long productId, @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @Valid @RequestBody MerchantProductStatusRequest request ) { return ApiResponse.success(merchantManagementService.updateProductStatus(principal, productId, request)); } @GetMapping("/benefits") public ApiResponse>> benefits(@RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal) { return ApiResponse.success(merchantManagementService.benefits(principal)); } @PostMapping("/verification/scan") public ApiResponse> scanVerification( @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @Valid @RequestBody VerificationScanRequest request ) { return ApiResponse.success(merchantVerificationService.scan(principal, request)); } /** * 免费入住券核销。涉及支付的订单核销走 {@code POST /orders/{orderNo}/verify}: * 核销要写记录并确认延迟入账的资金,与不涉及支付的免费入住券是两条不同的线。 */ @PostMapping("/verification/free-stay/confirm") public ApiResponse> confirmFreeStayVerification( @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @Valid @RequestBody VerificationConfirmRequest request, @RequestHeader(value = "X-Idempotency-Key", required = false) String idempotencyKey ) { return ApiResponse.success(merchantVerificationService.confirmFreeStay(principal, request, idempotencyKey)); } @GetMapping("/manual-consumptions/{verificationId}") public ApiResponse> manualConsumptionDetail( @PathVariable String verificationId, @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal ) { return ApiResponse.success(manualConsumptionService.detail(principal, verificationId)); } @PostMapping("/manual-consumptions/quote") public ApiResponse> quoteManualConsumption( @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @Valid @RequestBody ManualConsumptionCreateRequest request ) { return ApiResponse.success(manualConsumptionService.quote(principal, request)); } @PostMapping("/manual-consumptions") public ApiResponse> createManualConsumption( @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @Valid @RequestBody ManualConsumptionCreateRequest request, @RequestHeader(value = "X-Idempotency-Key", required = false) String idempotencyKey ) { return ApiResponse.success(manualConsumptionService.create(principal, request, idempotencyKey)); } @GetMapping("/orders") public ApiResponse>> orders( @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(required = false) String category, @RequestParam(required = false) String status, @RequestParam(required = false) String startDate, @RequestParam(required = false) String endDate ) { return ApiResponse.success(merchantWorkService.orders(principal.merchantId(), principal.storeId(), page, size, new MerchantOrderQuery(category, status, startDate, endDate))); } @GetMapping("/orders/{orderNo}") public ApiResponse> order( @PathVariable String orderNo, @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal ) { return ApiResponse.success(merchantWorkService.order(orderNo, principal.merchantId(), principal.storeId())); } @PostMapping("/orders/{orderNo}/verify") public ApiResponse> verify( @PathVariable String orderNo, @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @Valid @RequestBody VerifyRequest request, @RequestHeader(value = "X-Idempotency-Key", required = false) String idempotencyKey ) { return ApiResponse.success(merchantWorkService.verify(orderNo, principal, request, idempotencyKey)); } @PostMapping("/orders/revoke-verification") public ApiResponse> revokeVerification( @RequestAttribute(MerchantAuthService.PRINCIPAL_ATTRIBUTE) MerchantPrincipal principal, @Valid @RequestBody MerchantRevokeVerificationRequest request, @RequestHeader(value = "X-Idempotency-Key", required = false) String idempotencyKey ) { return ApiResponse.success(merchantWorkService.revokeVerification(principal, request, idempotencyKey)); } }