44 KiB
44 KiB
// 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 提交订单开票申请(接口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
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)
}
}
=== SDK 生成完成 ===