intelligence_finance_v1-202.../intelligence_finance_v1/valid.md

40 KiB
Raw Blame History

// File: intelligence_finance_v1/go.mod

module intelligence_finance_v1

go 1.21

// File: intelligence_finance_v1/errors.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

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

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

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, &notification); err != nil {
		return nil, fmt.Errorf("unmarshal payment notification: %w", err)
	}

	return &notification, 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, &notification); err != nil {
		return nil, fmt.Errorf("unmarshal general notification: %w", err)
	}

	return &notification, 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

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)
}