From 5d1b647b7e62b98afff80e17aaf3bb44e4543fd4 Mon Sep 17 00:00:00 2001 From: renzhiyuan <465386466@qq.com> Date: Mon, 17 Aug 2026 17:04:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6:=20intelli?= =?UTF-8?q?gence=5Ffinance=5Fv1=5Fga/valid.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- intelligence_finance_v1_ga/valid.md | 1305 +++++++++++++++++++++++++++ 1 file changed, 1305 insertions(+) create mode 100644 intelligence_finance_v1_ga/valid.md diff --git a/intelligence_finance_v1_ga/valid.md b/intelligence_finance_v1_ga/valid.md new file mode 100644 index 0000000..bf50c08 --- /dev/null +++ b/intelligence_finance_v1_ga/valid.md @@ -0,0 +1,1305 @@ +// File: intelligence_finance_v1_ga/go.mod +```go +module intelligence_finance_v1_ga + +go 1.21 +``` + +// File: intelligence_finance_v1_ga/errors.go +```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 +```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 +```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 +```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 提交订单开票申请(接口1)。 +func (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 查询订单开票状态(接口2)。 +func (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 创建付款单据(接口3)。 +func (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 创建供应商(接口6)。 +func (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 根据名称查询供应商(接口7)。 +func (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 更新供应商(接口8)。 +func (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 根据用户自定义编码查询供应商(接口9)。 +func (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 解析支付完成通知数据(接口4)。 +func 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 +```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) + } +} +``` \ No newline at end of file