intelligence_finance_v1_ga-.../intelligence_finance_v1_ga/valid.md

44 KiB
Raw Permalink Blame History

// File: intelligence_finance_v1_ga/go.mod

module intelligence_finance_v1_ga

go 1.21

// File: intelligence_finance_v1_ga/errors.go

package intelligence_finance_v1_ga

import "fmt"

// APIError 表示 HTTP 层面的错误(非 200 响应)。
type APIError struct {
	// StatusCode HTTP 状态码
	StatusCode int
	// Body 响应体原始内容
	Body string
}

// Error 实现 error 接口。
func (e *APIError) Error() string {
	return fmt.Sprintf("api error: status=%d body=%s", e.StatusCode, e.Body)
}

// BusinessError 表示业务层面的错误(通用响应结构中 code 非成功值)。
type BusinessError struct {
	// Code 业务错误码
	Code int
	// Msg 业务错误描述
	Msg string
}

// Error 实现 error 接口。
func (e *BusinessError) Error() string {
	return fmt.Sprintf("business error: code=%d msg=%s", e.Code, e.Msg)
}

// NotificationError 表示通知处理失败。
type NotificationError struct {
	// Reason 失败原因
	Reason string
}

// Error 实现 error 接口。
func (e *NotificationError) Error() string {
	return fmt.Sprintf("notification error: %s", e.Reason)
}

// File: intelligence_finance_v1_ga/crypto.go

package intelligence_finance_v1_ga

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

// GenerateTimestamp 生成秒级时间戳。
func GenerateTimestamp() string {
	return fmt.Sprintf("%d", time.Now().Unix())
}

// GenerateTimestampMillis 生成毫秒级时间戳。
func GenerateTimestampMillis() string {
	return fmt.Sprintf("%d", time.Now().UnixMilli())
}

// GenerateNonce 生成指定长度的随机字符串(加密安全)。
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 "", err
		}
		b[i] = charset[num.Int64()]
	}
	return string(b), nil
}

// HmacSHA256Base64 使用 HmacSHA256 算法对 data 进行签名,密钥为 key结果 Base64 编码。
// 对应文档中客户应用签名算法HmacSHA256密钥为 client-secret
// 签名数据为 x-bfl-signature-timestamp + x-bfl-signature-nonce。
func HmacSHA256Base64(key, data string) string {
	h := hmac.New(sha256.New, []byte(key))
	h.Write([]byte(data))
	return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

// BuildSignString 方式1字典序排序拼接排除空值与签名字段本身。
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 方式2固定顺序拼接排除空值。
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_v1_ga/types.go

package intelligence_finance_v1_ga

import "encoding/json"

// ==================== 通用 ====================

// CommonResponse 通用响应结构。
type CommonResponse struct {
	Code int             `json:"code"`
	Msg  string          `json:"msg"`
	Data json.RawMessage `json:"data"`
}

// ==================== 接口1订单开票 ====================

// InvoiceApplyRequest 提交订单开票申请请求。
type InvoiceApplyRequest struct {
	// CompanyCode 开票的企业主体编码,不传则默认主体开票
	CompanyCode string `json:"companyCode,omitempty"`
	// OrderID 订单唯一标识(需保证在贵方系统内唯一)
	OrderID string `json:"orderId"`
	// InvoiceType 发票类型枚举1-专用发票2-普通发票3-普通发票(电子)4-专用发票(电子)8-数电专票9-数电普票
	InvoiceType int `json:"invoiceType"`
	// Products 货物/服务明细列表,至少一项
	Products []ProductItem `json:"products"`
	// Remark 订单备注(非发票备注)
	Remark string `json:"remark,omitempty"`
	// Purchaser 购方企业名称
	Purchaser string `json:"purchaser"`
	// TaxNum 购方纳税人识别号
	TaxNum string `json:"taxnum,omitempty"`
	// PurchaserAddress 购方地址
	PurchaserAddress string `json:"purchaserAddress,omitempty"`
	// PurchaserTel 购方电话
	PurchaserTel string `json:"purchaserTel,omitempty"`
	// BankName 购方开户行名称
	BankName string `json:"bankName,omitempty"`
	// BankAccount 购方银行账号
	BankAccount string `json:"bankAccount,omitempty"`
	// Phone 收票人手机号(用于接收电票短信)
	Phone string `json:"phone,omitempty"`
	// Email 收票人邮箱(用于接收电票邮件)
	Email string `json:"email,omitempty"`
	// ApplyPerson 开票申请人名称
	ApplyPerson string `json:"applyPerson,omitempty"`
	// Payee 收款人(发票票面)
	Payee string `json:"payee,omitempty"`
	// Reviewer 复核人(发票票面)
	Reviewer string `json:"reviewer,omitempty"`
	// InvoiceRemark 发票备注栏内容
	InvoiceRemark string `json:"invoiceRemark,omitempty"`
	// NaturalPerson 购买方自然人标识Y-是N-否默认N数电票可选传
	NaturalPerson string `json:"naturalPerson,omitempty"`
	// AdditionInfo 附加信息JSON数组字符串
	AdditionInfo string `json:"additionInfo,omitempty"`
}

// ProductItem 货物/服务明细项。
type ProductItem struct {
	// ProductName 货物或服务名称
	ProductName string `json:"productName"`
	// RevenueCode 19位税收分类编码
	RevenueCode string `json:"revenueCode"`
	// AmountIncludeTax 单条明细含税总金额(单位:元)
	AmountIncludeTax float64 `json:"amountIncludeTax"`
	// Specs 规格型号
	Specs string `json:"specs,omitempty"`
	// Unit 计量单位(如:台、个、次)
	Unit string `json:"unit,omitempty"`
	// Quantity 数量
	Quantity float64 `json:"quantity"`
	// Discount 折扣金额无折扣传0
	Discount float64 `json:"discount,omitempty"`
	// TaxSign 是否含税0-不含税1-含税默认建议传1
	TaxSign int `json:"taxSign,omitempty"`
	// TaxRate 税率小数形式如0.13表示13%
	TaxRate float64 `json:"taxRate,omitempty"`
}

// InvoiceApplyResponse 提交订单开票申请响应。
type InvoiceApplyResponse struct {
	// Status 开票状态0-未开票1-开票中2-部分失败3-开票成功4-开票失败5-部分未开6-未配置数电账号7-未配置自动开票配置
	Status int `json:"status"`
	// ErrorMsg 错误信息(开票失败时返回原因)
	ErrorMsg string `json:"errorMsg,omitempty"`
	// DataList 发票数据列表(一张订单可能对应多张发票)
	DataList []InvoiceData `json:"dataList"`
}

// InvoiceData 发票数据。
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"`
	PurchaserBankAccount 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"`
	SellerBankAccount    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 发票商品明细。
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"`
}

// ==================== 接口2开票状态查询 ====================

// InvoiceStatusQueryRequest 查询订单开票状态请求。
type InvoiceStatusQueryRequest struct {
	// OrderID 订单唯一标识(需保证在贵方系统内唯一)
	OrderID string `json:"orderId"`
}

// InvoiceStatusQueryResponse 查询订单开票状态响应。
type InvoiceStatusQueryResponse struct {
	// Status 开票状态0-未开票3-成功4-失败6-未配置数电账号7-未配置自动开票)
	Status int `json:"status"`
	// Message 状态描述(如"开票成功"
	Message string `json:"message"`
	// Data 发票详细列表结构与回调中的data字段一致
	Data []InvoiceData `json:"data"`
}

// ==================== 接口3创建付款单据 ====================

// CreatePaymentRequest 创建付款单据请求。
type CreatePaymentRequest 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"`
	PaymentDetailListJSON  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 部门信息。
type Department struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// Customer 客户信息。
type Customer struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// Supplier 供应商信息。
type Supplier struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// Project 项目信息。
type Project struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// Category 收支类别信息。
type Category struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// Product 商品信息。
type Product struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// Company 企业主体信息。
type Company struct {
	Code string `json:"code,omitempty"`
	Name string `json:"name"`
}

// EnterpriseAccount 企业账号信息。
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 收款账户信息。
type RecipientAccount struct {
	AccountCategory string `json:"accountCategory"`
	AccountType     string `json:"accountType,omitempty"`
	CardNo          string `json:"cardNo,omitempty"`
	AccountName     string `json:"accountName,omitempty"`
}

// PaymentDetail 付款明细。
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 付款明细发票信息。
type InvoiceInfo struct {
	InvoiceNo   string `json:"invoiceNo,omitempty"`
	InvoiceCode string `json:"invoiceCode,omitempty"`
}

// CreatePaymentResponse 创建付款单据响应。
type CreatePaymentResponse struct {
	// Code 单据唯一编码
	Code string `json:"code"`
}

// ==================== 接口4支付完成通知 ====================

// PaymentNotifyData 支付完成通知数据。
type PaymentNotifyData 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 付款账户信息。
type PayerAccountInfo struct {
	BankOpenDTO          *BankOpenDTO `json:"bankOpenDTO,omitempty"`
	EnterpriseAccountCode string      `json:"enterpriseAccountCode,omitempty"`
	AccountType          string       `json:"accountType,omitempty"`
}

// PayeeAccountInfo 收款账户信息。
type PayeeAccountInfo struct {
	BankOpenDTO *BankOpenDTO `json:"bankOpenDTO,omitempty"`
}

// BankOpenDTO 银行信息。
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"`
}

// 支付状态枚举。
const (
	PaymentStatusSuccess     = "SUCCESS"     // 支付成功
	PaymentStatusFail        = "FAIL"        // 支付失败
	PaymentStatusTerminate   = "TERMINATE"   // 支付取消
	PaymentStatusWaitPay     = "WAIT_PAY"    // 待支付
	PaymentStatusPaying      = "PAYING"      // 支付中
	PaymentStatusPartSuccess = "PART_SUCCESS" // 部分支付成功
	PaymentStatusRefund      = "REFUND"      // 退款
)

// 来源枚举。
const (
	SourceApproval = "approval" // 审批单
	SourceOpenAPI  = "openapi"  // 开发接口
)

// 账户类型枚举。
const (
	AccountTypeAlipay           = "ALIPAY"            // 支付宝
	AccountTypeBankCard         = "BANKCARD"          // 银行卡
	AccountTypeCorpBankCard     = "CORP_BANK_CARD"    // 对公银行卡
	AccountTypePersonalBankCard = "PERSONAL_BANK_CARD" // 对私银行卡
)

// ==================== 接口5支付状态查询 ====================

// PaymentStatusQueryRequest 查询支付状态请求。
type PaymentStatusQueryRequest struct {
	Code   string `json:"code"`
	UserID string `json:"userId"`
}

// ==================== 通用通知机制 ====================

// Notification 通用通知数据。
type Notification struct {
	BizType string `json:"bizType"`
	BizID   string `json:"bizId"`
	Data    string `json:"data,omitempty"`
}

// 通知响应常量。
const (
	NotifyResultSuccess = "SUCCESS" // 成功
	NotifyResultFailed  = "FAILED"  // 失败
)

// ==================== 接口6创建供应商 ====================

// CreateSupplierRequest 创建供应商请求。
type CreateSupplierRequest struct {
	InvokeID string `json:"invokeId,omitempty"`
	UserID   string `json:"userId"`
	Data     string `json:"data,omitempty"`
	BizType  string `json:"bizType"`
}

// SupplierBizData 供应商业务数据。
type SupplierBizData struct {
	CorpID       string        `json:"corpId"`
	Creator      string        `json:"creator"`
	SupplierInfo *SupplierInfo `json:"supplierInfo,omitempty"`
}

// SupplierInfo 供应商数据。
type SupplierInfo struct {
	Name                   string `json:"name"`
	CorpID                 string `json:"corpId"`
	UserDefineCode         string `json:"userDefineCode,omitempty"`
	Description            string `json:"description,omitempty"`
	ContactAddress         string `json:"contactAddress,omitempty"`
	ContactCompanyTelephone string `json:"contactCompanyTelephone,omitempty"`
	ContactEmail           string `json:"contactEmail,omitempty"`
	ContactName            string `json:"contactName,omitempty"`
	ContactTelephone       string `json:"contactTelephone,omitempty"`
	Creator                string `json:"creator"`
	InvoiceAccount         string `json:"invoiceAccount,omitempty"`
	InvoiceAddress         string `json:"invoiceAddress,omitempty"`
	InvoiceBankName        string `json:"invoiceBankName,omitempty"`
	InvoiceName            string `json:"invoiceName,omitempty"`
	InvoiceTaxNo           string `json:"invoiceTaxNo,omitempty"`
	PurchaserTel           string `json:"purchaserTel,omitempty"`
	BankName               string `json:"bankName,omitempty"`
	InvoiceTelephone       string `json:"invoiceTelephone,omitempty"`
	AccountName            string `json:"accountName,omitempty"`
	AccountType            string `json:"accountType,omitempty"`
}

// CreateSupplierResponse 创建供应商响应。
type CreateSupplierResponse struct {
	InvokeID string `json:"invokeId,omitempty"`
	BizType  string `json:"bizType"`
	Data     string `json:"data,omitempty"`
}

// SupplierResponseData 供应商响应数据。
type SupplierResponseData struct {
	Result  string `json:"result,omitempty"`
	Success bool   `json:"success"`
}

// SupplierResult 供应商信息(响应)。
type SupplierResult struct {
	SupplierID             string `json:"supplierId"`
	SupplierName           string `json:"supplierName"`
	Name                   string `json:"name"`
	CorpID                 string `json:"corpId"`
	UserDefineCode         string `json:"userDefineCode,omitempty"`
	Description            string `json:"description,omitempty"`
	ContactAddress         string `json:"contactAddress,omitempty"`
	ContactCompanyTelephone string `json:"contactCompanyTelephone,omitempty"`
	ContactEmail           string `json:"contactEmail,omitempty"`
	ContactName            string `json:"contactName,omitempty"`
	ContactTelephone       string `json:"contactTelephone,omitempty"`
	Creator                string `json:"creator"`
	InvoiceAccount         string `json:"invoiceAccount,omitempty"`
	InvoiceAddress         string `json:"invoiceAddress,omitempty"`
	InvoiceBankName        string `json:"invoiceBankName,omitempty"`
	InvoiceName            string `json:"invoiceName,omitempty"`
	InvoiceTaxNo           string `json:"invoiceTaxNo,omitempty"`
	CreateTime             int64  `json:"createTime,omitempty"`
	ModifiedTime           int64  `json:"modifiedTime,omitempty"`
	Code                   string `json:"code,omitempty"`
	ID                     int64  `json:"id,omitempty"`
	Status                 string `json:"status,omitempty"`
}

// ==================== 接口7根据名称查询供应商 ====================

// QuerySupplierByNameRequest 根据名称查询供应商请求。
type QuerySupplierByNameRequest struct {
	InvokeID string `json:"invokeId,omitempty"`
	UserID   string `json:"userId"`
	Data     string `json:"data,omitempty"`
	BizType  string `json:"bizType"`
}

// QueryByName 按名称查询业务数据。
type QueryByName struct {
	CorpID string `json:"corpId"`
	Name   string `json:"name"`
}

// QuerySupplierByNameResponse 根据名称查询供应商响应。
type QuerySupplierByNameResponse struct {
	InvokeID string `json:"invokeId,omitempty"`
	BizType  string `json:"bizType"`
	Data     string `json:"data,omitempty"`
	Result   string `json:"result,omitempty"`
	Success  bool   `json:"success"`
}

// ==================== 接口8更新供应商 ====================

// UpdateSupplierRequest 更新供应商请求。
type UpdateSupplierRequest struct {
	InvokeID string `json:"invokeId,omitempty"`
	UserID   string `json:"userId"`
	Data     string `json:"data,omitempty"`
	BizType  string `json:"bizType"`
}

// UpdateSupplierBizData 更新供应商业务数据。
type UpdateSupplierBizData struct {
	SupplierID             string `json:"supplierId"`
	Name                   string `json:"name"`
	CorpID                 string `json:"corpId"`
	UserDefineCode         string `json:"userDefineCode,omitempty"`
	Description            string `json:"description,omitempty"`
	ContactAddress         string `json:"contactAddress,omitempty"`
	ContactCompanyTelephone string `json:"contactCompanyTelephone,omitempty"`
	ContactEmail           string `json:"contactEmail,omitempty"`
	ContactName            string `json:"contactName,omitempty"`
	ContactTelephone       string `json:"contactTelephone,omitempty"`
	UserID                 string `json:"userId"`
	InvoiceAccount         string `json:"invoiceAccount,omitempty"`
	InvoiceAddress         string `json:"invoiceAddress,omitempty"`
	InvoiceBankName        string `json:"invoiceBankName,omitempty"`
	InvoiceName            string `json:"invoiceName,omitempty"`
	InvoiceTaxNo           string `json:"invoiceTaxNo,omitempty"`
	PurchaserTel           string `json:"purchaserTel,omitempty"`
	BankName               string `json:"bankName,omitempty"`
	InvoiceTelephone       string `json:"invoiceTelephone,omitempty"`
	AccountName            string `json:"accountName,omitempty"`
	AccountType            string `json:"accountType,omitempty"`
	BankBranchCode         string `json:"bankBranchCode,omitempty"`
	BankCode               string `json:"bankCode,omitempty"`
	BankCity               string `json:"bankCity,omitempty"`
	BankProvince           string `json:"bankProvince,omitempty"`
}

// UpdateSupplierResponse 更新供应商响应。
type UpdateSupplierResponse struct {
	InvokeID string `json:"invokeId,omitempty"`
	BizType  string `json:"bizType"`
	Data     string `json:"data,omitempty"`
}

// ==================== 接口9根据用户自定义编码查询供应商 ====================

// QuerySupplierByUserDefineCodeRequest 根据用户自定义编码查询供应商请求。
type QuerySupplierByUserDefineCodeRequest struct {
	InvokeID string `json:"invokeId,omitempty"`
	UserID   string `json:"userId"`
	Data     string `json:"data,omitempty"`
	BizType  string `json:"bizType"`
}

// QueryByUserDefineCode 按用户自定义编码查询业务数据。
type QueryByUserDefineCode struct {
	CorpID         string `json:"corpId"`
	UserDefineCode string `json:"userDefineCode"`
}

// QuerySupplierByUserDefineCodeResponse 根据用户自定义编码查询供应商响应。
type QuerySupplierByUserDefineCodeResponse struct {
	InvokeID string `json:"invokeId,omitempty"`
	BizType  string `json:"bizType"`
	Data     string `json:"data,omitempty"`
}

// File: intelligence_finance_v1_ga/client.go

package intelligence_finance_v1_ga

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

// 供应商业务类型常量。
const (
	BizTypeCreateSupplier              = "create_supplier"
	BizTypeQuerySupplierByName         = "query_supplier_by_name"
	BizTypeUpdateSupplier              = "update_supplier"
	BizTypeQuerySupplierByUserDefine   = "query_supplier_by_userDefineCode"
)

// Client 业财连接接口客户端。
type Client struct {
	baseURL      string
	tenantID     string
	clientID     string
	clientSecret string
	signEnabled  bool
	httpClient   *http.Client

	// 各接口路径(可通过 Option 覆盖)
	pathSubmitInvoiceApply            string
	pathQueryInvoiceStatus            string
	pathCreatePayment                 string
	pathQueryPaymentStatus            string
	pathCreateSupplier                string
	pathQuerySupplierByName           string
	pathUpdateSupplier                string
	pathQuerySupplierByUserDefineCode string
}

// Option 客户端配置项。
type Option func(*Client)

// WithHTTPClient 自定义 http.Client。
func WithHTTPClient(c *http.Client) Option {
	return func(cli *Client) { cli.httpClient = c }
}

// WithSignEnabled 是否启用请求签名钉钉AI表格场景可关闭签名由AI表格自动完成func WithSignEnabled(enabled bool) Option {
	return func(cli *Client) { cli.signEnabled = enabled }
}

// WithPathSubmitInvoiceApply 设置提交订单开票申请接口路径。
func WithPathSubmitInvoiceApply(path string) Option {
	return func(cli *Client) { cli.pathSubmitInvoiceApply = path }
}

// WithPathQueryInvoiceStatus 设置开票状态查询接口路径。
func WithPathQueryInvoiceStatus(path string) Option {
	return func(cli *Client) { cli.pathQueryInvoiceStatus = path }
}

// WithPathCreatePayment 设置创建付款单据接口路径。
func WithPathCreatePayment(path string) Option {
	return func(cli *Client) { cli.pathCreatePayment = path }
}

// WithPathQueryPaymentStatus 设置支付状态查询接口路径。
func WithPathQueryPaymentStatus(path string) Option {
	return func(cli *Client) { cli.pathQueryPaymentStatus = path }
}

// WithPathCreateSupplier 设置创建供应商接口路径。
func WithPathCreateSupplier(path string) Option {
	return func(cli *Client) { cli.pathCreateSupplier = path }
}

// WithPathQuerySupplierByName 设置根据名称查询供应商接口路径。
func WithPathQuerySupplierByName(path string) Option {
	return func(cli *Client) { cli.pathQuerySupplierByName = path }
}

// WithPathUpdateSupplier 设置更新供应商接口路径。
func WithPathUpdateSupplier(path string) Option {
	return func(cli *Client) { cli.pathUpdateSupplier = path }
}

// WithPathQuerySupplierByUserDefineCode 设置根据用户自定义编码查询供应商接口路径。
func WithPathQuerySupplierByUserDefineCode(path string) Option {
	return func(cli *Client) { cli.pathQuerySupplierByUserDefineCode = path }
}

// NewClient 创建业财连接接口客户端。
//
// baseURL 为平台接口根地址tenantID 为平台分配的租户唯一标识;
// clientID 为平台分配的应用标识钉钉AI表格固定为 "dd-ai-table"
// clientSecret 为平台分配的密钥,用于请求签名。
func NewClient(baseURL, tenantID, clientID, clientSecret string, opts ...Option) *Client {
	c := &Client{
		baseURL:      strings.TrimRight(baseURL, "/"),
		tenantID:     tenantID,
		clientID:     clientID,
		clientSecret: clientSecret,
		signEnabled:  true,
		httpClient:   &http.Client{Timeout: 30 * time.Second},

		pathSubmitInvoiceApply:            "/invoice/apply",
		pathQueryInvoiceStatus:            "/invoice/status",
		pathCreatePayment:                 "/payment/create",
		pathQueryPaymentStatus:            "/payment/status",
		pathCreateSupplier:                "/supplier/create",
		pathQuerySupplierByName:           "/supplier/queryByName",
		pathUpdateSupplier:                "/supplier/update",
		pathQuerySupplierByUserDefineCode: "/supplier/queryByUserDefineCode",
	}
	for _, opt := range opts {
		opt(c)
	}
	return c
}

// doRequest 发送 POST 请求并解析响应。
func (c *Client) doRequest(ctx context.Context, path string, reqBody, respBody interface{}) error {
	body, err := json.Marshal(reqBody)
	if err != nil {
		return fmt.Errorf("marshal request: %w", err)
	}

	httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("new request: %w", err)
	}

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

	if c.signEnabled {
		timestamp := GenerateTimestamp()
		nonce, err := GenerateNonce(32)
		if err != nil {
			return fmt.Errorf("generate nonce: %w", err)
		}
		sign := HmacSHA256Base64(c.clientSecret, timestamp+nonce)
		httpReq.Header.Set("x-bfl-signature-timestamp", timestamp)
		httpReq.Header.Set("x-bfl-signature-nonce", nonce)
		httpReq.Header.Set("x-bfl-signature", sign)
	}

	httpResp, err := c.httpClient.Do(httpReq)
	if err != nil {
		return fmt.Errorf("do request: %w", err)
	}
	defer httpResp.Body.Close()

	respData, err := io.ReadAll(httpResp.Body)
	if err != nil {
		return fmt.Errorf("read response: %w", err)
	}

	if httpResp.StatusCode != http.StatusOK {
		return &APIError{StatusCode: httpResp.StatusCode, Body: string(respData)}
	}

	if err := json.Unmarshal(respData, respBody); err != nil {
		return fmt.Errorf("unmarshal response: %w", err)
	}
	return nil
}

// checkBusinessCode 校验通用响应结构中的业务 code。
func checkBusinessCode(code int, msg string) error {
	if code != 0 && code != 200 {
		return &BusinessError{Code: code, Msg: msg}
	}
	return nil
}

// SubmitInvoiceApply 提交订单开票申请接口1func (c *Client) SubmitInvoiceApply(ctx context.Context, req *InvoiceApplyRequest) (*InvoiceApplyResponse, error) {
	var envelope struct {
		Code int                  `json:"code"`
		Msg  string               `json:"msg"`
		Data *InvoiceApplyResponse `json:"data"`
	}
	if err := c.doRequest(ctx, c.pathSubmitInvoiceApply, req, &envelope); err != nil {
		return nil, err
	}
	if err := checkBusinessCode(envelope.Code, envelope.Msg); err != nil {
		return nil, err
	}
	return envelope.Data, nil
}

// QueryInvoiceStatus 查询订单开票状态接口2func (c *Client) QueryInvoiceStatus(ctx context.Context, req *InvoiceStatusQueryRequest) (*InvoiceStatusQueryResponse, error) {
	var envelope struct {
		Code int                        `json:"code"`
		Msg  string                     `json:"msg"`
		Data *InvoiceStatusQueryResponse `json:"data"`
	}
	if err := c.doRequest(ctx, c.pathQueryInvoiceStatus, req, &envelope); err != nil {
		return nil, err
	}
	if err := checkBusinessCode(envelope.Code, envelope.Msg); err != nil {
		return nil, err
	}
	return envelope.Data, nil
}

// CreatePayment 创建付款单据接口3func (c *Client) CreatePayment(ctx context.Context, req *CreatePaymentRequest) (*CreatePaymentResponse, error) {
	var envelope struct {
		Code int                    `json:"code"`
		Msg  string                 `json:"msg"`
		Data *CreatePaymentResponse `json:"data"`
	}
	if err := c.doRequest(ctx, c.pathCreatePayment, req, &envelope); err != nil {
		return nil, err
	}
	if err := checkBusinessCode(envelope.Code, envelope.Msg); err != nil {
		return nil, err
	}
	return envelope.Data, nil
}

// QueryPaymentStatus 查询支付状态接口5响应数据与支付通知一致。
func (c *Client) QueryPaymentStatus(ctx context.Context, req *PaymentStatusQueryRequest) (*PaymentNotifyData, error) {
	var envelope struct {
		Code int               `json:"code"`
		Msg  string            `json:"msg"`
		Data *PaymentNotifyData `json:"data"`
	}
	if err := c.doRequest(ctx, c.pathQueryPaymentStatus, req, &envelope); err != nil {
		return nil, err
	}
	if err := checkBusinessCode(envelope.Code, envelope.Msg); err != nil {
		return nil, err
	}
	return envelope.Data, nil
}

// CreateSupplier 创建供应商接口6func (c *Client) CreateSupplier(ctx context.Context, req *CreateSupplierRequest) (*CreateSupplierResponse, error) {
	var envelope struct {
		Code int                     `json:"code"`
		Msg  string                  `json:"msg"`
		Data *CreateSupplierResponse `json:"data"`
	}
	if err := c.doRequest(ctx, c.pathCreateSupplier, req, &envelope); err != nil {
		return nil, err
	}
	if err := checkBusinessCode(envelope.Code, envelope.Msg); err != nil {
		return nil, err
	}
	return envelope.Data, nil
}

// QuerySupplierByName 根据名称查询供应商接口7func (c *Client) QuerySupplierByName(ctx context.Context, req *QuerySupplierByNameRequest) (*QuerySupplierByNameResponse, error) {
	var envelope struct {
		Code int                         `json:"code"`
		Msg  string                      `json:"msg"`
		Data *QuerySupplierByNameResponse `json:"data"`
	}
	if err := c.doRequest(ctx, c.pathQuerySupplierByName, req, &envelope); err != nil {
		return nil, err
	}
	if err := checkBusinessCode(envelope.Code, envelope.Msg); err != nil {
		return nil, err
	}
	return envelope.Data, nil
}

// UpdateSupplier 更新供应商接口8func (c *Client) UpdateSupplier(ctx context.Context, req *UpdateSupplierRequest) (*UpdateSupplierResponse, error) {
	var envelope struct {
		Code int                     `json:"code"`
		Msg  string                  `json:"msg"`
		Data *UpdateSupplierResponse `json:"data"`
	}
	if err := c.doRequest(ctx, c.pathUpdateSupplier, req, &envelope); err != nil {
		return nil, err
	}
	if err := checkBusinessCode(envelope.Code, envelope.Msg); err != nil {
		return nil, err
	}
	return envelope.Data, nil
}

// QuerySupplierByUserDefineCode 根据用户自定义编码查询供应商接口9func (c *Client) QuerySupplierByUserDefineCode(ctx context.Context, req *QuerySupplierByUserDefineCodeRequest) (*QuerySupplierByUserDefineCodeResponse, error) {
	var envelope struct {
		Code int                                    `json:"code"`
		Msg  string                                 `json:"msg"`
		Data *QuerySupplierByUserDefineCodeResponse `json:"data"`
	}
	if err := c.doRequest(ctx, c.pathQuerySupplierByUserDefineCode, req, &envelope); err != nil {
		return nil, err
	}
	if err := checkBusinessCode(envelope.Code, envelope.Msg); err != nil {
		return nil, err
	}
	return envelope.Data, nil
}

// ==================== 通知机制辅助 ====================

// ParseNotification 解析通用通知数据。
func ParseNotification(body []byte) (*Notification, error) {
	var n Notification
	if err := json.Unmarshal(body, &n); err != nil {
		return nil, fmt.Errorf("parse notification: %w", err)
	}
	return &n, nil
}

// ParsePaymentNotify 解析支付完成通知数据接口4func ParsePaymentNotify(body []byte) (*PaymentNotifyData, error) {
	var n PaymentNotifyData
	if err := json.Unmarshal(body, &n); err != nil {
		return nil, fmt.Errorf("parse payment notify: %w", err)
	}
	return &n, nil
}

// VerifyNotificationSignature 校验通知签名。
//
// 平台使用客户提供的验签密钥对原始请求体进行 HmacSHA256 签名并 Base64 编码,
// 通过请求头 x-bfl-signature 传递。verifyKey 为客户在平台配置的验签密钥。
func VerifyNotificationSignature(verifyKey string, rawBody []byte, signature string) (bool, error) {
	if signature == "" {
		return false, &NotificationError{Reason: "empty signature"}
	}
	expected := HmacSHA256Base64(verifyKey, string(rawBody))
	return hmacEqual(expected, signature), nil
}

// hmacEqual 常量时间比较两个 Base64 签名是否一致。
func hmacEqual(a, b string) bool {
	if len(a) != len(b) {
		return false
	}
	var v byte
	for i := 0; i < len(a); i++ {
		v |= a[i] ^ b[i]
	}
	return v == 0
}

// File: intelligence_finance_v1_ga/example_test.go

package intelligence_finance_v1_ga

import (
	"context"
	"encoding/json"
	"fmt"
	"testing"
)

// ExampleClient_SubmitInvoiceApply 演示提交订单开票申请。
func ExampleClient_SubmitInvoiceApply() {
	ctx := context.Background()
	client := NewClient(
		"https://api.example.com",
		"your-tenant-id",
		"dd-ai-table",
		"your-client-secret",
	)

	req := &InvoiceApplyRequest{
		OrderID:     "ORDER-20240101-001",
		InvoiceType: 9, // 数电普票
		Purchaser:   "某某科技有限公司",
		TaxNum:      "91330100XXXXXXXXXX",
		Email:       "finance@example.com",
		Phone:       "13800000000",
		Products: []ProductItem{
			{
				ProductName:      "软件服务",
				RevenueCode:      "3040201000000000000",
				AmountIncludeTax: 11300,
				Quantity:         1,
				TaxRate:          0.13,
				TaxSign:          1,
			},
		},
	}

	resp, err := client.SubmitInvoiceApply(ctx, req)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Printf("status=%d errorMsg=%s\n", resp.Status, resp.ErrorMsg)
	// Output:
}

// ExampleClient_CreatePayment 演示创建付款单据。
func ExampleClient_CreatePayment() {
	ctx := context.Background()
	client := NewClient(
		"https://api.example.com",
		"your-tenant-id",
		"dd-ai-table",
		"your-client-secret",
	)

	req := &CreatePaymentRequest{
		Code:   "PAY-20240101-001",
		UserID: "04221***",
		Title:  "供应商货款",
		Amount: "11300.00",
		Supplier: &Supplier{
			Name: "某某供应商",
		},
		PaymentDetailList: []PaymentDetail{
			{
				Amount: "11300.00",
				Tax:    "1300.00",
			},
		},
	}

	resp, err := client.CreatePayment(ctx, req)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Printf("code=%s\n", resp.Code)
	// Output:
}

// ExampleClient_CreateSupplier 演示创建供应商。
func ExampleClient_CreateSupplier() {
	ctx := context.Background()
	client := NewClient(
		"https://api.example.com",
		"your-tenant-id",
		"dd-ai-table",
		"your-client-secret",
	)

	bizData := []SupplierBizData{
		{
			CorpID:  "dingXXXXXX",
			Creator: "04221***",
			SupplierInfo: &SupplierInfo{
				Name:            "某某供应商",
				CorpID:          "dingXXXXXX",
				Creator:         "04221***",
				ContactName:     "张三",
				ContactTelephone: "18888888888",
				InvoiceName:     "某某供应商",
				InvoiceTaxNo:    "91330100XXXXXXXXXX",
			},
		},
	}
	dataBytes, _ := json.Marshal(bizData)

	req := &CreateSupplierRequest{
		InvokeID: "REQ-001",
		UserID:   "04221***",
		Data:     string(dataBytes),
		BizType:  BizTypeCreateSupplier,
	}

	resp, err := client.CreateSupplier(ctx, req)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Printf("bizType=%s data=%s\n", resp.BizType, resp.Data)
	// Output:
}

// ExampleClient_QuerySupplierByName 演示根据名称查询供应商。
func ExampleClient_QuerySupplierByName() {
	ctx := context.Background()
	client := NewClient(
		"https://api.example.com",
		"your-tenant-id",
		"dd-ai-table",
		"your-client-secret",
	)

	queries := []QueryByName{
		{CorpID: "dingXXXXXX", Name: "某某供应商"},
	}
	dataBytes, _ := json.Marshal(queries)

	req := &QuerySupplierByNameRequest{
		InvokeID: "REQ-002",
		UserID:   "04221***",
		Data:     string(dataBytes),
		BizType:  BizTypeQuerySupplierByName,
	}

	resp, err := client.QuerySupplierByName(ctx, req)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Printf("success=%v result=%s\n", resp.Success, resp.Result)
	// Output:
}

// ExampleClient_UpdateSupplier 演示更新供应商。
func ExampleClient_UpdateSupplier() {
	ctx := context.Background()
	client := NewClient(
		"https://api.example.com",
		"your-tenant-id",
		"dd-ai-table",
		"your-client-secret",
	)

	bizData := UpdateSupplierBizData{
		SupplierID:     "SUP_XXXXX",
		Name:           "某某供应商",
		CorpID:         "dingXXXXXX",
		UserID:         "04221***",
		ContactName:    "李四",
		ContactTelephone: "18888888888",
		AccountType:    AccountTypeCorpBankCard,
	}
	dataBytes, _ := json.Marshal(bizData)

	req := &UpdateSupplierRequest{
		InvokeID: "REQ-003",
		UserID:   "04221***",
		Data:     string(dataBytes),
		BizType:  BizTypeUpdateSupplier,
	}

	resp, err := client.UpdateSupplier(ctx, req)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Printf("bizType=%s\n", resp.BizType)
	// Output:
}

// ExampleClient_QuerySupplierByUserDefineCode 演示根据用户自定义编码查询供应商。
func ExampleClient_QuerySupplierByUserDefineCode() {
	ctx := context.Background()
	client := NewClient(
		"https://api.example.com",
		"your-tenant-id",
		"dd-ai-table",
		"your-client-secret",
	)

	bizData := QueryByUserDefineCode{
		CorpID:         "dingXXXXXX",
		UserDefineCode: "SUP-CODE-001",
	}
	dataBytes, _ := json.Marshal(bizData)

	req := &QuerySupplierByUserDefineCodeRequest{
		InvokeID: "REQ-004",
		UserID:   "04221***",
		Data:     string(dataBytes),
		BizType:  BizTypeQuerySupplierByUserDefine,
	}

	resp, err := client.QuerySupplierByUserDefineCode(ctx, req)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Printf("bizType=%s data=%s\n", resp.BizType, resp.Data)
	// Output:
}

// TestVerifyNotificationSignature 演示通知验签。
func TestVerifyNotificationSignature(t *testing.T) {
	verifyKey := "customer-verify-secret"
	rawBody := []byte(`{"bizType":"payment","bizId":"PAY-001","data":"{}"}`)
	sign := HmacSHA256Base64(verifyKey, string(rawBody))

	ok, err := VerifyNotificationSignature(verifyKey, rawBody, sign)
	if err != nil {
		t.Fatalf("verify error: %v", err)
	}
	if !ok {
		t.Fatal("signature mismatch")
	}
}

// TestParsePaymentNotify 演示解析支付完成通知。
func TestParsePaymentNotify(t *testing.T) {
	body := []byte(`{
		"code":"PAY-001",
		"instanceId":"inst-001",
		"corpId":"dingXXXXXX",
		"paymentStatus":"SUCCESS",
		"paymentTime":"2024-01-01 10:00:00",
		"userId":"04221***",
		"amount":"11300.00"
	}`)
	n, err := ParsePaymentNotify(body)
	if err != nil {
		t.Fatalf("parse error: %v", err)
	}
	if n.PaymentStatus != PaymentStatusSuccess {
		t.Fatalf("unexpected status: %s", n.PaymentStatus)
	}
}