From fc3727f73ffd244cee9252ddbd0c353311c8aa2c Mon Sep 17 00:00:00 2001 From: renzhiyuan <465386466@qq.com> Date: Mon, 17 Aug 2026 15:48:51 +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/generate.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- intelligence_finance_v1/generate.md | 1234 +++++++++++++++++++++++++++ 1 file changed, 1234 insertions(+) create mode 100644 intelligence_finance_v1/generate.md diff --git a/intelligence_finance_v1/generate.md b/intelligence_finance_v1/generate.md new file mode 100644 index 0000000..e1ee9fd --- /dev/null +++ b/intelligence_finance_v1/generate.md @@ -0,0 +1,1234 @@ +Now I'll generate all 6 files for the complete Go SDK. + +// File: intelligence_finance_v1/go.mod +```go +module intelligence_finance_v1 + +go 1.21 +``` + +// File: intelligence_finance_v1/errors.go +```go +package intelligence_finance_v1 + +import "fmt" + +// ErrorCode 表示 API 返回的错误码 +type ErrorCode int + +// APIError 表示 API 调用返回的业务错误 +type APIError struct { + Code int `json:"code"` + Message string `json:"msg"` +} + +// Error 实现 error 接口 +func (e *APIError) Error() string { + return fmt.Sprintf("API error: code=%d, msg=%s", e.Code, e.Message) +} + +// NewAPIError 创建一个新的 APIError +func NewAPIError(code int, msg string) *APIError { + return &APIError{ + Code: code, + Message: msg, + } +} + +// 开票状态常量 +const ( + InvoiceStatusNotInvoiced = 0 // 未开票 + InvoiceStatusInvoicing = 1 // 开票中 + InvoiceStatusPartiallyFailed = 2 // 部分失败 + InvoiceStatusSuccess = 3 // 开票成功 + InvoiceStatusFailed = 4 // 开票失败 + InvoiceStatusPartiallyUnsent = 5 // 部分未开 + InvoiceStatusNoAccount = 6 // 未配置数电账号 + InvoiceStatusNoAutoConfig = 7 // 未配置自动开票配置 +) + +// 发票类型常量 +const ( + InvoiceTypeSpecial = 1 // 专用发票 + InvoiceTypeNormal = 2 // 普通发票 + InvoiceTypeNormalElectronic = 3 // 普通发票(电子) + InvoiceTypeSpecialElectronic = 4 // 专用发票(电子) + InvoiceTypeDigitalSpecial = 8 // 数电专票 + InvoiceTypeDigitalNormal = 9 // 数电普票 +) + +// 支付状态常量 +const ( + PaymentStatusSuccess = "SUCCESS" // 支付成功 + PaymentStatusFail = "FAIL" // 支付失败 + PaymentStatusTerminate = "TERMINATE" // 支付取消 + PaymentStatusWaitPay = "WAIT_PAY" // 待支付 + PaymentStatusPaying = "PAYING" // 支付中 + PaymentStatusPartSuccess = "PART_SUCCESS" // 部分支付成功 + PaymentStatusRefund = "REFUND" // 退款 +) + +// 账户类型常量 +const ( + AccountTypeAlipay = "ALIPAY" // 支付宝 + AccountTypeBankCard = "BANKCARD" // 银行卡 + AccountTypeCorpBankCard = "CORP_BANK_CARD" // 对公银行卡 + AccountTypePersonalBankCard = "PERSONAL_BANK_CARD" // 对私银行卡 +) + +// 通知响应常量 +const ( + NotificationSuccess = "SUCCESS" + NotificationFailed = "FAILED" +) + +// 发票状态常量(发票自身状态) +const ( + InvStatusNormal = "1" // 正常 + InvStatusRedacted = "2" // 已红冲 + InvStatusVoided = "3" // 已作废 +) + +// 开票类型 +const ( + IssueTypeNormal = "0" // 正数票 + IssueTypeNegative = "1" // 负数票(红冲) +) + +// 来源枚举 +const ( + SourceApproval = "approval" // 审批单 + SourceOpenAPI = "openapi" // 开发接口 +) +``` + +// File: intelligence_finance_v1/crypto.go +```go +package intelligence_finance_v1 + +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 生成指定长度的加密安全随机字符串 +// length 建议为 16-32 位 +func GenerateNonce(length int) (string, error) { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + b := make([]byte, length) + for i := range b { + num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) + if err != nil { + return "", fmt.Errorf("generate nonce: %w", err) + } + b[i] = charset[num.Int64()] + } + return string(b), nil +} + +// HmacSHA256Sign 使用 HmacSHA256 算法对数据进行签名,返回 Base64 编码的签名字符串 +// 密钥为 client-secret,签名数据为 timestamp + nonce +func HmacSHA256Sign(secret string, data string) string { + h := hmac.New(sha256.New, []byte(secret)) + h.Write([]byte(data)) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} + +// HmacSHA256Verify 验证 HmacSHA256 签名 +func HmacSHA256Verify(secret string, data string, signature string) bool { + expected := HmacSHA256Sign(secret, data) + return hmac.Equal([]byte(expected), []byte(signature)) +} + +// BuildSignString 按字典序排序拼接参数,格式为 key=value&key2=value2 +// 排除空值、sign 和 signature 字段 +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 按固定顺序拼接参数 +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/types.go +```go +package intelligence_finance_v1 + +// ============================================================================ +// 通用结构 +// ============================================================================ + +// commonResponse 通用 API 响应结构(内部使用) +type commonResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data any `json:"data"` +} + +// ============================================================================ +// 订单开票(接口 1) +// ============================================================================ + +// InvoiceRequest 订单开票请求参数 +type InvoiceRequest 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 string `json:"amountIncludeTax"` + // Specs 规格型号 + Specs string `json:"specs,omitempty"` + // Unit 计量单位(如:台、个、次) + Unit string `json:"unit,omitempty"` + // Quantity 数量 + Quantity string `json:"quantity"` + // Discount 折扣金额(无折扣传0) + Discount string `json:"discount,omitempty"` + // TaxSign 是否含税:0-不含税;1-含税(默认建议传1) + TaxSign int `json:"taxSign,omitempty"` + // TaxRate 税率(小数形式,如0.13表示13%) + TaxRate string `json:"taxRate,omitempty"` +} + +// InvoiceResponse 订单开票响应数据 +type InvoiceResponse 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 税控设备号 + DeviceCode string `json:"deviceCode"` + // Drawer 开票人 + Drawer string `json:"drawer"` + // Email 邮箱(购方邮箱) + Email string `json:"email"` + // InvoiceType 发票类型:1-专用发票;2-普通发票;3-普通发票(电子);4-专用发票(电子);8-数电专票;9-数电普票 + InvoiceType string `json:"invoiceType"` + // IssueType 开票类型:0-正数票;1-负数票(红冲) + IssueType string `json:"issueType"` + // ListFlag 清单标识:0-无清单;1-有清单 + ListFlag string `json:"listFlag"` + // Mobile 手机号(购方手机) + Mobile string `json:"mobile"` + // OriginalInvCode 红冲时对应的原蓝票代码 + OriginalInvCode string `json:"originalInvCode,omitempty"` + // OriginalInvNo 红冲时对应的原蓝票号码 + OriginalInvNo string `json:"originalInvNo,omitempty"` + // AdditionInfo 数电发票备注栏的附加信息部分 + AdditionInfo string `json:"additionInfo,omitempty"` + // Payee 收款人 + Payee string `json:"payee"` + // PurchaserAddress 购方地址 + PurchaserAddress string `json:"purchaserAddress"` + // PurchaserBankAccount 购方银行账号 + PurchaserBankAccount string `json:"purchaserBankAccount"` + // PurchaserBankName 购方开户行 + PurchaserBankName string `json:"purchaserBankName"` + // PurchaserName 购方名称 + PurchaserName string `json:"purchaserName"` + // PurchaserTaxNo 购方税号 + PurchaserTaxNo string `json:"purchaserTaxNo"` + // PurchaserTel 购方电话 + PurchaserTel string `json:"purchaserTel"` + // NaturalPerson 购买方自然人标识:Y-是;N-否 + NaturalPerson string `json:"naturalPerson,omitempty"` + // Remark 备注 + Remark string `json:"remark"` + // Reviewer 复核人 + Reviewer string `json:"reviewer"` + // SellerAddress 销方地址 + SellerAddress string `json:"sellerAddress,omitempty"` + // SellerBankAccount 销方开户账号 + SellerBankAccount string `json:"sellerBankAccount"` + // SellerBankName 销方开户行 + SellerBankName string `json:"sellerBankName"` + // SellerName 销方名称 + SellerName string `json:"sellerName"` + // CheckCode 校验码 + CheckCode string `json:"checkCode"` + // CipherText 密码区 + CipherText string `json:"cipherText"` + // DrewDate 开票日期(格式:yyyy-MM-dd HH:mm:ss) + DrewDate string `json:"drewDate,omitempty"` + // InvoiceCode 发票代码 + InvoiceCode string `json:"invoiceCode"` + // InvoiceNo 发票号码 + InvoiceNo string `json:"invoiceNo"` + // InvoiceStatus 发票状态:1-正常;2-已红冲;3-已作废 + InvoiceStatus string `json:"invoiceStatus"` + // LayoutFileURL 电子发票地址(PDF/OFD) + LayoutFileURL string `json:"layoutFileUrl,omitempty"` + // PDFURL 电子发票PDF地址 + PDFURL string `json:"pdfUrl,omitempty"` + // OFDURL 电子发票OFD地址 + OFDURL string `json:"ofdUrl,omitempty"` + // XMLURL 电子发票XML数据地址 + XMLURL string `json:"xmlUrl,omitempty"` + // TotalExcludeTax 合计金额(不含税) + TotalExcludeTax string `json:"totalExcludeTax"` + // TotalIncludeTax 合计金额(含税) + TotalIncludeTax string `json:"totalIncludeTax"` + // TotalTaxAmount 合计税额 + TotalTaxAmount string `json:"totalTaxAmount"` + // LevyingType 征税方式 + LevyingType string `json:"levyingType"` + // Details 商品明细列表 + Details []InvoiceDetail `json:"details"` +} + +// InvoiceDetail 发票商品明细 +type InvoiceDetail struct { + // Amount 金额 + Amount string `json:"amount,omitempty"` + // Quantity 数量 + Quantity string `json:"quantity,omitempty"` + // DeductionAmount 扣除金额 + DeductionAmount string `json:"deductionAmount,omitempty"` + // TaxAmount 税额 + TaxAmount string `json:"taxAmount,omitempty"` + // ItemTitle 商品合并显示名称 + ItemTitle string `json:"itemTitle"` + // TaxCode 税收分类编码(19位) + TaxCode string `json:"taxCode"` + // ItemType 商品行性质:0-正常行;1-折扣行;2-被折扣行 + ItemType string `json:"itemType"` + // ItemName 商品简称 + ItemName string `json:"itemName"` + // Specs 商品规格型号 + Specs string `json:"specs,omitempty"` + // TaxFreePolicy 免税政策:1-免税;2-不征税;3-普通零税率 + TaxFreePolicy string `json:"taxFreePolicy"` + // PreferentialPolicy 优惠政策类型 + PreferentialPolicy string `json:"preferentialPolicy"` + // TaxRate 税率(小数形式,如0.13) + TaxRate string `json:"taxRate"` + // TaxSign 是否含税:0-否;1-是 + TaxSign string `json:"taxSign"` + // Unit 计量单位(如:台、个、次) + Unit string `json:"unit,omitempty"` + // UnitPrice 单价 + 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 []InvoiceData `json:"data"` +} + +// ============================================================================ +// 创建付款单据(接口 3) +// ============================================================================ + +// PaymentDocumentRequest 创建付款单据请求参数 +type PaymentDocumentRequest struct { + // Code 单据编码 + Code string `json:"code"` + // YidaAppType 宜搭应用类型 + YidaAppType string `json:"yidaAppType,omitempty"` + // EmpAccountUserID 员工账号用户ID + EmpAccountUserID string `json:"empAccountUserId,omitempty"` + // Department 部门信息 + Department *Department `json:"department,omitempty"` + // Usage 用途 + Usage string `json:"usage,omitempty"` + // PaymentUserID 付款人用户ID + PaymentUserID string `json:"paymentUserId,omitempty"` + // Customer 客户信息 + Customer *Customer `json:"customer,omitempty"` + // PrincipalID 负责人ID + PrincipalID string `json:"principalId,omitempty"` + // Remark 备注 + Remark string `json:"remark,omitempty"` + // Supplier 供应商信息 + Supplier *Supplier `json:"supplier,omitempty"` + // Title 标题 + Title string `json:"title,omitempty"` + // Project 项目信息 + Project *Project `json:"project,omitempty"` + // PaymentUserIDListStr 付款人用户ID列表字符串 + PaymentUserIDListStr string `json:"paymentUserIdListStr,omitempty"` + // NeedPayment 是否需要付款 + NeedPayment *bool `json:"needPayment,omitempty"` + // PaymentDetailListJSONStr 付款明细列表JSON字符串 + PaymentDetailListJSONStr string `json:"paymentDetailListJsonStr,omitempty"` + // PaymentDetailList 付款明细列表 + PaymentDetailList []PaymentDetail `json:"paymentDetailList,omitempty"` + // Company 企业主体信息 + Company *Company `json:"company,omitempty"` + // Amount 金额 + Amount string `json:"amount,omitempty"` + // RecipientAccountInfo 收款账户信息 + RecipientAccountInfo *RecipientAccount `json:"recipientAccountInfo,omitempty"` + // EnterpriseAccount 企业账号信息 + EnterpriseAccount *EnterpriseAccount `json:"enterpriseAccount,omitempty"` + // Category 收支类别 + Category []Category `json:"category,omitempty"` + // UserID 用户ID + UserID string `json:"userId"` + // OccurDate 发生日期(时间戳,毫秒) + OccurDate *int64 `json:"occurDate,omitempty"` + // Product 商品信息 + Product *Product `json:"product,omitempty"` + // YidaFormUUID 宜搭表单UUID + YidaFormUUID string `json:"yidaFormUuid,omitempty"` + // CanEditPaymentInfo 是否可编辑付款信息 + CanEditPaymentInfo *bool `json:"canEditPaymentInfo,omitempty"` + // PaymentUserIDList 付款人用户ID列表 + PaymentUserIDList []string `json:"paymentUserIdList,omitempty"` + // YidaProcInsID 宜搭流程实例ID + YidaProcInsID string `json:"yidaProcInsId,omitempty"` + // SyncPaymentOrder 是否同步付款单 + 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"` +} + +// PaymentDocumentResponse 创建付款单据响应数据 +type PaymentDocumentResponse struct { + // Code 单据唯一标识 + Code string `json:"code"` +} + +// ============================================================================ +// 支付完成通知(接口 4)- 平台回调客户 +// ============================================================================ + +// PaymentNotification 支付完成通知数据 +type PaymentNotification struct { + // Code 单据编码 + Code string `json:"code"` + // InstanceID 实例ID + InstanceID string `json:"instanceId"` + // CorpID 企业ID + CorpID string `json:"corpId"` + // PaymentStatus 支付状态 + PaymentStatus string `json:"paymentStatus"` + // PaymentTime 支付时间 + PaymentTime string `json:"paymentTime"` + // UserID 用户ID + UserID string `json:"userId"` + // FailReason 失败原因 + FailReason string `json:"failReason,omitempty"` + // PayerAccountInfo 付款账户信息 + PayerAccountInfo *PayerAccountInfo `json:"payerAccountInfo,omitempty"` + // PayeeAccountInfo 收款账户信息 + PayeeAccountInfo *PayeeAccountInfo `json:"payeeAccountInfo,omitempty"` + // RelatedRowNumberList 关联行号列表 + RelatedRowNumberList []string `json:"relatedRowNumberList,omitempty"` + // Source 来源 + Source string `json:"source,omitempty"` + // Template 模板 + Template string `json:"template,omitempty"` + // Amount 金额 + Amount string `json:"amount,omitempty"` +} + +// PayeeAccountInfo 收款账户信息 +type PayeeAccountInfo struct { + BankOpenDTO *BankOpenDTO `json:"bankOpenDTO,omitempty"` +} + +// PayerAccountInfo 付款账户信息 +type PayerAccountInfo struct { + BankOpenDTO *BankOpenDTO `json:"bankOpenDTO,omitempty"` + EnterpriseAccountCode string `json:"enterpriseAccountCode,omitempty"` + AccountType string `json:"accountType,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"` +} + +// ============================================================================ +// 支付状态查询(接口 5) +// ============================================================================ + +// PaymentStatusQueryRequest 支付状态查询请求参数 +type PaymentStatusQueryRequest struct { + // Code 单据编码 + Code string `json:"code"` + // UserID 用户ID + UserID string `json:"userId"` +} + +// PaymentStatusQueryResponse 支付状态查询响应(与支付通知数据结构一致) +type PaymentStatusQueryResponse = PaymentNotification + +// ============================================================================ +// 通用通知(回调) +// ============================================================================ + +// GeneralNotification 通用通知数据 +type GeneralNotification struct { + // BizType 业务类型 + BizType string `json:"bizType"` + // BizID 业务ID + BizID string `json:"bizId"` + // Data 通知数据 + Data string `json:"data,omitempty"` +} +``` + +// File: intelligence_finance_v1/client.go +```go +package intelligence_finance_v1 + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +const ( + // DefaultTimeout 默认请求超时时间 + DefaultTimeout = 30 * time.Second + // DefaultNonceLength 默认随机数长度 + DefaultNonceLength = 16 + // HeaderTenantID 租户ID请求头 + HeaderTenantID = "tenant-id" + // HeaderClientID 应用标识请求头 + HeaderClientID = "client-id" + // HeaderSignatureTimestamp 签名时间戳请求头 + HeaderSignatureTimestamp = "x-bfl-signature-timestamp" + // HeaderSignatureNonce 签名随机数请求头 + HeaderSignatureNonce = "x-bfl-signature-nonce" + // HeaderSignature 签名信息请求头 + HeaderSignature = "x-bfl-signature" + // ClientIDDingTalk 钉钉AI表格的固定 client-id + ClientIDDingTalk = "dd-ai-table" +) + +// Client 业财连接 SDK 客户端 +// 用于调用平台提供的接口(订单开票、开票状态查询、创建付款单据、支付状态查询) +// 以及处理平台回调通知 +type Client struct { + // BaseURL 平台接口基础地址 + BaseURL string + // TenantID 平台分配的租户唯一标识 + TenantID string + // ClientID 平台分配的应用标识 + ClientID string + // ClientSecret 平台分配的密钥,用于签名 + ClientSecret string + // HTTPClient HTTP 客户端 + HTTPClient *http.Client + // NonceLength 随机数长度 + NonceLength int +} + +// ClientOption 客户端配置选项 +type ClientOption func(*Client) + +// WithHTTPClient 设置自定义 HTTP 客户端 +func WithHTTPClient(httpClient *http.Client) ClientOption { + return func(c *Client) { + c.HTTPClient = httpClient + } +} + +// WithNonceLength 设置随机数长度 +func WithNonceLength(length int) ClientOption { + return func(c *Client) { + c.NonceLength = length + } +} + +// NewClient 创建一个新的业财连接客户端 +// baseURL: 平台接口基础地址(如 "https://api.example.com") +// tenantID: 平台分配的租户唯一标识 +// clientID: 平台分配的应用标识(钉钉AI表格固定为 "dd-ai-table") +// clientSecret: 平台分配的密钥 +func NewClient(baseURL, tenantID, clientID, clientSecret string, opts ...ClientOption) *Client { + c := &Client{ + BaseURL: baseURL, + TenantID: tenantID, + ClientID: clientID, + ClientSecret: clientSecret, + HTTPClient: &http.Client{ + Timeout: DefaultTimeout, + }, + NonceLength: DefaultNonceLength, + } + for _, opt := range opts { + opt(c) + } + return c +} + +// NewDingTalkClient 创建一个钉钉AI表格专用的客户端 +// baseURL: 平台接口基础地址 +// tenantID: 平台分配的租户唯一标识 +// clientSecret: 平台分配的密钥(在钉钉AI表格中配置为 APPSecret) +// 注意:钉钉AI表格的 client-id 固定为 "dd-ai-table",签名由AI表格自动完成 +func NewDingTalkClient(baseURL, tenantID, clientSecret string, opts ...ClientOption) *Client { + return NewClient(baseURL, tenantID, ClientIDDingTalk, clientSecret, opts...) +} + +// ============================================================================ +// 签名相关方法 +// ============================================================================ + +// buildSignature 构建请求签名 +// 签名算法:HmacSHA256(client-secret, timestamp + nonce),结果 Base64 编码 +func (c *Client) buildSignature(timestamp, nonce string) string { + data := timestamp + nonce + return HmacSHA256Sign(c.ClientSecret, data) +} + +// setAuthHeaders 设置认证请求头 +func (c *Client) setAuthHeaders(req *http.Request, timestamp, nonce string) { + req.Header.Set(HeaderTenantID, c.TenantID) + req.Header.Set(HeaderClientID, c.ClientID) + req.Header.Set(HeaderSignatureTimestamp, timestamp) + req.Header.Set(HeaderSignatureNonce, nonce) + req.Header.Set(HeaderSignature, c.buildSignature(timestamp, nonce)) + req.Header.Set("Content-Type", "application/json") +} + +// ============================================================================ +// 内部 HTTP 请求方法 +// ============================================================================ + +// doRequest 执行带签名的 POST 请求 +func (c *Client) doRequest(ctx context.Context, path string, requestBody interface{}) (*commonResponse, error) { + bodyBytes, err := json.Marshal(requestBody) + if err != nil { + return nil, fmt.Errorf("marshal request body: %w", err) + } + + url := c.BaseURL + path + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(bodyBytes)) + if err != nil { + return nil, fmt.Errorf("create request: %w", err) + } + + timestamp := GenerateTimestamp() + nonce, err := GenerateNonce(c.NonceLength) + if err != nil { + return nil, fmt.Errorf("generate nonce: %w", err) + } + + c.setAuthHeaders(req, timestamp, nonce) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("execute request: %w", err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response body: %w", err) + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, fmt.Errorf("unexpected HTTP status: %d, body: %s", resp.StatusCode, string(respBody)) + } + + var commonResp commonResponse + if err := json.Unmarshal(respBody, &commonResp); err != nil { + return nil, fmt.Errorf("unmarshal response: %w", err) + } + + if commonResp.Code != 0 { + return nil, NewAPIError(commonResp.Code, commonResp.Msg) + } + + return &commonResp, nil +} + +// ============================================================================ +// 接口 1:订单开票 +// ============================================================================ + +// CreateInvoice 提交订单开票请求 +// path: 接口路径(接口短码,由平台对接时分配) +// req: 开票请求参数 +// 返回开票响应,包含开票状态和发票数据列表 +func (c *Client) CreateInvoice(ctx context.Context, path string, req *InvoiceRequest) (*InvoiceResponse, error) { + commonResp, err := c.doRequest(ctx, path, req) + if err != nil { + return nil, err + } + + // 将 data 字段重新序列化后反序列化为 InvoiceResponse + dataBytes, err := json.Marshal(commonResp.Data) + if err != nil { + return nil, fmt.Errorf("marshal response data: %w", err) + } + + var invoiceResp InvoiceResponse + if err := json.Unmarshal(dataBytes, &invoiceResp); err != nil { + return nil, fmt.Errorf("unmarshal invoice response: %w", err) + } + + return &invoiceResp, nil +} + +// ============================================================================ +// 接口 2:开票状态查询 +// ============================================================================ + +// QueryInvoiceStatus 查询订单开票状态 +// path: 接口路径(接口短码,由平台对接时分配) +// req: 查询请求参数 +// 返回开票状态查询响应 +func (c *Client) QueryInvoiceStatus(ctx context.Context, path string, req *InvoiceStatusQueryRequest) (*InvoiceStatusQueryResponse, error) { + commonResp, err := c.doRequest(ctx, path, req) + if err != nil { + return nil, err + } + + dataBytes, err := json.Marshal(commonResp.Data) + if err != nil { + return nil, fmt.Errorf("marshal response data: %w", err) + } + + var statusResp InvoiceStatusQueryResponse + if err := json.Unmarshal(dataBytes, &statusResp); err != nil { + return nil, fmt.Errorf("unmarshal invoice status response: %w", err) + } + + return &statusResp, nil +} + +// ============================================================================ +// 接口 3:创建付款单据 +// ============================================================================ + +// CreatePaymentDocument 创建付款单据 +// path: 接口路径(接口短码,由平台对接时分配) +// req: 付款单据请求参数 +// 返回创建结果,包含单据唯一标识 +func (c *Client) CreatePaymentDocument(ctx context.Context, path string, req *PaymentDocumentRequest) (*PaymentDocumentResponse, error) { + commonResp, err := c.doRequest(ctx, path, req) + if err != nil { + return nil, err + } + + dataBytes, err := json.Marshal(commonResp.Data) + if err != nil { + return nil, fmt.Errorf("marshal response data: %w", err) + } + + var paymentResp PaymentDocumentResponse + if err := json.Unmarshal(dataBytes, &paymentResp); err != nil { + return nil, fmt.Errorf("unmarshal payment document response: %w", err) + } + + return &paymentResp, nil +} + +// ============================================================================ +// 接口 5:支付状态查询 +// ============================================================================ + +// QueryPaymentStatus 查询支付状态 +// path: 接口路径(接口短码,由平台对接时分配) +// req: 查询请求参数 +// 返回支付状态信息,结构与支付通知一致 +func (c *Client) QueryPaymentStatus(ctx context.Context, path string, req *PaymentStatusQueryRequest) (*PaymentStatusQueryResponse, error) { + commonResp, err := c.doRequest(ctx, path, req) + if err != nil { + return nil, err + } + + dataBytes, err := json.Marshal(commonResp.Data) + if err != nil { + return nil, fmt.Errorf("marshal response data: %w", err) + } + + var paymentStatusResp PaymentStatusQueryResponse + if err := json.Unmarshal(dataBytes, &paymentStatusResp); err != nil { + return nil, fmt.Errorf("unmarshal payment status response: %w", err) + } + + return &paymentStatusResp, nil +} + +// ============================================================================ +// 回调通知处理(接口 4:支付完成通知) +// ============================================================================ + +// ParsePaymentNotification 从 HTTP 请求中解析支付完成通知 +// 该方法用于客户接收平台回调时使用 +func ParsePaymentNotification(r *http.Request) (*PaymentNotification, error) { + if r.Method != http.MethodPost { + return nil, fmt.Errorf("invalid HTTP method: %s, expected POST", r.Method) + } + + body, err := io.ReadAll(r.Body) + if err != nil { + return nil, fmt.Errorf("read notification body: %w", err) + } + defer r.Body.Close() + + var notification PaymentNotification + if err := json.Unmarshal(body, ¬ification); err != nil { + return nil, fmt.Errorf("unmarshal payment notification: %w", err) + } + + return ¬ification, nil +} + +// ParseGeneralNotification 从 HTTP 请求中解析通用通知 +func ParseGeneralNotification(r *http.Request) (*GeneralNotification, error) { + if r.Method != http.MethodPost { + return nil, fmt.Errorf("invalid HTTP method: %s, expected POST", r.Method) + } + + body, err := io.ReadAll(r.Body) + if err != nil { + return nil, fmt.Errorf("read notification body: %w", err) + } + defer r.Body.Close() + + var notification GeneralNotification + if err := json.Unmarshal(body, ¬ification); err != nil { + return nil, fmt.Errorf("unmarshal general notification: %w", err) + } + + return ¬ification, nil +} + +// VerifyNotificationSignature 验证回调通知签名 +// payload: 原始请求体 +// signature: 请求中的签名 +// secret: 客户在平台配置的验签密钥 +// 使用 HmacSHA256 算法验证 +func VerifyNotificationSignature(payload []byte, signature, secret string) bool { + return HmacSHA256Verify(secret, string(payload), signature) +} + +// WriteNotificationResponse 写入通知响应 +// success: true 返回 "SUCCESS",false 返回 "FAILED" +func WriteNotificationResponse(w http.ResponseWriter, success bool) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + if success { + w.WriteHeader(http.StatusOK) + w.Write([]byte(NotificationSuccess)) + } else { + w.WriteHeader(http.StatusOK) + w.Write([]byte(NotificationFailed)) + } +} +``` + +// File: intelligence_finance_v1/example_test.go +```go +package intelligence_finance_v1_test + +import ( + "context" + "fmt" + "log" + "net/http" + + "intelligence_finance_v1" +) + +// ExampleClient_CreateInvoice 演示如何提交订单开票请求 +func ExampleClient_CreateInvoice() { + // 创建客户端 + client := intelligence_finance_v1.NewClient( + "https://api.example.com", // 平台基础地址 + "your-tenant-id", // 租户ID + "your-client-id", // 应用标识 + "your-client-secret", // 密钥 + ) + + // 构建开票请求 + req := &intelligence_finance_v1.InvoiceRequest{ + OrderID: "ORDER-2024-001", + InvoiceType: intelligence_finance_v1.InvoiceTypeDigitalNormal, // 数电普票 + Purchaser: "测试购方企业", + Taxnum: "91110000000000000X", + Products: []intelligence_finance_v1.ProductItem{ + { + ProductName: "测试商品", + RevenueCode: "1090101000000000000", + AmountIncludeTax: "1130.00", + Quantity: "1", + Unit: "个", + TaxSign: 1, + TaxRate: "0.13", + Discount: "0", + }, + }, + Email: "test@example.com", + Phone: "13800138000", + } + + // 调用接口 + ctx := context.Background() + resp, err := client.CreateInvoice(ctx, "/api/invoice/create", req) + if err != nil { + log.Fatalf("创建发票失败: %v", err) + } + + fmt.Printf("开票状态: %d\n", resp.Status) + if resp.Status == intelligence_finance_v1.InvoiceStatusSuccess { + for _, data := range resp.DataList { + fmt.Printf("发票号码: %s, 发票代码: %s\n", data.InvoiceNo, data.InvoiceCode) + } + } +} + +// ExampleClient_QueryInvoiceStatus 演示如何查询开票状态 +func ExampleClient_QueryInvoiceStatus() { + client := intelligence_finance_v1.NewClient( + "https://api.example.com", + "your-tenant-id", + "your-client-id", + "your-client-secret", + ) + + req := &intelligence_finance_v1.InvoiceStatusQueryRequest{ + OrderID: "ORDER-2024-001", + } + + ctx := context.Background() + resp, err := client.QueryInvoiceStatus(ctx, "/api/invoice/status", req) + if err != nil { + log.Fatalf("查询开票状态失败: %v", err) + } + + fmt.Printf("状态: %d, 描述: %s\n", resp.Status, resp.Message) + for _, data := range resp.Data { + fmt.Printf("发票号码: %s, 发票状态: %s\n", data.InvoiceNo, data.InvoiceStatus) + } +} + +// ExampleClient_CreatePaymentDocument 演示如何创建付款单据 +func ExampleClient_CreatePaymentDocument() { + client := intelligence_finance_v1.NewClient( + "https://api.example.com", + "your-tenant-id", + "your-client-id", + "your-client-secret", + ) + + needPayment := true + req := &intelligence_finance_v1.PaymentDocumentRequest{ + Code: "PAY-2024-001", + UserID: "user123", + Title: "测试付款单", + Amount: "10000.00", + Supplier: &intelligence_finance_v1.Supplier{ + Name: "测试供应商", + }, + Company: &intelligence_finance_v1.Company{ + Name: "测试企业", + }, + Department: &intelligence_finance_v1.Department{ + Name: "财务部", + }, + NeedPayment: &needPayment, + PaymentDetailList: []intelligence_finance_v1.PaymentDetail{ + { + Amount: "10000.00", + Remark: "货款", + }, + }, + } + + ctx := context.Background() + resp, err := client.CreatePaymentDocument(ctx, "/api/payment/create", req) + if err != nil { + log.Fatalf("创建付款单据失败: %v", err) + } + + fmt.Printf("付款单据创建成功,单据编码: %s\n", resp.Code) +} + +// ExampleClient_QueryPaymentStatus 演示如何查询支付状态 +func ExampleClient_QueryPaymentStatus() { + client := intelligence_finance_v1.NewClient( + "https://api.example.com", + "your-tenant-id", + "your-client-id", + "your-client-secret", + ) + + req := &intelligence_finance_v1.PaymentStatusQueryRequest{ + Code: "PAY-2024-001", + UserID: "user123", + } + + ctx := context.Background() + resp, err := client.QueryPaymentStatus(ctx, "/api/payment/status", req) + if err != nil { + log.Fatalf("查询支付状态失败: %v", err) + } + + fmt.Printf("支付状态: %s, 支付时间: %s\n", resp.PaymentStatus, resp.PaymentTime) +} + +// Example_HandlePaymentNotification 演示如何处理平台支付完成回调 +func Example_HandlePaymentNotification() { + // 这是一个 HTTP handler 示例,展示如何处理平台回调 + http.HandleFunc("/callback/payment", func(w http.ResponseWriter, r *http.Request) { + // 解析通知数据 + notification, err := intelligence_finance_v1.ParsePaymentNotification(r) + if err != nil { + log.Printf("解析通知失败: %v", err) + intelligence_finance_v1.WriteNotificationResponse(w, false) + return + } + + // 根据支付状态处理业务逻辑 + switch notification.PaymentStatus { + case intelligence_finance_v1.PaymentStatusSuccess: + log.Printf("支付成功: 单据 %s, 金额 %s, 时间 %s", + notification.Code, notification.Amount, notification.PaymentTime) + // 更新订单状态等业务处理... + case intelligence_finance_v1.PaymentStatusFail: + log.Printf("支付失败: 单据 %s, 原因: %s", + notification.Code, notification.FailReason) + case intelligence_finance_v1.PaymentStatusTerminate: + log.Printf("支付已取消: 单据 %s", notification.Code) + default: + log.Printf("支付状态: %s, 单据 %s", notification.PaymentStatus, notification.Code) + } + + // 返回成功响应 + intelligence_finance_v1.WriteNotificationResponse(w, true) + }) + + log.Println("回调服务启动在 :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} + +// Example_VerifySignature 演示签名生成和验证 +func Example_VerifySignature() { + secret := "your-client-secret" + timestamp := intelligence_finance_v1.GenerateTimestamp() + nonce, _ := intelligence_finance_v1.GenerateNonce(16) + + // 生成签名 + data := timestamp + nonce + signature := intelligence_finance_v1.HmacSHA256Sign(secret, data) + fmt.Printf("签名: %s\n", signature) + + // 验证签名 + valid := intelligence_finance_v1.HmacSHA256Verify(secret, data, signature) + fmt.Printf("签名验证: %v\n", valid) +} + +// Example_DingTalkClient 演示钉钉AI表格客户端的使用 +func Example_DingTalkClient() { + // 钉钉AI表格的 client-id 固定为 "dd-ai-table" + // 签名由AI表格自动完成,但 SDK 仍会生成签名请求头 + client := intelligence_finance_v1.NewDingTalkClient( + "https://api.example.com", + "your-tenant-id", + "your-app-secret", // 在钉钉AI表格中配置为 APPSecret + ) + + req := &intelligence_finance_v1.InvoiceStatusQueryRequest{ + OrderID: "ORDER-2024-001", + } + + ctx := context.Background() + resp, err := client.QueryInvoiceStatus(ctx, "/api/invoice/status", req) + if err != nil { + log.Fatalf("查询失败: %v", err) + } + + fmt.Printf("开票状态: %d\n", resp.Status) +} +``` + +=== SDK 生成完成 === \ No newline at end of file