yeg
2026-09-17 e3f412c5474311a471345ea508db4875c9ec648e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package com.talentcard.server.business.portal;
 
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.List;
 
import com.talentcard.server.business.auth.TalentAuthService;
import com.talentcard.server.business.auth.TalentSessionPrincipal;
import com.talentcard.server.business.payment.HuifuPaymentService;
import com.talentcard.server.business.payment.PaymentReconciliationService;
import com.talentcard.server.business.payment.PaymentReconciliationService.PendingPaymentConfirmation;
import com.talentcard.server.business.payment.PaymentReconciliationService.PendingPaymentState;
import com.talentcard.server.common.ApiResponse;
import com.talentcard.server.common.BusinessException;
import com.talentcard.server.common.ErrorCode;
import jakarta.validation.Valid;
import org.springframework.http.HttpHeaders;
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.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/api/app/pending-orders")
public class PendingOrderPortalController {
    private final PendingOrderPortalService pendingOrderPortalService;
    private final HuifuPaymentService huifuPaymentService;
    private final PaymentReconciliationService paymentReconciliationService;
    private final TalentAuthService talentAuthService;
 
    public PendingOrderPortalController(PendingOrderPortalService pendingOrderPortalService, HuifuPaymentService huifuPaymentService,
            PaymentReconciliationService paymentReconciliationService, TalentAuthService talentAuthService) {
        this.pendingOrderPortalService = pendingOrderPortalService;
        this.huifuPaymentService = huifuPaymentService;
        this.paymentReconciliationService = paymentReconciliationService;
        this.talentAuthService = talentAuthService;
    }
 
    @GetMapping("/{orderNo}")
    public ApiResponse<Map<String, Object>> detail(@PathVariable String orderNo, @RequestParam Long talentId) {
        return ApiResponse.success(pendingOrderPortalService.detail(orderNo, talentId));
    }
 
    @GetMapping("/{orderNo}/coupons")
    public ApiResponse<List<Map<String, Object>>> coupons(@PathVariable String orderNo, @RequestParam Long talentId) {
        return ApiResponse.success(pendingOrderPortalService.coupons(orderNo, talentId));
    }
 
    @PostMapping("/{orderNo}/coupon")
    public ApiResponse<Map<String, Object>> chooseCoupon(@PathVariable String orderNo, @Valid @RequestBody PendingOrderCouponRequest request) {
        return ApiResponse.success(pendingOrderPortalService.chooseCoupon(orderNo, request));
    }
 
    @PostMapping("/{orderNo}/cancel")
    public ApiResponse<Map<String, Object>> cancel(
            @PathVariable String orderNo,
            @Valid @RequestBody PendingOrderCancelRequest request,
            @RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization
    ) {
        ensureSessionTalent(authorization, request.talentId());
        return ApiResponse.success(pendingOrderPortalService.cancel(orderNo, request.talentId()));
    }
 
    @PostMapping("/{orderNo}/pay")
    public ApiResponse<Map<String, Object>> pay(@PathVariable String orderNo, @Valid @RequestBody PendingOrderPaymentRequest request) {
        throw new BusinessException(ErrorCode.CONFLICT, "支付接口已升级,请使用支付发起接口");
    }
 
    @PostMapping("/{orderNo}/payment")
    public ApiResponse<Map<String, Object>> createPayment(
            @PathVariable String orderNo,
            @Valid @RequestBody PendingOrderPaymentRequest request,
            @RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization,
            @RequestHeader(value = "X-Idempotency-Key", required = false) String idempotencyKey
    ) {
        ensureSessionTalent(authorization, request.talentId());
        return ApiResponse.success(huifuPaymentService.beginMiniProgramPayment(orderNo, request.talentId(), request.priceVersion(), idempotencyKey));
    }
 
    @GetMapping("/{orderNo}/payment")
    public ApiResponse<Map<String, Object>> paymentStatus(
            @PathVariable String orderNo,
            @RequestParam Long talentId,
            @RequestParam(required = false) String paymentNo,
            @RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization
    ) {
        ensureSessionTalent(authorization, talentId);
        return ApiResponse.success(huifuPaymentService.paymentStatus(orderNo, talentId, paymentNo));
    }
 
    /**
     * 放弃支付:客户端在收银台里点了关闭时调用,服务端立刻尝试关单。
     *
     * <p>只触发关单、绝不结算:客户端说「我放弃了」只当作信号,钱收没收下一律以通道回执为准。
     */
    @PostMapping("/{orderNo}/payment/abandon")
    public ApiResponse<Map<String, Object>> abandonPayment(
            @PathVariable String orderNo,
            @Valid @RequestBody PendingOrderAbandonRequest request,
            @RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization
    ) {
        ensureSessionTalent(authorization, request.talentId());
        PendingPaymentConfirmation confirmation =
                paymentReconciliationService.abandonPendingPayments(orderNo, request.paymentNo());
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("paymentNo", confirmation.paymentNo());
        // 关单有结论(关掉了/通道拒绝关单但仍然允许继续)就是 CANCELLED;
        // 通道说还在处理中就是 PAYMENT_PROCESSING——后台会接着关,客户端按可等待渲染。
        result.put("paymentStatus", confirmation.state() == PendingPaymentState.UNCONFIRMED
                ? "PAYMENT_PROCESSING" : "CANCELLED");
        return ApiResponse.success(result);
    }
 
    private void ensureSessionTalent(String authorization, Long talentId) {
        TalentSessionPrincipal principal = talentAuthService.authenticate(authorization);
        if (!principal.talentId().equals(talentId)) throw new BusinessException(ErrorCode.FORBIDDEN, "无权操作其他人才用户的订单");
    }
}