添加文件: intelligence_finance_v1_ga/generate.md
This commit is contained in:
parent
61f6b48624
commit
5c814966af
|
|
@ -0,0 +1,802 @@
|
||||||
|
// File: intelligence_finance_v1_ga/go.mod
|
||||||
|
```
|
||||||
|
module intelligence_finance_v1_ga
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
```
|
||||||
|
|
||||||
|
// File: intelligence_finance_v1_ga/crypto.go
|
||||||
|
```go
|
||||||
|
package intelligence_finance_v1_ga
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateTimestamp 生成秒级时间戳。
|
||||||
|
func GenerateTimestamp() string {
|
||||||
|
return fmt.Sprintf("%d", time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateTimestampMillis 生成毫秒级时间戳。
|
||||||
|
func GenerateTimestampMillis() string {
|
||||||
|
return fmt.Sprintf("%d", time.Now().UnixMilli())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateNonce 生成指定长度的随机字符串(加密安全)。
|
||||||
|
func GenerateNonce(length int) (string, error) {
|
||||||
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
b := make([]byte, length)
|
||||||
|
for i := range b {
|
||||||
|
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
b[i] = charset[num.Int64()]
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, "&")
|
||||||
|
}
|
||||||
|
|
||||||
|
// HmacSHA256Base64 使用 HmacSHA256 算法对 data 进行签名,并返回 Base64 编码结果。
|
||||||
|
// 密钥为 client-secret,签名数据为 x-bfl-signature-timestamp + x-bfl-signature-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))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
// File: intelligence_finance_v1_ga/errors.go
|
||||||
|
```go
|
||||||
|
package intelligence_finance_v1_ga
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// APIError 表示 API 调用返回的错误。
|
||||||
|
type APIError struct {
|
||||||
|
// StatusCode HTTP 状态码。
|
||||||
|
StatusCode int
|
||||||
|
// Code 业务错误码。
|
||||||
|
Code int
|
||||||
|
// Message 错误信息。
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error 实现 error 接口。
|
||||||
|
func (e *APIError) Error() string {
|
||||||
|
if e.Code != 0 {
|
||||||
|
return fmt.Sprintf("api error: code=%d, msg=%s", e.Code, e.Message)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("api error: http status=%d, body=%s", e.StatusCode, e.Message)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
// File: intelligence_finance_v1_ga/types.go
|
||||||
|
```go
|
||||||
|
package intelligence_finance_v1_ga
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
// ==================== 枚举常量 ====================
|
||||||
|
|
||||||
|
// 发票类型枚举。
|
||||||
|
const (
|
||||||
|
InvoiceTypeSpecial = 1 // 专用发票
|
||||||
|
InvoiceTypeNormal = 2 // 普通发票
|
||||||
|
InvoiceTypeNormalElec = 3 // 普通发票(电子)
|
||||||
|
InvoiceTypeSpecialElec = 4 // 专用发票(电子)
|
||||||
|
InvoiceTypeDigitalSpecial = 8 // 数电专票
|
||||||
|
InvoiceTypeDigitalNormal = 9 // 数电普票
|
||||||
|
)
|
||||||
|
|
||||||
|
// 开票状态枚举。
|
||||||
|
const (
|
||||||
|
InvoiceStatusNotIssued = 0 // 未开票
|
||||||
|
InvoiceStatusIssuing = 1 // 开票中
|
||||||
|
InvoiceStatusPartialFail = 2 // 部分失败
|
||||||
|
InvoiceStatusSuccess = 3 // 开票成功
|
||||||
|
InvoiceStatusFail = 4 // 开票失败
|
||||||
|
InvoiceStatusPartialNot = 5 // 部分未开
|
||||||
|
InvoiceStatusNoDigitalAcct = 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" // 对私银行卡
|
||||||
|
)
|
||||||
|
|
||||||
|
// 通知响应常量。
|
||||||
|
const (
|
||||||
|
NotifyResponseSuccess = "SUCCESS" // 通知处理成功
|
||||||
|
NotifyResponseFailed = "FAILED" // 通知处理失败
|
||||||
|
)
|
||||||
|
|
||||||
|
// ==================== 通用结构 ====================
|
||||||
|
|
||||||
|
// CommonResponse 通用响应结构。
|
||||||
|
type CommonResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data json.RawMessage `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommonNotifyData 通用通知数据结构(bizType/bizId/data)。
|
||||||
|
type CommonNotifyData struct {
|
||||||
|
BizType string `json:"bizType"`
|
||||||
|
BizID string `json:"bizId"`
|
||||||
|
Data string `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 接口1:订单开票 ====================
|
||||||
|
|
||||||
|
// OrderInvoiceRequest 订单开票请求参数。
|
||||||
|
type OrderInvoiceRequest struct {
|
||||||
|
CompanyCode string `json:"companyCode,omitempty"` // 开票的企业主体编码,不传则默认主体开票
|
||||||
|
OrderID string `json:"orderId"` // 订单唯一标识
|
||||||
|
InvoiceType int `json:"invoiceType"` // 发票类型枚举
|
||||||
|
Products []Product `json:"products"` // 货物/服务明细列表,至少一项
|
||||||
|
Remark string `json:"remark,omitempty"` // 订单备注(非发票备注)
|
||||||
|
Purchaser string `json:"purchaser"` // 购方企业名称
|
||||||
|
Taxnum string `json:"taxnum,omitempty"` // 购方纳税人识别号
|
||||||
|
PurchaserAddress 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"` // 购买方自然人标识:Y-是,N-否(默认N)
|
||||||
|
AdditionInfo string `json:"additionInfo,omitempty"` // 附加信息(JSON数组字符串)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Product 货物/服务明细项。
|
||||||
|
type Product struct {
|
||||||
|
ProductName string `json:"productName"` // 货物或服务名称
|
||||||
|
RevenueCode string `json:"revenueCode"` // 19位税收分类编码
|
||||||
|
AmountIncludeTax string `json:"amountIncludeTax"` // 单条明细含税总金额(单位:元)
|
||||||
|
Specs string `json:"specs,omitempty"` // 规格型号
|
||||||
|
Unit string `json:"unit,omitempty"` // 计量单位(如:台、个、次)
|
||||||
|
Quantity string `json:"quantity"` // 数量
|
||||||
|
Discount string `json:"discount,omitempty"` // 折扣金额(无折扣传0)
|
||||||
|
TaxSign int `json:"taxSign,omitempty"` // 是否含税:0-不含税;1-含税(默认建议传1)
|
||||||
|
TaxRate string `json:"taxRate,omitempty"` // 税率(小数形式,如0.13表示13%)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrderInvoiceResponse 订单开票响应参数。
|
||||||
|
type OrderInvoiceResponse 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"` // 开票类型:0-正数票;1-负数票(红冲)
|
||||||
|
ListFlag string `json:"listFlag"` // 清单标识:0-无清单;1-有清单
|
||||||
|
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"` // 购买方自然人标识:Y-是;N-否
|
||||||
|
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"` // 开票日期(格式:yyyy-MM-dd HH:mm:ss)
|
||||||
|
InvoiceCode string `json:"invoiceCode"` // 发票代码
|
||||||
|
InvoiceNo string `json:"invoiceNo"` // 发票号码
|
||||||
|
InvoiceStatus string `json:"invoiceStatus"` // 发票状态:1-正常;2-已红冲;3-已作废
|
||||||
|
LayoutFileURL string `json:"layoutFileUrl,omitempty"` // 电子发票地址(PDF/OFD)
|
||||||
|
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 []Detail `json:"details"` // 商品明细列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detail 商品明细。
|
||||||
|
type Detail 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"` // 税收分类编码(19位)
|
||||||
|
ItemType string `json:"itemType"` // 商品行性质:0-正常行;1-折扣行;2-被折扣行
|
||||||
|
ItemName string `json:"itemName"` // 商品简称
|
||||||
|
Specs string `json:"specs,omitempty"` // 商品规格型号
|
||||||
|
TaxFreePolicy string `json:"taxFreePolicy"` // 免税政策:1-免税;2-不征税;3-普通零税率
|
||||||
|
PreferentialPolicy string `json:"preferentialPolicy"` // 优惠政策类型
|
||||||
|
TaxRate string `json:"taxRate"` // 税率(小数形式,如0.13)
|
||||||
|
TaxSign string `json:"taxSign"` // 是否含税:0-否;1-是
|
||||||
|
Unit string `json:"unit,omitempty"` // 计量单位(如:台、个、次)
|
||||||
|
UnitPrice string `json:"unitPrice,omitempty"` // 单价
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 接口2:开票状态查询 ====================
|
||||||
|
|
||||||
|
// QueryInvoiceStatusRequest 开票状态查询请求参数。
|
||||||
|
type QueryInvoiceStatusRequest struct {
|
||||||
|
OrderID string `json:"orderId"` // 订单唯一标识
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryInvoiceStatusResponse 开票状态查询响应参数。
|
||||||
|
type QueryInvoiceStatusResponse struct {
|
||||||
|
Status int `json:"status"` // 开票状态
|
||||||
|
Message string `json:"message"` // 状态描述(如"开票成功")
|
||||||
|
Data []InvoiceData `json:"data"` // 发票详细列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 接口3:创建付款单据 ====================
|
||||||
|
|
||||||
|
// CreatePaymentOrderRequest 创建付款单据请求参数。
|
||||||
|
type CreatePaymentOrderRequest 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 *ProductInfo `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"` // 类别名称
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProductInfo 商品信息。
|
||||||
|
type ProductInfo 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"` // 发票代码
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePaymentOrderResponse 创建付款单据响应参数。
|
||||||
|
type CreatePaymentOrderResponse struct {
|
||||||
|
Code string `json:"code"` // 单据唯一编码
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 接口4:支付完成通知 / 接口5:支付状态查询 ====================
|
||||||
|
|
||||||
|
// PaymentNotifyData 支付完成通知数据(接口4通知数据,也是接口5的响应数据)。
|
||||||
|
type PaymentNotifyData 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"` // 账户类型
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryPaymentStatusRequest 支付状态查询请求参数。
|
||||||
|
type QueryPaymentStatusRequest struct {
|
||||||
|
Code string `json:"code"` // 单据编码
|
||||||
|
UserID string `json:"userId"` // 用户ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryPaymentStatusResponse 支付状态查询响应参数(与支付通知数据结构一致)。
|
||||||
|
type QueryPaymentStatusResponse = PaymentNotifyData
|
||||||
|
```
|
||||||
|
|
||||||
|
// File: intelligence_finance_v1_ga/client.go
|
||||||
|
```go
|
||||||
|
package intelligence_finance_v1_ga
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 接口路径常量。
|
||||||
|
// 文档未提供具体路径,接口短码在对接时分配,请根据实际分配结果修改以下常量。
|
||||||
|
const (
|
||||||
|
// PathOrderInvoice 订单开票接口路径。
|
||||||
|
PathOrderInvoice = "/invoice/order"
|
||||||
|
// PathQueryInvoiceStatus 开票状态查询接口路径。
|
||||||
|
PathQueryInvoiceStatus = "/invoice/status"
|
||||||
|
// PathCreatePaymentOrder 创建付款单据接口路径。
|
||||||
|
PathCreatePaymentOrder = "/payment/order"
|
||||||
|
// PathQueryPaymentStatus 支付状态查询接口路径。
|
||||||
|
PathQueryPaymentStatus = "/payment/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client 是业财连接接口的 SDK 客户端。
|
||||||
|
type Client struct {
|
||||||
|
baseURL string
|
||||||
|
tenantID string
|
||||||
|
clientID string
|
||||||
|
clientSecret string
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientOption 定义客户端配置选项。
|
||||||
|
type ClientOption func(*Client)
|
||||||
|
|
||||||
|
// WithHTTPClient 自定义 HTTP 客户端。
|
||||||
|
func WithHTTPClient(hc *http.Client) ClientOption {
|
||||||
|
return func(c *Client) { c.httpClient = hc }
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient 创建业财连接接口客户端。
|
||||||
|
// baseURL 为平台接口根地址;tenantID 为平台分配的租户唯一标识;
|
||||||
|
// clientID 为平台分配的应用标识(钉钉AI表格固定为 "dd-ai-table");
|
||||||
|
// clientSecret 为平台分配的密钥,用于 HmacSHA256 签名。
|
||||||
|
func NewClient(baseURL, tenantID, clientID, clientSecret string, opts ...ClientOption) *Client {
|
||||||
|
c := &Client{
|
||||||
|
baseURL: strings.TrimRight(baseURL, "/"),
|
||||||
|
tenantID: tenantID,
|
||||||
|
clientID: clientID,
|
||||||
|
clientSecret: clientSecret,
|
||||||
|
httpClient: &http.Client{Timeout: 30 * time.Second},
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// do 发送 POST 请求,自动生成时间戳、随机数并完成 HmacSHA256 签名。
|
||||||
|
// 若响应符合通用结构 {code, msg, data},则自动解包 data 到 respBody;
|
||||||
|
// 否则直接将整个响应体解析到 respBody。
|
||||||
|
func (c *Client) do(ctx context.Context, path string, reqBody, respBody interface{}) error {
|
||||||
|
bodyBytes, err := json.Marshal(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal request body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := GenerateTimestamp()
|
||||||
|
nonce, err := GenerateNonce(16)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("generate nonce: %w", err)
|
||||||
|
}
|
||||||
|
signature := HmacSHA256Base64(c.clientSecret, timestamp+nonce)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, bytes.NewReader(bodyBytes))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("new request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("tenant-id", c.tenantID)
|
||||||
|
req.Header.Set("client-id", c.clientID)
|
||||||
|
req.Header.Set("x-bfl-signature-timestamp", timestamp)
|
||||||
|
req.Header.Set("x-bfl-signature-nonce", nonce)
|
||||||
|
req.Header.Set("x-bfl-signature", signature)
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("do request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return &APIError{StatusCode: resp.StatusCode, Message: string(data)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试按通用响应结构 {code, msg, data} 解包
|
||||||
|
var common struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data json.RawMessage `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &common); err == nil && len(common.Data) > 0 {
|
||||||
|
if err := json.Unmarshal(common.Data, respBody); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接解析整个响应体
|
||||||
|
if err := json.Unmarshal(data, respBody); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal response body: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrderInvoice 提交订单开票请求。
|
||||||
|
func (c *Client) OrderInvoice(ctx context.Context, req *OrderInvoiceRequest) (*OrderInvoiceResponse, error) {
|
||||||
|
var resp OrderInvoiceResponse
|
||||||
|
if err := c.do(ctx, PathOrderInvoice, req, &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryInvoiceStatus 查询订单开票状态。
|
||||||
|
func (c *Client) QueryInvoiceStatus(ctx context.Context, req *QueryInvoiceStatusRequest) (*QueryInvoiceStatusResponse, error) {
|
||||||
|
var resp QueryInvoiceStatusResponse
|
||||||
|
if err := c.do(ctx, PathQueryInvoiceStatus, req, &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePaymentOrder 创建付款单据。
|
||||||
|
func (c *Client) CreatePaymentOrder(ctx context.Context, req *CreatePaymentOrderRequest) (*CreatePaymentOrderResponse, error) {
|
||||||
|
var resp CreatePaymentOrderResponse
|
||||||
|
if err := c.do(ctx, PathCreatePaymentOrder, req, &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryPaymentStatus 查询支付状态。
|
||||||
|
func (c *Client) QueryPaymentStatus(ctx context.Context, req *QueryPaymentStatusRequest) (*QueryPaymentStatusResponse, error) {
|
||||||
|
var resp QueryPaymentStatusResponse
|
||||||
|
if err := c.do(ctx, PathQueryPaymentStatus, req, &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePaymentNotify 解析支付完成通知(接口4)的请求体。
|
||||||
|
// 客户在回调接口中调用此方法解析通知数据,解析成功后应返回 "SUCCESS",失败返回 "FAILED"。
|
||||||
|
func ParsePaymentNotify(body []byte) (*PaymentNotifyData, error) {
|
||||||
|
var data PaymentNotifyData
|
||||||
|
if err := json.Unmarshal(body, &data); err != nil {
|
||||||
|
return nil, fmt.Errorf("parse payment notify: %w", err)
|
||||||
|
}
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseCommonNotify 解析通用通知(bizType/bizId/data)的请求体。
|
||||||
|
func ParseCommonNotify(body []byte) (*CommonNotifyData, error) {
|
||||||
|
var data CommonNotifyData
|
||||||
|
if err := json.Unmarshal(body, &data); err != nil {
|
||||||
|
return nil, fmt.Errorf("parse common notify: %w", err)
|
||||||
|
}
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
// File: intelligence_finance_v1_ga/example_test.go
|
||||||
|
```go
|
||||||
|
package intelligence_finance_v1_ga_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
sdk "intelligence_finance_v1_ga"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestOrderInvoice 演示订单开票接口的调用。
|
||||||
|
func TestOrderInvoice(t *testing.T) {
|
||||||
|
client := sdk.NewClient(
|
||||||
|
"https://api.example.com",
|
||||||
|
"your-tenant-id",
|
||||||
|
"dd-ai-table",
|
||||||
|
"your-client-secret",
|
||||||
|
)
|
||||||
|
|
||||||
|
req := &sdk.OrderInvoiceRequest{
|
||||||
|
OrderID: "ORDER-20240101-001",
|
||||||
|
InvoiceType: sdk.InvoiceTypeDigitalSpecial,
|
||||||
|
Purchaser: "示例科技有限公司",
|
||||||
|
Taxnum: "91330100MA27XXXXXX",
|
||||||
|
Email: "finance@example.com",
|
||||||
|
Phone: "13800000000",
|
||||||
|
Products: []sdk.Product{
|
||||||
|
{
|
||||||
|
ProductName: "软件开发服务",
|
||||||
|
RevenueCode: "3040201000000000000",
|
||||||
|
AmountIncludeTax: "10000.00",
|
||||||
|
Quantity: "1",
|
||||||
|
TaxSign: 1,
|
||||||
|
TaxRate: "0.06",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.OrderInvoice(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OrderInvoice failed: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("开票状态: %d, 错误信息: %s\n", resp.Status, resp.ErrorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestQueryInvoiceStatus 演示开票状态查询接口的调用。
|
||||||
|
func TestQueryInvoiceStatus(t *testing.T) {
|
||||||
|
client := sdk.NewClient(
|
||||||
|
"https://api.example.com",
|
||||||
|
"your-tenant-id",
|
||||||
|
"dd-ai-table",
|
||||||
|
"your-client-secret",
|
||||||
|
)
|
||||||
|
|
||||||
|
resp, err := client.QueryInvoiceStatus(context.Background(), &sdk.QueryInvoiceStatusRequest{
|
||||||
|
OrderID: "ORDER-20240101-001",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("QueryInvoiceStatus failed: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("状态: %d, 描述: %s\n", resp.Status, resp.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCreatePaymentOrder 演示创建付款单据接口的调用。
|
||||||
|
func TestCreatePaymentOrder(t *testing.T) {
|
||||||
|
client := sdk.NewClient(
|
||||||
|
"https://api.example.com",
|
||||||
|
"your-tenant-id",
|
||||||
|
"dd-ai-table",
|
||||||
|
"your-client-secret",
|
||||||
|
)
|
||||||
|
|
||||||
|
req := &sdk.CreatePaymentOrderRequest{
|
||||||
|
Code: "PAY-20240101-001",
|
||||||
|
UserID: "user-001",
|
||||||
|
Title: "供应商货款",
|
||||||
|
Amount: "10000.00",
|
||||||
|
Supplier: &sdk.Supplier{
|
||||||
|
Name: "示例供应商",
|
||||||
|
},
|
||||||
|
Company: &sdk.Company{
|
||||||
|
Name: "示例企业",
|
||||||
|
},
|
||||||
|
PaymentDetailList: []sdk.PaymentDetail{
|
||||||
|
{
|
||||||
|
Amount: "10000.00",
|
||||||
|
Remark: "货款",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.CreatePaymentOrder(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreatePaymentOrder failed: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("单据编码: %s\n", resp.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestQueryPaymentStatus 演示支付状态查询接口的调用。
|
||||||
|
func TestQueryPaymentStatus(t *testing.T) {
|
||||||
|
client := sdk.NewClient(
|
||||||
|
"https://api.example.com",
|
||||||
|
"your-tenant-id",
|
||||||
|
"dd-ai-table",
|
||||||
|
"your-client-secret",
|
||||||
|
)
|
||||||
|
|
||||||
|
resp, err := client.QueryPaymentStatus(context.Background(), &sdk.QueryPaymentStatusRequest{
|
||||||
|
Code: "PAY-20240101-001",
|
||||||
|
UserID: "user-001",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("QueryPaymentStatus failed: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("支付状态: %s, 支付时间: %s\n", resp.PaymentStatus, resp.PaymentTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParsePaymentNotify 演示支付完成通知的解析(客户回调接口中使用)。
|
||||||
|
func TestParsePaymentNotify(t *testing.T) {
|
||||||
|
body := []byte(`{
|
||||||
|
"code": "PAY-20240101-001",
|
||||||
|
"instanceId": "inst-001",
|
||||||
|
"corpId": "corp-001",
|
||||||
|
"paymentStatus": "SUCCESS",
|
||||||
|
"paymentTime": "2024-01-01 12:00:00",
|
||||||
|
"userId": "user-001",
|
||||||
|
"source": "openapi",
|
||||||
|
"amount": "10000.00"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
data, err := sdk.ParsePaymentNotify(body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParsePaymentNotify failed: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("支付状态: %s\n", data.PaymentStatus)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== SDK 生成完成 ===
|
||||||
Loading…
Reference in New Issue