package api import ( "fmt" "net/http" "strconv" "strings" "github.com/gin-gonic/gin" _ "oci-portal/internal/oci" // swagger 注解引用 ) // payInvoiceRequest 是付款请求体;email 接收 OSP 付款回执。 type payInvoiceRequest struct { Email string `json:"email" binding:"required"` } // @Summary 发票列表 // @Tags 账单 // @Param id path int true "配置 ID" // @Param year query int false "自然年过滤(按开票时间);缺省全量" // @Success 200 {array} oci.Invoice // @Security BearerAuth // @Router /api/v1/oci-configs/{id}/invoices [get] func (h *ociConfigHandler) invoices(c *gin.Context) { id, ok := pathID(c) if !ok { return } year, _ := strconv.Atoi(c.Query("year")) list, err := h.svc.Invoices(c.Request.Context(), id, year) if err != nil { respondError(c, err) return } c.JSON(http.StatusOK, list) } // @Summary 发票费用明细 // @Tags 账单 // @Param id path int true "配置 ID" // @Param internalId path string true "发票内部 ID" // @Success 200 {array} oci.InvoiceLine // @Security BearerAuth // @Router /api/v1/oci-configs/{id}/invoices/{internalId}/lines [get] func (h *ociConfigHandler) invoiceLines(c *gin.Context) { id, ok := pathID(c) if !ok { return } lines, err := h.svc.InvoiceLines(c.Request.Context(), id, c.Param("internalId")) if err != nil { respondError(c, err) return } c.JSON(http.StatusOK, lines) } // @Summary 发票 PDF 下载 // @Tags 账单 // @Param id path int true "配置 ID" // @Param internalId path string true "发票内部 ID" // @Param number query string false "发票号(用作下载文件名)" // @Success 200 {file} binary "PDF 原文" // @Security BearerAuth // @Router /api/v1/oci-configs/{id}/invoices/{internalId}/pdf [get] func (h *ociConfigHandler) invoicePdf(c *gin.Context) { id, ok := pathID(c) if !ok { return } data, err := h.svc.InvoicePdf(c.Request.Context(), id, c.Param("internalId")) if err != nil { respondError(c, err) return } c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%q", pdfFileName(c.Query("number"), c.Param("internalId")))) c.Data(http.StatusOK, "application/pdf", data) } // pdfFileName 生成下载文件名:优先发票号,剔除引号/路径分隔等不安全字符。 func pdfFileName(number, internalID string) string { name := strings.TrimSpace(number) if name == "" { name = internalID } name = strings.Map(func(r rune) rune { if r == '"' || r == '/' || r == '\\' || r < 0x20 { return '_' } return r }, name) return name + ".pdf" } // @Summary 支付发票 // @Tags 账单 // @Param id path int true "配置 ID" // @Param internalId path string true "发票内部 ID" // @Param body body payInvoiceRequest true "回执邮箱" // @Success 200 {object} map[string]string // @Security BearerAuth // @Router /api/v1/oci-configs/{id}/invoices/{internalId}/pay [post] func (h *ociConfigHandler) payInvoice(c *gin.Context) { id, ok := pathID(c) if !ok { return } var req payInvoiceRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.svc.PayInvoice(c.Request.Context(), id, c.Param("internalId"), req.Email); err != nil { respondError(c, err) return } c.JSON(http.StatusOK, gin.H{"status": "submitted"}) } // @Summary 付款方式列表 // @Tags 账单 // @Param id path int true "配置 ID" // @Success 200 {array} oci.PaymentMethod // @Security BearerAuth // @Router /api/v1/oci-configs/{id}/payment-methods [get] func (h *ociConfigHandler) paymentMethods(c *gin.Context) { id, ok := pathID(c) if !ok { return } methods, err := h.svc.PaymentMethods(c.Request.Context(), id) if err != nil { respondError(c, err) return } c.JSON(http.StatusOK, methods) }