diff --git a/intelligence_finance/valid.md b/intelligence_finance/valid.md new file mode 100644 index 0000000..6c1616e --- /dev/null +++ b/intelligence_finance/valid.md @@ -0,0 +1,1007 @@ +// File: go.mod +``` +module intelligence_finance + +go 1.21 +``` + +// File: crypto.go +```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 +```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 +```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 +```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, ¬ification); err != nil { + return nil, &ErrInvalidResponse{Message: fmt.Sprintf("parse payment notification: %v", err)} + } + return ¬ification, 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, ¬ification); err != nil { + return nil, &ErrInvalidResponse{Message: fmt.Sprintf("parse notification: %v", err)} + } + return ¬ification, 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 +```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 +} +``` \ No newline at end of file