intelligence_finance-202608.../intelligence_finance/generate.md

57 KiB

Now I have the necessary encryption utilities. Let me also need the HmacSHA256 implementation. Since the document specifically mentions HmacSHA256, I'll implement it directly in crypto.go using Go's standard library.

Let me now generate all 6 files:

// File: intelligence_finance/go.mod

module intelligence_finance

go 1.21

// File: intelligence_finance/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: intelligence_finance/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: intelligence_finance/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: intelligence_finance/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: intelligence_finance/example_test.go

package intelligence_finance_test

import (
	"context"
	"fmt"
	"log"

	intelligence_finance "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
}

Wait, I need to fix the import in example_test.go - the package import should use the module name.

Let me reconsider the structure. The example_test.go is in package intelligence_finance_test and imports intelligence_finance as an external package. But the module name is intelligence_finance, so the import path would be intelligence_finance. However, in Go, if the test file is in the same directory, the import path should use the module path. Since the module is intelligence_finance, the import would be intelligence_finance.

Actually, wait - the example_test.go is in the same directory as the other files, but uses a different package name (intelligence_finance_test). For external test packages, the import path is the module path. If the module is intelligence_finance, the import would be "intelligence_finance".

But actually, in Go, when you have a module intelligence_finance and files in the root directory, the import path should be the module path. Let me keep it as "intelligence_finance".

Let me now output all the files properly.

Actually, looking at the import more carefully:

import (
    intelligence_finance "intelligence_finance"
)

This won't work because the alias is the same as the package name. Let me just use:

import "intelligence_finance"

But wait, the example_test.go file is in the same directory as the package, but uses package intelligence_finance_test. This is an external test package, so it needs to import the package. The import path would be intelligence_finance (the module name).

Let me fix this.

Also, I need to reconsider the parseResponse function. The APIResponse has Data as map[string]interface{}, but the actual responses might be different structures. Let me think about this...

Actually, looking at the API responses more carefully:

  1. Invoice Create Response: { "status": 1, "errorMsg": "...", "dataList": [...] }

    • This is the data field of the general response
  2. Invoice Status Query Response: { "status": 3, "message": "成功", "data": [...] }

    • This is the data field of the general response
  3. Payment Create Response: { "code": "单据唯一标识" }

    • This is the data field of the general response
  4. Payment Status Query Response: Same as payment notification data

    • This is the data field of the general response

So the general response is:

{
  "code": 0,
  "msg": "success",
  "data": { ... specific response fields ... }
}

And the specific response fields are what I defined as the response types.

So the parseResponse function should work - it unmarshals the general response, checks the code, then marshals the data back to JSON and unmarshals into the target type.

Wait, but the APIResponse.Data is map[string]interface{}. If the data is actually an array (like dataList), then this won't work. Let me change the Data field to json.RawMessage to handle any JSON value.

Let me fix this.

Actually, looking at the document again:

  • Invoice Create: the response has dataList which is a list, but it's a field inside the data object, not the data itself
  • Invoice Status Query: the response has data which is a list of invoice data

Wait, let me re-read:

Invoice Create Response:

  • status: Integer
  • errorMsg: String
  • dataList: List