intelligence_finance-202608.../intelligence_finance/valid.md

34 KiB

// File: go.mod

module intelligence_finance

go 1.21

// File: crypto.go

package intelligence_finance

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"math/big"
	"sort"
	"strings"
	"time"
)

// GenerateTimestamp generates a Unix timestamp in seconds as a string.
func GenerateTimestamp() string {
	return fmt.Sprintf("%d", time.Now().Unix())
}

// GenerateTimestampMillis generates a Unix timestamp in milliseconds as a string.
func GenerateTimestampMillis() string {
	return fmt.Sprintf("%d", time.Now().UnixMilli())
}

// GenerateNonce generates a cryptographically secure random string of the given length.
func GenerateNonce(length int) (string, error) {
	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	b := make([]byte, length)
	for i := range b {
		num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
		if err != nil {
			return "", fmt.Errorf("generate nonce: %w", err)
		}
		b[i] = charset[num.Int64()]
	}
	return string(b), nil
}

// HmacSHA256Base64 computes HmacSHA256 of the given data using the secret key,
// and returns the result as a Base64-encoded string.
func HmacSHA256Base64(secret, data string) string {
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(data))
	return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}

// BuildSignString sorts parameters by key in dictionary order and concatenates
// them as key=value&key2=value2, excluding empty values and the sign/signature fields.
func BuildSignString(params map[string]string) string {
	keys := make([]string, 0, len(params))
	for k, v := range params {
		if v != "" && k != "sign" && k != "signature" {
			keys = append(keys, k)
		}
	}
	sort.Strings(keys)

	var parts []string
	for _, k := range keys {
		parts = append(parts, fmt.Sprintf("%s=%s", k, params[k]))
	}
	return strings.Join(parts, "&")
}

// BuildSignStringOrdered concatenates parameters in the specified key order,
// excluding empty values.
func BuildSignStringOrdered(params map[string]string, orderedKeys []string) string {
	var parts []string
	for _, k := range orderedKeys {
		if v, ok := params[k]; ok && v != "" {
			parts = append(parts, fmt.Sprintf("%s=%s", k, v))
		}
	}
	return strings.Join(parts, "&")
}

// File: types.go

package intelligence_finance

// ============================================================
// General API Response
// ============================================================

// APIResponse is the general response wrapper returned by the platform APIs.
type APIResponse struct {
	Code int                    `json:"code"`
	Msg  string                 `json:"msg,omitempty"`
	Data map[string]interface{} `json:"data,omitempty"`
}

// ============================================================
// Interface 1: 订单开票接口 (Invoice Create)
// ============================================================

// InvoiceCreateRequest is the request for creating an invoice order.
type InvoiceCreateRequest struct {
	CompanyCode    string               `json:"companyCode,omitempty"`
	OrderID        string               `json:"orderId"`
	InvoiceType    int                  `json:"invoiceType"`
	Products       []InvoiceProduct     `json:"products"`
	Remark         string               `json:"remark,omitempty"`
	Purchaser      string               `json:"purchaser"`
	TaxNum         string               `json:"taxnum,omitempty"`
	PurchaserAddr  string               `json:"purchaserAddress,omitempty"`
	PurchaserTel   string               `json:"purchaserTel,omitempty"`
	BankName       string               `json:"bankName,omitempty"`
	BankAccount    string               `json:"bankAccount,omitempty"`
	Phone          string               `json:"phone,omitempty"`
	Email          string               `json:"email,omitempty"`
	ApplyPerson    string               `json:"applyPerson,omitempty"`
	Payee          string               `json:"payee,omitempty"`
	Reviewer       string               `json:"reviewer,omitempty"`
	InvoiceRemark  string               `json:"invoiceRemark,omitempty"`
	NaturalPerson  string               `json:"naturalPerson,omitempty"`
	AdditionInfo   string               `json:"additionInfo,omitempty"`
}

// InvoiceProduct represents a single product/service line item in an invoice.
type InvoiceProduct struct {
	ProductName      string  `json:"productName"`
	RevenueCode      string  `json:"revenueCode"`
	AmountIncludeTax float64 `json:"amountIncludeTax"`
	Specs            string  `json:"specs,omitempty"`
	Unit             string  `json:"unit,omitempty"`
	Quantity         float64 `json:"quantity"`
	Discount         float64 `json:"discount,omitempty"`
	TaxSign          int     `json:"taxSign,omitempty"`
	TaxRate          float64 `json:"taxRate,omitempty"`
}

// InvoiceCreateResponse is the response for creating an invoice order.
type InvoiceCreateResponse struct {
	Status   int              `json:"status"`
	ErrorMsg string           `json:"errorMsg,omitempty"`
	DataList []InvoiceData    `json:"dataList"`
}

// InvoiceData contains detailed information about a single invoice.
type InvoiceData struct {
	DeviceCode         string           `json:"deviceCode"`
	Drawer             string           `json:"drawer"`
	Email              string           `json:"email"`
	InvoiceType        string           `json:"invoiceType"`
	IssueType          string           `json:"issueType"`
	ListFlag           string           `json:"listFlag"`
	Mobile             string           `json:"mobile"`
	OriginalInvCode    string           `json:"originalInvCode,omitempty"`
	OriginalInvNo      string           `json:"originalInvNo,omitempty"`
	AdditionInfo       string           `json:"additionInfo,omitempty"`
	Payee              string           `json:"payee"`
	PurchaserAddress   string           `json:"purchaserAddress"`
	PurchaserBankAcct  string           `json:"purchaserBankAccount"`
	PurchaserBankName  string           `json:"purchaserBankName"`
	PurchaserName      string           `json:"purchaserName"`
	PurchaserTaxNo     string           `json:"purchaserTaxNo"`
	PurchaserTel       string           `json:"purchaserTel"`
	NaturalPerson      string           `json:"naturalPerson,omitempty"`
	Remark             string           `json:"remark"`
	Reviewer           string           `json:"reviewer"`
	SellerAddress      string           `json:"sellerAddress,omitempty"`
	SellerBankAcct     string           `json:"sellerBankAccount"`
	SellerBankName     string           `json:"sellerBankName"`
	SellerName         string           `json:"sellerName"`
	CheckCode          string           `json:"checkCode"`
	CipherText         string           `json:"cipherText"`
	DrewDate           string           `json:"drewDate,omitempty"`
	InvoiceCode        string           `json:"invoiceCode"`
	InvoiceNo          string           `json:"invoiceNo"`
	InvoiceStatus      string           `json:"invoiceStatus"`
	LayoutFileURL      string           `json:"layoutFileUrl,omitempty"`
	PDFURL             string           `json:"pdfUrl,omitempty"`
	OFDURL             string           `json:"ofdUrl,omitempty"`
	XMLURL             string           `json:"xmlUrl,omitempty"`
	TotalExcludeTax    string           `json:"totalExcludeTax"`
	TotalIncludeTax    string           `json:"totalIncludeTax"`
	TotalTaxAmount     string           `json:"totalTaxAmount"`
	LevyingType        string           `json:"levyingType"`
	Details            []InvoiceDetail  `json:"details"`
}

// InvoiceDetail represents a product detail line within an invoice.
type InvoiceDetail struct {
	Amount             string `json:"amount,omitempty"`
	Quantity           string `json:"quantity,omitempty"`
	DeductionAmount    string `json:"deductionAmount,omitempty"`
	TaxAmount          string `json:"taxAmount,omitempty"`
	ItemTitle          string `json:"itemTitle"`
	TaxCode            string `json:"taxCode"`
	ItemType           string `json:"itemType"`
	ItemName           string `json:"itemName"`
	Specs              string `json:"specs,omitempty"`
	TaxFreePolicy      string `json:"taxFreePolicy"`
	PreferentialPolicy string `json:"preferentialPolicy"`
	TaxRate            string `json:"taxRate"`
	TaxSign            string `json:"taxSign"`
	Unit               string `json:"unit,omitempty"`
	UnitPrice          string `json:"unitPrice,omitempty"`
}

// ============================================================
// Interface 2: 开票状态查询 (Invoice Status Query)
// ============================================================

// InvoiceStatusQueryRequest is the request for querying invoice status.
type InvoiceStatusQueryRequest struct {
	OrderID string `json:"orderId"`
}

// InvoiceStatusQueryResponse is the response for querying invoice status.
type InvoiceStatusQueryResponse struct {
	Status  int            `json:"status"`
	Message string         `json:"message,omitempty"`
	Data    []InvoiceData  `json:"data"`
}

// ============================================================
// Interface 3: 创建付款单据 (Create Payment Order)
// ============================================================

// CreatePaymentOrderRequest is the request for creating a payment order.
type CreatePaymentOrderRequest struct {
	Code                     string              `json:"code"`
	YidaAppType              string              `json:"yidaAppType,omitempty"`
	EmpAccountUserID         string              `json:"empAccountUserId,omitempty"`
	Department               *Department         `json:"department,omitempty"`
	Usage                    string              `json:"usage,omitempty"`
	PaymentUserID            string              `json:"paymentUserId,omitempty"`
	Customer                 *Customer           `json:"customer,omitempty"`
	PrincipalID              string              `json:"principalId,omitempty"`
	Remark                   string              `json:"remark,omitempty"`
	Supplier                 *Supplier           `json:"supplier,omitempty"`
	Title                    string              `json:"title,omitempty"`
	Project                  *Project            `json:"project,omitempty"`
	PaymentUserIDListStr     string              `json:"paymentUserIdListStr,omitempty"`
	NeedPayment              bool                `json:"needPayment,omitempty"`
	PaymentDetailListJSONStr string              `json:"paymentDetailListJsonStr,omitempty"`
	PaymentDetailList        []PaymentDetail     `json:"paymentDetailList,omitempty"`
	Company                  *Company            `json:"company,omitempty"`
	Amount                   string              `json:"amount,omitempty"`
	RecipientAccountInfo     *RecipientAccount   `json:"recipientAccountInfo,omitempty"`
	EnterpriseAccount        *EnterpriseAccount  `json:"enterpriseAccount,omitempty"`
	Category                 []Category          `json:"category,omitempty"`
	UserID                   string              `json:"userId"`
	OccurDate                int64               `json:"occurDate,omitempty"`
	Product                  *Product            `json:"product,omitempty"`
	YidaFormUUID             string              `json:"yidaFormUuid,omitempty"`
	CanEditPaymentInfo       bool                `json:"canEditPaymentInfo,omitempty"`
	PaymentUserIDList        []string            `json:"paymentUserIdList,omitempty"`
	YidaProcInsID            string              `json:"yidaProcInsId,omitempty"`
	SyncPaymentOrder         bool                `json:"syncPaymentOrder,omitempty"`
}

// Department represents department information.
type Department struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// Customer represents customer information.
type Customer struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// Supplier represents supplier information.
type Supplier struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// Project represents project information.
type Project struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// Category represents income/expense category information.
type Category struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// Product represents product information.
type Product struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// Company represents enterprise entity information.
type Company struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// EnterpriseAccount represents enterprise account information.
type EnterpriseAccount struct {
	EnterpriseAccountCode string `json:"enterpriseAccountCode,omitempty"`
	AccountCategory       string `json:"accountCategory"`
	AccountType           string `json:"accountType,omitempty"`
	CardNo                string `json:"cardNo,omitempty"`
	AccountName           string `json:"accountName,omitempty"`
	OfficialNumber        string `json:"officialNumber,omitempty"`
	OfficialName          string `json:"officialName,omitempty"`
	Name                  string `json:"name,omitempty"`
	Code                  string `json:"code,omitempty"`
	City                  string `json:"city,omitempty"`
	Province              string `json:"province,omitempty"`
}

// RecipientAccount represents recipient account information.
type RecipientAccount struct {
	AccountCategory string `json:"accountCategory"`
	AccountType     string `json:"accountType,omitempty"`
	CardNo          string `json:"cardNo,omitempty"`
	AccountName     string `json:"accountName,omitempty"`
}

// PaymentDetail represents a payment detail line item.
type PaymentDetail struct {
	Amount       string       `json:"amount,omitempty"`
	InvoiceInfo  *InvoiceInfo `json:"invoiceInfo,omitempty"`
	ProductCode  string       `json:"productCode,omitempty"`
	ProjectCode  string       `json:"projectCode,omitempty"`
	Remark       string       `json:"remark,omitempty"`
	PrincipalID  string       `json:"principalId,omitempty"`
	Tax          string       `json:"tax,omitempty"`
}

// InvoiceInfo represents invoice information within a payment detail.
type InvoiceInfo struct {
	InvoiceNo   string `json:"invoiceNo,omitempty"`
	InvoiceCode string `json:"invoiceCode,omitempty"`
}

// CreatePaymentOrderResponse is the response for creating a payment order.
type CreatePaymentOrderResponse struct {
	Code string `json:"code"`
}

// ============================================================
// Interface 4: 支付完成通知 (Payment Notification - Callback)
// ============================================================

// PaymentNotificationData represents the payment notification data sent by the platform.
type PaymentNotificationData struct {
	Code                 string               `json:"code"`
	InstanceID           string               `json:"instanceId"`
	CorpID               string               `json:"corpId"`
	PaymentStatus        string               `json:"paymentStatus"`
	PaymentTime          string               `json:"paymentTime"`
	UserID               string               `json:"userId"`
	FailReason           string               `json:"failReason,omitempty"`
	PayerAccountInfo     *PayerAccountInfo    `json:"payerAccountInfo,omitempty"`
	PayeeAccountInfo     *PayeeAccountInfo    `json:"payeeAccountInfo,omitempty"`
	RelatedRowNumberList []string             `json:"relatedRowNumberList,omitempty"`
	Source               string               `json:"source,omitempty"`
	Template             string               `json:"template,omitempty"`
	Amount               string               `json:"amount,omitempty"`
}

// PayerAccountInfo represents the payer's account information.
type PayerAccountInfo struct {
	BankOpenDTO           *BankOpenDTO `json:"bankOpenDTO,omitempty"`
	EnterpriseAccountCode string      `json:"enterpriseAccountCode,omitempty"`
	AccountType          string       `json:"accountType,omitempty"`
}

// PayeeAccountInfo represents the payee's account information.
type PayeeAccountInfo struct {
	BankOpenDTO *BankOpenDTO `json:"bankOpenDTO,omitempty"`
}

// BankOpenDTO represents bank account information.
type BankOpenDTO struct {
	BankCode       string `json:"bankCode,omitempty"`
	BankName       string `json:"bankName,omitempty"`
	BankBranchCode string `json:"bankBranchCode,omitempty"`
	BankBranchName string `json:"bankBranchName,omitempty"`
	AccountName    string `json:"accountName,omitempty"`
	BankCardNo     string `json:"bankCardNo,omitempty"`
	Type           string `json:"type,omitempty"`
}

// PaymentStatus constants.
const (
	PaymentStatusSuccess    = "SUCCESS"
	PaymentStatusFail       = "FAIL"
	PaymentStatusTerminate  = "TERMINATE"
	PaymentStatusWaitPay    = "WAIT_PAY"
	PaymentStatusPaying     = "PAYING"
	PaymentStatusPartSucc   = "PART_SUCCESS"
	PaymentStatusRefund     = "REFUND"
)

// Source constants for payment notifications.
const (
	SourceApproval = "approval"
	SourceOpenAPI  = "openapi"
)

// AccountType constants.
const (
	AccountTypeAlipay           = "ALIPAY"
	AccountTypeBankCard         = "BANKCARD"
	AccountTypeCorpBankCard     = "CORP_BANK_CARD"
	AccountTypePersonalBankCard = "PERSONAL_BANK_CARD"
)

// ============================================================
// Interface 5: 支付状态查询 (Payment Status Query)
// ============================================================

// PaymentStatusQueryRequest is the request for querying payment status.
type PaymentStatusQueryRequest struct {
	Code   string `json:"code"`
	UserID string `json:"userId"`
}

// PaymentStatusQueryResponse is the response for querying payment status,
// which has the same structure as PaymentNotificationData.
type PaymentStatusQueryResponse struct {
	PaymentNotificationData
}

// ============================================================
// Generic Notification (Business Notification)
// ============================================================

// NotificationRequest represents a generic notification request from the platform.
type NotificationRequest struct {
	BizType string `json:"bizType"`
	BizID   string `json:"bizId"`
	Data    string `json:"data,omitempty"`
}

// File: errors.go

package intelligence_finance

import "fmt"

// ErrorCode represents a numeric error code from the API.
type ErrorCode int

// Predefined error codes.
const (
	CodeSuccess ErrorCode = 0
)

// APIError represents an error returned by the platform API.
type APIError struct {
	Code    ErrorCode `json:"code"`
	Message string    `json:"msg,omitempty"`
}

// Error implements the error interface.
func (e *APIError) Error() string {
	return fmt.Sprintf("api error: code=%d, msg=%s", e.Code, e.Message)
}

// ErrInvalidResponse is returned when the API response cannot be parsed.
type ErrInvalidResponse struct {
	Message string
}

// Error implements the error interface.
func (e *ErrInvalidResponse) Error() string {
	return fmt.Sprintf("invalid response: %s", e.Message)
}

// ErrNetwork is returned when a network error occurs.
type ErrNetwork struct {
	Err error
}

// Error implements the error interface.
func (e *ErrNetwork) Error() string {
	return fmt.Sprintf("network error: %v", e.Err)
}

// Unwrap returns the underlying error.
func (e *ErrNetwork) Unwrap() error {
	return e.Err
}

// ErrSigning is returned when a signing error occurs.
type ErrSigning struct {
	Message string
	Err     error
}

// Error implements the error interface.
func (e *ErrSigning) Error() string {
	if e.Err != nil {
		return fmt.Sprintf("signing error: %s: %v", e.Message, e.Err)
	}
	return fmt.Sprintf("signing error: %s", e.Message)
}

// Unwrap returns the underlying error.
func (e *ErrSigning) Unwrap() error {
	return e.Err
}

// File: client.go

package intelligence_finance

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
)

// Default API paths - these can be customized after receiving the actual short codes.
const (
	defaultPathInvoiceCreate       = "/api/invoice/create"
	defaultPathInvoiceStatusQuery  = "/api/invoice/status"
	defaultPathPaymentCreate       = "/api/payment/create"
	defaultPathPaymentStatusQuery  = "/api/payment/status"
)

// Client is the intelligence finance API client.
type Client struct {
	baseURL      string
	tenantID     string
	clientID     string
	clientSecret string
	httpClient   *http.Client

	// Paths for each API endpoint (configurable after onboarding).
	pathInvoiceCreate      string
	pathInvoiceStatusQuery string
	pathPaymentCreate      string
	pathPaymentStatusQuery string
}

// ClientOption is a functional option for configuring the Client.
type ClientOption func(*Client)

// WithHTTPClient sets a custom HTTP client.
func WithHTTPClient(httpClient *http.Client) ClientOption {
	return func(c *Client) {
		c.httpClient = httpClient
	}
}

// WithInvoiceCreatePath sets a custom path for the invoice create API.
func WithInvoiceCreatePath(path string) ClientOption {
	return func(c *Client) {
		c.pathInvoiceCreate = path
	}
}

// WithInvoiceStatusQueryPath sets a custom path for the invoice status query API.
func WithInvoiceStatusQueryPath(path string) ClientOption {
	return func(c *Client) {
		c.pathInvoiceStatusQuery = path
	}
}

// WithPaymentCreatePath sets a custom path for the payment create API.
func WithPaymentCreatePath(path string) ClientOption {
	return func(c *Client) {
		c.pathPaymentCreate = path
	}
}

// WithPaymentStatusQueryPath sets a custom path for the payment status query API.
func WithPaymentStatusQueryPath(path string) ClientOption {
	return func(c *Client) {
		c.pathPaymentStatusQuery = path
	}
}

// NewClient creates a new intelligence finance API client.
//   - baseURL: the base URL of the platform API (e.g., "https://api.example.com")
//   - tenantID: the tenant ID assigned by the platform
//   - clientID: the application client ID assigned by the platform
//   - clientSecret: the application client secret assigned by the platform
func NewClient(baseURL, tenantID, clientID, clientSecret string, opts ...ClientOption) *Client {
	c := &Client{
		baseURL:                strings.TrimRight(baseURL, "/"),
		tenantID:               tenantID,
		clientID:               clientID,
		clientSecret:           clientSecret,
		httpClient:             &http.Client{},
		pathInvoiceCreate:      defaultPathInvoiceCreate,
		pathInvoiceStatusQuery: defaultPathInvoiceStatusQuery,
		pathPaymentCreate:      defaultPathPaymentCreate,
		pathPaymentStatusQuery: defaultPathPaymentStatusQuery,
	}
	for _, opt := range opts {
		opt(c)
	}
	return c
}

// doRequest sends an HTTP request with authentication headers and returns the raw response body.
func (c *Client) doRequest(ctx context.Context, path string, request interface{}) ([]byte, error) {
	bodyBytes, err := json.Marshal(request)
	if err != nil {
		return nil, &ErrSigning{Message: "marshal request body", Err: err}
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, bytes.NewReader(bodyBytes))
	if err != nil {
		return nil, &ErrNetwork{Err: fmt.Errorf("create request: %w", err)}
	}

	// Set standard headers
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("tenant-id", c.tenantID)
	req.Header.Set("client-id", c.clientID)

	// Generate and set signature headers
	timestamp := GenerateTimestamp()
	nonce, err := GenerateNonce(16)
	if err != nil {
		return nil, &ErrSigning{Message: "generate nonce", Err: err}
	}

	signature := HmacSHA256Base64(c.clientSecret, timestamp+nonce)

	req.Header.Set("x-bfl-signature-timestamp", timestamp)
	req.Header.Set("x-bfl-signature-nonce", nonce)
	req.Header.Set("x-bfl-signature", signature)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, &ErrNetwork{Err: fmt.Errorf("send request: %w", err)}
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, &ErrNetwork{Err: fmt.Errorf("read response body: %w", err)}
	}

	if resp.StatusCode != http.StatusOK {
		return nil, &ErrInvalidResponse{
			Message: fmt.Sprintf("unexpected status code %d: %s", resp.StatusCode, string(respBody)),
		}
	}

	return respBody, nil
}

// parseResponse parses the API response and extracts the data field.
func parseResponse(body []byte, target interface{}) error {
	var apiResp APIResponse
	if err := json.Unmarshal(body, &apiResp); err != nil {
		return &ErrInvalidResponse{Message: fmt.Sprintf("unmarshal response: %v", err)}
	}

	if apiResp.Code != 0 {
		return &APIError{Code: ErrorCode(apiResp.Code), Message: apiResp.Msg}
	}

	if target != nil && apiResp.Data != nil {
		// Convert map to JSON then unmarshal into target
		dataBytes, err := json.Marshal(apiResp.Data)
		if err != nil {
			return &ErrInvalidResponse{Message: fmt.Sprintf("marshal data: %v", err)}
		}
		if err := json.Unmarshal(dataBytes, target); err != nil {
			return &ErrInvalidResponse{Message: fmt.Sprintf("unmarshal data: %v", err)}
		}
	}

	return nil
}

// parseResponseRaw parses the API response and returns the raw data field.
func parseResponseRaw(body []byte) (map[string]interface{}, error) {
	var apiResp APIResponse
	if err := json.Unmarshal(body, &apiResp); err != nil {
		return nil, &ErrInvalidResponse{Message: fmt.Sprintf("unmarshal response: %v", err)}
	}

	if apiResp.Code != 0 {
		return nil, &APIError{Code: ErrorCode(apiResp.Code), Message: apiResp.Msg}
	}

	return apiResp.Data, nil
}

// ============================================================
// Interface 1: CreateInvoice - 订单开票接口
// ============================================================

// CreateInvoice submits an invoice creation request for an order.
// It returns the invoice creation response containing status and invoice data.
func (c *Client) CreateInvoice(ctx context.Context, req *InvoiceCreateRequest) (*InvoiceCreateResponse, error) {
	body, err := c.doRequest(ctx, c.pathInvoiceCreate, req)
	if err != nil {
		return nil, err
	}

	var result InvoiceCreateResponse
	if err := parseResponse(body, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// ============================================================
// Interface 2: QueryInvoiceStatus - 开票状态查询接口
// ============================================================

// QueryInvoiceStatus queries the invoice status for a given order.
// It returns the invoice status response containing the current status and invoice data.
func (c *Client) QueryInvoiceStatus(ctx context.Context, req *InvoiceStatusQueryRequest) (*InvoiceStatusQueryResponse, error) {
	body, err := c.doRequest(ctx, c.pathInvoiceStatusQuery, req)
	if err != nil {
		return nil, err
	}

	var result InvoiceStatusQueryResponse
	if err := parseResponse(body, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// ============================================================
// Interface 3: CreatePaymentOrder - 创建付款单据接口
// ============================================================

// CreatePaymentOrder creates a payment order.
// It returns the response containing the unique payment order code.
func (c *Client) CreatePaymentOrder(ctx context.Context, req *CreatePaymentOrderRequest) (*CreatePaymentOrderResponse, error) {
	body, err := c.doRequest(ctx, c.pathPaymentCreate, req)
	if err != nil {
		return nil, err
	}

	var result CreatePaymentOrderResponse
	if err := parseResponse(body, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// ============================================================
// Interface 4: Payment Notification Handler (平台回调客户接口)
// ============================================================

// ParsePaymentNotification parses a payment notification request body
// sent by the platform. This is used when the platform calls back to
// the customer's system after a payment is completed.
//
// Usage example in an HTTP handler:
//
//	func handler(w http.ResponseWriter, r *http.Request) {
//	    body, _ := io.ReadAll(r.Body)
//	    notification, err := ParsePaymentNotification(body)
//	    if err != nil {
//	        http.Error(w, "FAILED", http.StatusBadRequest)
//	        return
//	    }
//	    // Process the notification...
//	    w.Write([]byte("SUCCESS"))
//	}
func ParsePaymentNotification(body []byte) (*PaymentNotificationData, error) {
	var notification PaymentNotificationData
	if err := json.Unmarshal(body, &notification); err != nil {
		return nil, &ErrInvalidResponse{Message: fmt.Sprintf("parse payment notification: %v", err)}
	}
	return &notification, nil
}

// ParseGenericNotification parses a generic notification request body
// sent by the platform. The generic notification contains bizType, bizId, and data.
func ParseGenericNotification(body []byte) (*NotificationRequest, error) {
	var notification NotificationRequest
	if err := json.Unmarshal(body, &notification); err != nil {
		return nil, &ErrInvalidResponse{Message: fmt.Sprintf("parse notification: %v", err)}
	}
	return &notification, nil
}

// ============================================================
// Interface 5: QueryPaymentStatus - 支付状态查询接口
// ============================================================

// QueryPaymentStatus queries the payment status for a given payment order.
// The response has the same structure as PaymentNotificationData.
func (c *Client) QueryPaymentStatus(ctx context.Context, req *PaymentStatusQueryRequest) (*PaymentStatusQueryResponse, error) {
	body, err := c.doRequest(ctx, c.pathPaymentStatusQuery, req)
	if err != nil {
		return nil, err
	}

	var result PaymentStatusQueryResponse
	if err := parseResponse(body, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// File: example_test.go

package intelligence_finance_test

import (
	"context"
	"fmt"
	"log"

	"intelligence_finance"
)

// ExampleClient_CreateInvoice demonstrates how to create an invoice.
func ExampleClient_CreateInvoice() {
	// Create a new client
	client := intelligence_finance.NewClient(
		"https://api.example.com",
		"your-tenant-id",
		"your-client-id",
		"your-client-secret",
	)

	ctx := context.Background()

	// Build the invoice create request
	req := &intelligence_finance.InvoiceCreateRequest{
		OrderID:     "ORD-2024-001",
		InvoiceType: 9, // 数电普票
		Purchaser:   "某某科技有限公司",
		TaxNum:      "91110108MA01XXXXX",
		Phone:       "13800138000",
		Email:       "finance@example.com",
		ApplyPerson: "张三",
		Payee:       "李四",
		Reviewer:    "王五",
		Products: []intelligence_finance.InvoiceProduct{
			{
				ProductName:      "云服务器ECS",
				RevenueCode:      "1010101010101010101",
				AmountIncludeTax: 1180.00,
				Unit:             "台",
				Quantity:         1,
				TaxRate:          0.06,
				TaxSign:          1,
			},
		},
	}

	resp, err := client.CreateInvoice(ctx, req)
	if err != nil {
		log.Fatalf("CreateInvoice failed: %v", err)
	}

	fmt.Printf("Invoice status: %d\n", resp.Status)
	for _, inv := range resp.DataList {
		fmt.Printf("Invoice No: %s, Amount: %s\n", inv.InvoiceNo, inv.TotalIncludeTax)
	}
}

// ExampleClient_QueryInvoiceStatus demonstrates how to query invoice status.
func ExampleClient_QueryInvoiceStatus() {
	client := intelligence_finance.NewClient(
		"https://api.example.com",
		"your-tenant-id",
		"your-client-id",
		"your-client-secret",
	)

	ctx := context.Background()

	req := &intelligence_finance.InvoiceStatusQueryRequest{
		OrderID: "ORD-2024-001",
	}

	resp, err := client.QueryInvoiceStatus(ctx, req)
	if err != nil {
		log.Fatalf("QueryInvoiceStatus failed: %v", err)
	}

	fmt.Printf("Status: %d, Message: %s\n", resp.Status, resp.Message)
	for _, inv := range resp.Data {
		fmt.Printf("Invoice No: %s\n", inv.InvoiceNo)
	}
}

// ExampleClient_CreatePaymentOrder demonstrates how to create a payment order.
func ExampleClient_CreatePaymentOrder() {
	client := intelligence_finance.NewClient(
		"https://api.example.com",
		"your-tenant-id",
		"your-client-id",
		"your-client-secret",
	)

	ctx := context.Background()

	req := &intelligence_finance.CreatePaymentOrderRequest{
		Code:   "PAY-2024-001",
		UserID: "user-001",
		Title:  "采购付款-云服务器",
		Amount: "1180.00",
		Department: &intelligence_finance.Department{
			Name: "技术部",
		},
		Supplier: &intelligence_finance.Supplier{
			Name: "某某云服务商",
		},
		PaymentDetailList: []intelligence_finance.PaymentDetail{
			{
				Amount: "1180.00",
			},
		},
	}

	resp, err := client.CreatePaymentOrder(ctx, req)
	if err != nil {
		log.Fatalf("CreatePaymentOrder failed: %v", err)
	}

	fmt.Printf("Payment order code: %s\n", resp.Code)
}

// ExampleClient_QueryPaymentStatus demonstrates how to query payment status.
func ExampleClient_QueryPaymentStatus() {
	client := intelligence_finance.NewClient(
		"https://api.example.com",
		"your-tenant-id",
		"your-client-id",
		"your-client-secret",
	)

	ctx := context.Background()

	req := &intelligence_finance.PaymentStatusQueryRequest{
		Code:   "PAY-2024-001",
		UserID: "user-001",
	}

	resp, err := client.QueryPaymentStatus(ctx, req)
	if err != nil {
		log.Fatalf("QueryPaymentStatus failed: %v", err)
	}

	fmt.Printf("Payment status: %s\n", resp.PaymentStatus)
	fmt.Printf("Payment time: %s\n", resp.PaymentTime)
}

// ExampleParsePaymentNotification demonstrates how to handle a payment notification
// callback from the platform.
func ExampleParsePaymentNotification() {
	// This is the body received from the platform's callback
	body := []byte(`{
		"code": "PAY-2024-001",
		"instanceId": "inst-001",
		"corpId": "corp-123",
		"paymentStatus": "SUCCESS",
		"paymentTime": "2024-01-15 10:30:00",
		"userId": "user-001",
		"amount": "1180.00"
	}`)

	notification, err := intelligence_finance.ParsePaymentNotification(body)
	if err != nil {
		log.Fatalf("ParsePaymentNotification failed: %v", err)
	}

	fmt.Printf("Payment code: %s, Status: %s\n", notification.Code, notification.PaymentStatus)

	// In a real HTTP handler, respond with "SUCCESS" or "FAILED"
	// w.Write([]byte("SUCCESS"))
}

// ExampleClient_WithCustomOptions demonstrates how to create a client with custom options.
func ExampleClient_WithCustomOptions() {
	client := intelligence_finance.NewClient(
		"https://api.example.com",
		"your-tenant-id",
		"your-client-id",
		"your-client-secret",
		intelligence_finance.WithInvoiceCreatePath("/custom/path/invoice/create"),
		intelligence_finance.WithInvoiceStatusQueryPath("/custom/path/invoice/status"),
		intelligence_finance.WithPaymentCreatePath("/custom/path/payment/create"),
		intelligence_finance.WithPaymentStatusQueryPath("/custom/path/payment/status"),
	)

	_ = client // use the client as needed
}