982 lines
38 KiB
Markdown
982 lines
38 KiB
Markdown
。
|
||
|
||
现在我将根据文档生成完整的 Go SDK 代码工程。
|
||
|
||
## 分析
|
||
|
||
**认证机制**:客户应用使用 `HmacSHA256` 算法,密钥为 `client-secret`,签名数据为 `timestamp + nonce`,结果 Base64 编码。需要生成 timestamp 和 nonce。
|
||
|
||
**接口列表**:
|
||
1. 订单开票(POST)
|
||
2. 开票状态查询(POST)
|
||
3. 创建付款单据(POST)
|
||
4. 支付完成通知(回调,客户提供接口)
|
||
5. 支付状态查询(POST)
|
||
|
||
**通知机制**:客户提供接口,平台调用,响应直接返回字符串 `SUCCESS` 或 `FAILED`。
|
||
|
||
现在开始生成所有文件:
|
||
|
||
---
|
||
|
||
// 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"
|
||
|
||
// Error 表示 SDK 返回的错误类型。
|
||
type Error struct {
|
||
// Code 错误码
|
||
Code int
|
||
// Msg 错误信息
|
||
Msg string
|
||
// Err 底层错误
|
||
Err error
|
||
}
|
||
|
||
// Error 实现 error 接口。
|
||
func (e *Error) Error() string {
|
||
if e.Err != nil {
|
||
return fmt.Sprintf("code=%d, msg=%s, err=%v", e.Code, e.Msg, e.Err)
|
||
}
|
||
return fmt.Sprintf("code=%d, msg=%s", e.Code, e.Msg)
|
||
}
|
||
|
||
// Unwrap 返回底层错误,支持 errors.Is/As。
|
||
func (e *Error) Unwrap() error {
|
||
return e.Err
|
||
}
|
||
|
||
// NewError 创建一个新的 Error。
|
||
func NewError(code int, msg string) *Error {
|
||
return &Error{Code: code, Msg: msg}
|
||
}
|
||
|
||
// WrapError 包装底层错误。
|
||
func WrapError(code int, msg string, err error) *Error {
|
||
return &Error{Code: code, Msg: msg, Err: err}
|
||
}
|
||
|
||
// 预定义错误
|
||
var (
|
||
// ErrInvalidConfig 配置无效
|
||
ErrInvalidConfig = NewError(1001, "invalid config")
|
||
// ErrNetwork 网络请求失败
|
||
ErrNetwork = NewError(1002, "network error")
|
||
// ErrSignature 签名失败
|
||
ErrSignature = NewError(1003, "signature error")
|
||
// ErrResponse 响应解析失败
|
||
ErrResponse = NewError(1004, "response error")
|
||
// ErrBusiness 业务错误(code != 0)
|
||
ErrBusiness = NewError(1005, "business error")
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
// File: intelligence_finance_v1/crypto.go
|
||
```go
|
||
package intelligence_finance_v1
|
||
|
||
import (
|
||
"crypto/hmac"
|
||
"crypto/rand"
|
||
"crypto/sha256"
|
||
"encoding/base64"
|
||
"fmt"
|
||
"math/big"
|
||
"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 进行签名,密钥为 secret,结果以 Base64 编码返回。
|
||
//
|
||
// 对应文档认证机制:客户应用使用 client-secret 对 (timestamp + nonce) 进行签名。
|
||
func HmacSHA256Base64(secret, data string) string {
|
||
mac := hmac.New(sha256.New, []byte(secret))
|
||
mac.Write([]byte(data))
|
||
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||
}
|
||
|
||
// BuildSignature 根据文档认证机制生成签名。
|
||
//
|
||
// 签名算法:HmacSHA256
|
||
// 密钥:client-secret
|
||
// 签名数据:x-bfl-signature-timestamp + x-bfl-signature-nonce
|
||
// 结果编码:Base64
|
||
func BuildSignature(clientSecret, timestamp, nonce string) string {
|
||
return HmacSHA256Base64(clientSecret, timestamp+nonce)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
// File: intelligence_finance_v1/types.go
|
||
```go
|
||
package intelligence_finance_v1
|
||
|
||
// 发票类型枚举
|
||
const (
|
||
InvoiceTypeSpecial = 1 // 专用发票
|
||
InvoiceTypeNormal = 2 // 普通发票
|
||
InvoiceTypeNormalElectronic = 3 // 普通发票(电子)
|
||
InvoiceTypeSpecialElec = 4 // 专用发票(电子)
|
||
InvoiceTypeDigitalSpecial = 8 // 数电专票
|
||
InvoiceTypeDigitalNormal = 9 // 数电普票
|
||
)
|
||
|
||
// 开票状态枚举
|
||
const (
|
||
InvoiceStatusNotInvoiced = 0 // 未开票
|
||
InvoiceStatusInvoicing = 1 // 开票中
|
||
InvoiceStatusPartialFailed = 2 // 部分失败
|
||
InvoiceStatusSuccess = 3 // 开票成功
|
||
InvoiceStatusFailed = 4 // 开票失败
|
||
InvoiceStatusPartialNotDone = 5 // 部分未开
|
||
InvoiceStatusNoDigitalAccount = 6 // 未配置数电账号
|
||
InvoiceStatusNoAutoConfig = 7 // 未配置自动开票配置
|
||
)
|
||
|
||
// 支付状态枚举
|
||
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" // 对私银行卡
|
||
)
|
||
|
||
// CommonResponse 通用响应结构。
|
||
type CommonResponse struct {
|
||
Code int `json:"code"` // 响应码
|
||
Msg string `json:"msg"` // 响应信息
|
||
Data any `json:"data"` // 响应数据
|
||
}
|
||
|
||
// InvoiceRequest 订单开票请求参数。
|
||
type InvoiceRequest struct {
|
||
CompanyCode string `json:"companyCode"` // 开票的企业主体编码
|
||
OrderID string `json:"orderId"` // 订单唯一标识
|
||
InvoiceType int `json:"invoiceType"` // 发票类型枚举
|
||
Products []InvoiceProduct `json:"products"` // 货物/服务明细列表
|
||
Remark string `json:"remark,omitempty"` // 订单备注
|
||
Purchaser string `json:"purchaser"` // 购方企业名称
|
||
TaxNum string `json:"taxnum,omitempty"` // 购方纳税人识别号
|
||
PurchaserAddr string `json:"purchaserAddress,omitempty"` // 购方地址
|
||
PurchaserTel string `json:"purchaserTel,omitempty"` // 购方电话
|
||
BankName string `json:"bankName,omitempty"` // 购方开户行名称
|
||
BankAccount string `json:"bankAccount,omitempty"` // 购方银行账号
|
||
Phone string `json:"phone,omitempty"` // 收票人手机号
|
||
Email string `json:"email,omitempty"` // 收票人邮箱
|
||
ApplyPerson string `json:"applyPerson,omitempty"` // 开票申请人名称
|
||
Payee string `json:"payee,omitempty"` // 收款人
|
||
Reviewer string `json:"reviewer,omitempty"` // 复核人
|
||
InvoiceRemark string `json:"invoiceRemark,omitempty"` // 发票备注栏内容
|
||
NaturalPerson string `json:"naturalPerson,omitempty"` // 购买方自然人标识
|
||
AdditionInfo string `json:"additionInfo,omitempty"` // 附加信息(JSON数组字符串)
|
||
}
|
||
|
||
// InvoiceProduct 订单开票货物/服务明细项。
|
||
type InvoiceProduct struct {
|
||
ProductName string `json:"productName"` // 货物或服务名称
|
||
RevenueCode string `json:"revenueCode"` // 19位税收分类编码
|
||
AmountIncludeTax float64 `json:"amountIncludeTax"` // 单条明细含税总金额(元)
|
||
Specs string `json:"specs,omitempty"` // 规格型号
|
||
Unit string `json:"unit,omitempty"` // 计量单位
|
||
Quantity float64 `json:"quantity"` // 数量
|
||
Discount float64 `json:"discount,omitempty"` // 折扣金额
|
||
TaxSign int `json:"taxSign,omitempty"` // 是否含税:0-不含税;1-含税
|
||
TaxRate float64 `json:"taxRate,omitempty"` // 税率(小数形式)
|
||
}
|
||
|
||
// InvoiceResponse 订单开票响应参数。
|
||
type InvoiceResponse struct {
|
||
Status int `json:"status"` // 开票状态
|
||
ErrorMsg string `json:"errorMsg"` // 错误信息
|
||
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"` // 电子发票PDF地址
|
||
OfdURL string `json:"ofdUrl,omitempty"` // 电子发票OFD地址
|
||
XmlURL string `json:"xmlUrl,omitempty"` // 电子发票XML地址
|
||
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"` // 单价
|
||
}
|
||
|
||
// InvoiceStatusQueryRequest 开票状态查询请求参数。
|
||
type InvoiceStatusQueryRequest struct {
|
||
OrderID string `json:"orderId"` // 订单唯一标识
|
||
}
|
||
|
||
// InvoiceStatusQueryResponse 开票状态查询响应参数。
|
||
type InvoiceStatusQueryResponse struct {
|
||
Status int `json:"status"` // 开票状态
|
||
Message string `json:"message"` // 状态描述
|
||
Data []InvoiceData `json:"data"` // 发票详细列表
|
||
}
|
||
|
||
// PaymentOrderRequest 创建付款单据请求参数。
|
||
type PaymentOrderRequest struct {
|
||
Code string `json:"code"` // 单据编码
|
||
YidaAppType string `json:"yidaAppType,omitempty"` // 宜搭应用类型
|
||
EmpAccountUserID string `json:"empAccountUserId,omitempty"` // 员工账户用户ID
|
||
Department *Department `json:"department,omitempty"` // 部门信息
|
||
Usage string `json:"usage,omitempty"` // 用途
|
||
PaymentUserID string `json:"paymentUserId,omitempty"` // 付款用户ID
|
||
Customer *Customer `json:"customer,omitempty"` // 客户信息
|
||
PrincipalID string `json:"principalId,omitempty"` // 负责人ID
|
||
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"` // 付款用户ID列表字符串
|
||
NeedPayment bool `json:"needPayment,omitempty"` // 是否需要付款
|
||
PaymentDetailListJSONStr string `json:"paymentDetailListJsonStr,omitempty"` // 付款明细JSON字符串
|
||
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"` // 用户ID
|
||
OccurDate int64 `json:"occurDate,omitempty"` // 发生日期(时间戳)
|
||
Product *Product `json:"product,omitempty"` // 商品信息
|
||
YidaFormUUID string `json:"yidaFormUuid,omitempty"` // 宜搭表单UUID
|
||
CanEditPaymentInfo bool `json:"canEditPaymentInfo,omitempty"` // 是否可编辑付款信息
|
||
PaymentUserIDList []string `json:"paymentUserIdList,omitempty"` // 付款用户ID列表
|
||
YidaProcInsID string `json:"yidaProcInsId,omitempty"` // 宜搭流程实例ID
|
||
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"` // 负责人ID
|
||
Tax string `json:"tax,omitempty"` // 税额
|
||
}
|
||
|
||
// InvoiceInfo 付款明细发票信息。
|
||
type InvoiceInfo struct {
|
||
InvoiceNo string `json:"invoiceNo,omitempty"` // 发票号码
|
||
InvoiceCode string `json:"invoiceCode,omitempty"` // 发票代码
|
||
}
|
||
|
||
// PaymentOrderResponse 创建付款单据响应参数。
|
||
type PaymentOrderResponse struct {
|
||
Code string `json:"code"` // 单据唯一编码
|
||
}
|
||
|
||
// PaymentNotifyRequest 支付完成通知数据(接口4)。
|
||
type PaymentNotifyRequest struct {
|
||
Code string `json:"code"` // 单据编码
|
||
InstanceID string `json:"instanceId"` // 实例ID
|
||
CorpID string `json:"corpId"` // 企业ID
|
||
PaymentStatus string `json:"paymentStatus"` // 支付状态
|
||
PaymentTime string `json:"paymentTime"` // 支付时间
|
||
UserID string `json:"userId"` // 用户ID
|
||
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"` // 账户类型
|
||
}
|
||
|
||
// PaymentStatusQueryRequest 支付状态查询请求参数。
|
||
type PaymentStatusQueryRequest struct {
|
||
Code string `json:"code"` // 单据编码
|
||
UserID string `json:"userId"` // 用户ID
|
||
}
|
||
|
||
// PaymentStatusQueryResponse 支付状态查询响应参数(与支付通知数据结构一致)。
|
||
type PaymentStatusQueryResponse struct {
|
||
Code string `json:"code"` // 单据编码
|
||
InstanceID string `json:"instanceId"` // 实例ID
|
||
CorpID string `json:"corpId"` // 企业ID
|
||
PaymentStatus string `json:"paymentStatus"` // 支付状态
|
||
PaymentTime string `json:"paymentTime"` // 支付时间
|
||
UserID string `json:"userId"` // 用户ID
|
||
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"` // 金额
|
||
}
|
||
|
||
// Notification 通用通知数据(bizType/bizId/data)。
|
||
type Notification struct {
|
||
BizType string `json:"bizType"` // 业务类型
|
||
BizID string `json:"bizId"` // 业务ID
|
||
Data string `json:"data"` // 业务数据
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
// File: intelligence_finance_v1/client.go
|
||
```go
|
||
package intelligence_finance_v1
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"time"
|
||
)
|
||
|
||
// Client 是智能财务业财连接 SDK 的客户端。
|
||
type Client struct {
|
||
// baseURL 平台接口基础地址
|
||
baseURL string
|
||
// tenantID 平台分配的租户唯一标识
|
||
tenantID string
|
||
// clientID 平台分配的应用标识
|
||
clientID string
|
||
// clientSecret 平台分配的密钥
|
||
clientSecret string
|
||
// httpClient HTTP 客户端
|
||
httpClient *http.Client
|
||
// signEnabled 是否启用签名(钉钉AI表格无需配置签名,可关闭)
|
||
signEnabled bool
|
||
}
|
||
|
||
// ClientOption 客户端配置选项。
|
||
type ClientOption func(*Client)
|
||
|
||
// WithHTTPClient 自定义 HTTP 客户端。
|
||
func WithHTTPClient(hc *http.Client) ClientOption {
|
||
return func(c *Client) {
|
||
c.httpClient = hc
|
||
}
|
||
}
|
||
|
||
// WithSignEnabled 设置是否启用签名(默认启用)。
|
||
func WithSignEnabled(enabled bool) ClientOption {
|
||
return func(c *Client) {
|
||
c.signEnabled = enabled
|
||
}
|
||
}
|
||
|
||
// NewClient 创建一个新的智能财务业财连接客户端。
|
||
//
|
||
// 参数:
|
||
// - baseURL:平台接口基础地址
|
||
// - 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: 30 * time.Second},
|
||
signEnabled: true,
|
||
}
|
||
for _, opt := range opts {
|
||
opt(c)
|
||
}
|
||
return c
|
||
}
|
||
|
||
// doRequest 发送 HTTP 请求并解析响应。
|
||
func (c *Client) doRequest(ctx context.Context, path string, reqBody any, respBody any) error {
|
||
bodyBytes, err := json.Marshal(reqBody)
|
||
if err != nil {
|
||
return WrapError(ErrSignature.Code, "marshal request body failed", err)
|
||
}
|
||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, bytes.NewReader(bodyBytes))
|
||
if err != nil {
|
||
return WrapError(ErrNetwork.Code, "create request failed", err)
|
||
}
|
||
|
||
// 设置公共请求头
|
||
req.Header.Set("Content-Type", "application/json")
|
||
req.Header.Set("tenant-id", c.tenantID)
|
||
req.Header.Set("client-id", c.clientID)
|
||
|
||
// 签名(钉钉AI表格无需配置,可通过 WithSignEnabled(false) 关闭)
|
||
if c.signEnabled {
|
||
timestamp := GenerateTimestamp()
|
||
nonce, err := GenerateNonce(16)
|
||
if err != nil {
|
||
return WrapError(ErrSignature.Code, "generate nonce failed", err)
|
||
}
|
||
req.Header.Set("x-bfl-signature-timestamp", timestamp)
|
||
req.Header.Set("x-bfl-signature-nonce", nonce)
|
||
req.Header.Set("x-bfl-signature", BuildSignature(c.clientSecret, timestamp, nonce))
|
||
}
|
||
|
||
resp, err := c.httpClient.Do(req)
|
||
if err != nil {
|
||
return WrapError(ErrNetwork.Code, "do request failed", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
respBytes, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return WrapError(ErrResponse.Code, "read response body failed", err)
|
||
}
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
return WrapError(ErrResponse.Code, fmt.Sprintf("unexpected http status: %d", resp.StatusCode), nil)
|
||
}
|
||
|
||
if err := json.Unmarshal(respBytes, respBody); err != nil {
|
||
return WrapError(ErrResponse.Code, "unmarshal response body failed", err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// doRequestCommon 发送请求并解析通用响应结构(code/msg/data)。
|
||
func (c *Client) doRequestCommon(ctx context.Context, path string, reqBody any, data any) error {
|
||
var common CommonResponse
|
||
if err := c.doRequest(ctx, path, reqBody, &common); err != nil {
|
||
return err
|
||
}
|
||
if common.Code != 0 {
|
||
return WrapError(common.Code, common.Msg, nil)
|
||
}
|
||
if data != nil && common.Data != nil {
|
||
// 将 data 重新序列化后反序列化到目标结构
|
||
dataBytes, err := json.Marshal(common.Data)
|
||
if err != nil {
|
||
return WrapError(ErrResponse.Code, "marshal data failed", err)
|
||
}
|
||
if err := json.Unmarshal(dataBytes, data); err != nil {
|
||
return WrapError(ErrResponse.Code, "unmarshal data failed", err)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// CreateInvoice 提交订单开票请求(接口1)。
|
||
//
|
||
// 请求方法:POST
|
||
// 描述:提交订单开票请求。
|
||
func (c *Client) CreateInvoice(ctx context.Context, req *InvoiceRequest) (*InvoiceResponse, error) {
|
||
var resp InvoiceResponse
|
||
if err := c.doRequestCommon(ctx, "/invoice/create", req, &resp); err != nil {
|
||
return nil, err
|
||
}
|
||
return &resp, nil
|
||
}
|
||
|
||
// QueryInvoiceStatus 查询订单开票状态(接口2)。
|
||
//
|
||
// 请求方法:POST
|
||
// 描述:查询订单开票状态。
|
||
func (c *Client) QueryInvoiceStatus(ctx context.Context, req *InvoiceStatusQueryRequest) (*InvoiceStatusQueryResponse, error) {
|
||
var resp InvoiceStatusQueryResponse
|
||
if err := c.doRequestCommon(ctx, "/invoice/status", req, &resp); err != nil {
|
||
return nil, err
|
||
}
|
||
return &resp, nil
|
||
}
|
||
|
||
// CreatePaymentOrder 创建付款单据(接口3)。
|
||
//
|
||
// 请求方法:POST
|
||
// 描述:创建付款单据。
|
||
func (c *Client) CreatePaymentOrder(ctx context.Context, req *PaymentOrderRequest) (*PaymentOrderResponse, error) {
|
||
var resp PaymentOrderResponse
|
||
if err := c.doRequestCommon(ctx, "/payment/order/create", req, &resp); err != nil {
|
||
return nil, err
|
||
}
|
||
return &resp, nil
|
||
}
|
||
|
||
// QueryPaymentStatus 查询支付状态(接口5)。
|
||
//
|
||
// 请求方法:POST
|
||
// 描述:查询支付状态,响应数据与支付通知一致。
|
||
func (c *Client) QueryPaymentStatus(ctx context.Context, req *PaymentStatusQueryRequest) (*PaymentStatusQueryResponse, error) {
|
||
var resp PaymentStatusQueryResponse
|
||
if err := c.doRequestCommon(ctx, "/payment/status", req, &resp); err != nil {
|
||
return nil, err
|
||
}
|
||
return &resp, nil
|
||
}
|
||
|
||
// HandlePaymentNotify 处理支付完成通知(接口4,平台回调客户接口)。
|
||
//
|
||
// 该方法用于客户服务端接收平台回调,解析通知数据并返回响应字符串。
|
||
// 响应 BODY 直接返回字符串:SUCCESS 表示成功,FAILED 表示失败。
|
||
func HandlePaymentNotify(body []byte) (*PaymentNotifyRequest, error) {
|
||
var req PaymentNotifyRequest
|
||
if err := json.Unmarshal(body, &req); err != nil {
|
||
return nil, WrapError(ErrResponse.Code, "unmarshal payment notify failed", err)
|
||
}
|
||
return &req, nil
|
||
}
|
||
|
||
// HandleNotification 处理通用通知(bizType/bizId/data)。
|
||
//
|
||
// 该方法用于客户服务端接收平台通用通知,解析通知数据。
|
||
func HandleNotification(body []byte) (*Notification, error) {
|
||
var n Notification
|
||
if err := json.Unmarshal(body, &n); err != nil {
|
||
return nil, WrapError(ErrResponse.Code, "unmarshal notification failed", err)
|
||
}
|
||
return &n, nil
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
// File: intelligence_finance_v1/example_test.go
|
||
```go
|
||
package intelligence_finance_v1
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"net/http"
|
||
"testing"
|
||
)
|
||
|
||
// ExampleNewClient 展示如何创建客户端并提交订单开票请求。
|
||
func ExampleNewClient() {
|
||
// 创建客户端
|
||
client := NewClient(
|
||
"https://api.example.com",
|
||
"your-tenant-id",
|
||
"dd-ai-table",
|
||
"your-client-secret",
|
||
)
|
||
|
||
// 构建订单开票请求
|
||
req := &InvoiceRequest{
|
||
OrderID: "ORDER20240101001",
|
||
InvoiceType: InvoiceTypeDigitalSpecial, // 数电专票
|
||
Purchaser: "示例科技有限公司",
|
||
TaxNum: "91110000XXXXXXXXXX",
|
||
Email: "finance@example.com",
|
||
Phone: "13800000000",
|
||
Products: []InvoiceProduct{
|
||
{
|
||
ProductName: "软件开发服务",
|
||
RevenueCode: "1090101000000000000",
|
||
AmountIncludeTax: 11300,
|
||
Quantity: 1,
|
||
TaxSign: 1,
|
||
TaxRate: 0.13,
|
||
},
|
||
},
|
||
}
|
||
|
||
resp, err := client.CreateInvoice(context.Background(), req)
|
||
if err != nil {
|
||
fmt.Printf("create invoice failed: %v\n", err)
|
||
return
|
||
}
|
||
fmt.Printf("invoice status: %d\n", resp.Status)
|
||
}
|
||
|
||
// ExampleClient_QueryInvoiceStatus 展示如何查询开票状态。
|
||
func ExampleClient_QueryInvoiceStatus() {
|
||
client := NewClient(
|
||
"https://api.example.com",
|
||
"your-tenant-id",
|
||
"dd-ai-table",
|
||
"your-client-secret",
|
||
)
|
||
|
||
resp, err := client.QueryInvoiceStatus(context.Background(), &InvoiceStatusQueryRequest{
|
||
OrderID: "ORDER20240101001",
|
||
})
|
||
if err != nil {
|
||
fmt.Printf("query invoice status failed: %v\n", err)
|
||
return
|
||
}
|
||
fmt.Printf("status: %d, message: %s\n", resp.Status, resp.Message)
|
||
}
|
||
|
||
// ExampleClient_CreatePaymentOrder 展示如何创建付款单据。
|
||
func ExampleClient_CreatePaymentOrder() {
|
||
client := NewClient(
|
||
"https://api.example.com",
|
||
"your-tenant-id",
|
||
"dd-ai-table",
|
||
"your-client-secret",
|
||
)
|
||
|
||
req := &PaymentOrderRequest{
|
||
Code: "PAY20240101001",
|
||
UserID: "user-001",
|
||
Title: "供应商货款",
|
||
Amount: "11300.00",
|
||
Supplier: &Supplier{
|
||
Name: "示例供应商有限公司",
|
||
},
|
||
Company: &Company{
|
||
Name: "示例科技有限公司",
|
||
},
|
||
PaymentDetailList: []PaymentDetail{
|
||
{
|
||
Amount: "11300.00",
|
||
InvoiceInfo: &InvoiceInfo{
|
||
InvoiceNo: "12345678",
|
||
InvoiceCode: "011002000111",
|
||
},
|
||
},
|
||
},
|
||
}
|
||
|
||
resp, err := client.CreatePaymentOrder(context.Background(), req)
|
||
if err != nil {
|
||
fmt.Printf("create payment order failed: %v\n", err)
|
||
return
|
||
}
|
||
fmt.Printf("payment order code: %s\n", resp.Code)
|
||
}
|
||
|
||
// ExampleClient_QueryPaymentStatus 展示如何查询支付状态。
|
||
func ExampleClient_QueryPaymentStatus() {
|
||
client := NewClient(
|
||
"https://api.example.com",
|
||
"your-tenant-id",
|
||
"dd-ai-table",
|
||
"your-client-secret",
|
||
)
|
||
|
||
resp, err := client.QueryPaymentStatus(context.Background(), &PaymentStatusQueryRequest{
|
||
Code: "PAY20240101001",
|
||
UserID: "user-001",
|
||
})
|
||
if err != nil {
|
||
fmt.Printf("query payment status failed: %v\n", err)
|
||
return
|
||
}
|
||
fmt.Printf("payment status: %s\n", resp.PaymentStatus)
|
||
}
|
||
|
||
// ExampleHandlePaymentNotify 展示如何在客户服务端处理支付完成通知。
|
||
func ExampleHandlePaymentNotify() {
|
||
// 模拟平台回调的请求体
|
||
body := []byte(`{
|
||
"code": "PAY20240101001",
|
||
"instanceId": "inst-001",
|
||
"corpId": "corp-001",
|
||
"paymentStatus": "SUCCESS",
|
||
"paymentTime": "2024-01-01 12:00:00",
|
||
"userId": "user-001",
|
||
"amount": "11300.00",
|
||
"source": "openapi"
|
||
}`)
|
||
|
||
notify, err := HandlePaymentNotify(body)
|
||
if err != nil {
|
||
fmt.Printf("handle payment notify failed: %v\n", err)
|
||
return
|
||
}
|
||
|
||
// 处理业务逻辑...
|
||
|
||
// 返回响应字符串
|
||
if notify.PaymentStatus == PaymentStatusSuccess {
|
||
fmt.Println("SUCCESS")
|
||
} else {
|
||
fmt.Println("FAILED")
|
||
}
|
||
}
|
||
|
||
// TestSignature 验证签名算法。
|
||
func TestSignature(t *testing.T) {
|
||
secret := "test-secret"
|
||
timestamp := "1700000000"
|
||
nonce := "abc123"
|
||
sig := BuildSignature(secret, timestamp, nonce)
|
||
if sig == "" {
|
||
t.Fatal("signature should not be empty")
|
||
}
|
||
t.Logf("signature: %s", sig)
|
||
}
|
||
|
||
// TestHandlePaymentNotify 测试支付通知解析。
|
||
func TestHandlePaymentNotify(t *testing.T) {
|
||
body := []byte(`{"code":"PAY001","instanceId":"i1","corpId":"c1","paymentStatus":"SUCCESS","paymentTime":"2024-01-01 12:00:00","userId":"u1"}`)
|
||
n, err := HandlePaymentNotify(body)
|
||
if err != nil {
|
||
t.Fatalf("handle payment notify failed: %v", err)
|
||
}
|
||
if n.Code != "PAY001" {
|
||
t.Fatalf("unexpected code: %s", n.Code)
|
||
}
|
||
}
|
||
|
||
// TestHandleNotification 测试通用通知解析。
|
||
func TestHandleNotification(t *testing.T) {
|
||
body := []byte(`{"bizType":"invoice","bizId":"ORDER001","data":"{\"status\":3}"}`)
|
||
n, err := HandleNotification(body)
|
||
if err != nil {
|
||
t.Fatalf("handle notification failed: %v", err)
|
||
}
|
||
if n.BizType != "invoice" {
|
||
t.Fatalf("unexpected bizType: %s", n.BizType)
|
||
}
|
||
}
|
||
|
||
// TestJSONMarshal 测试请求体 JSON 序列化。
|
||
func TestJSONMarshal(t *testing.T) {
|
||
req := &InvoiceRequest{
|
||
OrderID: "ORDER001",
|
||
InvoiceType: 9,
|
||
Purchaser: "测试公司",
|
||
Products: []InvoiceProduct{
|
||
{ProductName: "服务", RevenueCode: "1090101000000000000", AmountIncludeTax: 100, Quantity: 1},
|
||
},
|
||
}
|
||
b, err := json.Marshal(req)
|
||
if err != nil {
|
||
t.Fatalf("marshal failed: %v", err)
|
||
}
|
||
t.Logf("request json: %s", string(b))
|
||
}
|
||
|
||
// TestHTTPHandler 展示如何将通知处理接入 HTTP 服务。
|
||
func TestHTTPHandler(t *testing.T) {
|
||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
var body []byte
|
||
_, _ = r.Body.Read(body)
|
||
notify, err := HandlePaymentNotify(body)
|
||
if err != nil {
|
||
w.Write([]byte("FAILED"))
|
||
return
|
||
}
|
||
_ = notify
|
||
w.Write([]byte("SUCCESS"))
|
||
})
|
||
_ = handler
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
=== SDK 生成完成 ===
|
||
|
||
## 加密实现
|
||
|
||
以下是从加密工具获取的完整实现:
|
||
|
||
### 加密实现 1
|
||
|
||
{
|
||
"success": true,
|
||
"data": "\n### 参数拼接规范\n\n**常见拼接方式**:\n\n1. **字典序排序**:按key字典序排序后拼接 `key=value\u0026key2=value2`\n2. **固定顺序**:按文档指定顺序拼接\n3. **JSON字符串**:整个请求体作为签名串\n\n**代码模板**:\n\n```go\npackage crypto\n\nimport (\n \"fmt\"\n \"sort\"\n \"strings\"\n)\n\n// BuildSignString 方式1:字典序排序拼接\nfunc BuildSignString(params map[string]string) string {\n keys := make([]string, 0, len(params))\n for k, v := range params {\n if v != \"\" \u0026\u0026 k != \"sign\" \u0026\u0026 k != \"signature\" {\n keys = append(keys, k)\n }\n }\n sort.Strings(keys)\n \n var parts []string\n for _, k := range keys {\n parts = append(parts, fmt.Sprintf(\"%s=%s\", k, params[k]))\n }\n return strings.Join(parts, \"\u0026\")\n}\n\n// BuildSignStringOrdered 方式2:固定顺序拼接\nfunc BuildSignStringOrdered(params map[string]string, orderedKeys []string) string {\n var parts []string\n for _, k := range orderedKeys {\n if v, ok := params[k]; ok \u0026\u0026 v != \"\" {\n parts = append(parts, fmt.Sprintf(\"%s=%s\", k, v))\n }\n }\n return strings.Join(parts, \"\u0026\")\n}\n```\n\n**注意事项**:\n- 确认文档指定的排序规则(字典序/固定顺序)\n- 确认空值是否要包含(通常排除空值)\n- 确认是否需要URL编码\n- 注意排除签名字段本身\n",
|
||
"tool": "param_concat"
|
||
}
|
||
|
||
### 加密实现 2
|
||
|
||
{
|
||
"success": true,
|
||
"data": "\n### 时间戳和随机数生成规范\n\n**代码模板**:\n\n```go\npackage crypto\n\nimport (\n \"crypto/rand\"\n \"fmt\"\n \"math/big\"\n \"time\"\n)\n\n// GenerateTimestamp 生成秒级时间戳\nfunc GenerateTimestamp() string {\n return fmt.Sprintf(\"%d\", time.Now().Unix())\n}\n\n// GenerateTimestampMillis 生成毫秒级时间戳\nfunc GenerateTimestampMillis() string {\n return fmt.Sprintf(\"%d\", time.Now().UnixMilli())\n}\n\n// GenerateNonce 生成指定长度的随机字符串(加密安全)\nfunc GenerateNonce(length int) (string, error) {\n const charset = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\"\n b := make([]byte, length)\n for i := range b {\n num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))\n if err != nil {\n return \"\", err\n }\n b[i] = charset[num.Int64()]\n }\n return string(b), nil\n}\n```\n\n**注意事项**:\n- 确认文档要求的是秒级还是毫秒级时间戳\n- 确认nonce的长度要求(通常16-32位)\n- 生产环境建议使用加密安全的随机数生成器\n",
|
||
"tool": "nonce_timestamp"
|
||
}
|
||
|