intelligence_finance_v1-202.../intelligence_finance_v1/client.go

298 lines
9.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package intelligence_finance_v1
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
// Client 业财连接SDK客户端
type Client struct {
baseURL string
tenantID string
clientID string
clientSecret string
httpClient *http.Client
debug bool
}
// ClientOption 客户端配置选项
type ClientOption func(*Client)
// WithHTTPClient 设置自定义HTTP客户端
func WithHTTPClient(httpClient *http.Client) ClientOption {
return func(c *Client) {
c.httpClient = httpClient
}
}
// WithDebug 开启调试模式
func WithDebug(debug bool) ClientOption {
return func(c *Client) {
c.debug = debug
}
}
// WithTimeout 设置请求超时时间
func WithTimeout(timeout time.Duration) ClientOption {
return func(c *Client) {
c.httpClient.Timeout = timeout
}
}
// NewClient 创建新的SDK客户端
// - baseURL: API基础地址
// - tenantID: 平台分配的租户唯一标识
// - clientID: 平台分配的应用标识
// - clientSecret: 平台分配的客户端密钥
// - opts: 可选配置项
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,
},
}
for _, opt := range opts {
opt(c)
}
return c
}
// doRequest 发送HTTP请求自动添加签名和认证头
func (c *Client) doRequest(ctx context.Context, path string, request interface{}, response interface{}) error {
bodyBytes, err := json.Marshal(request)
if err != nil {
return NewSDKError(ErrCodeInvalidParam, "请求序列化失败", err)
}
if c.debug {
fmt.Printf("[DEBUG] Request URL: %s%s\n", c.baseURL, path)
fmt.Printf("[DEBUG] Request Body: %s\n", string(bodyBytes))
}
// 生成时间戳和随机数
timestamp := GenerateTimestamp()
nonce, err := GenerateNonce(16)
if err != nil {
return NewSDKError(ErrCodeSignatureFailed, "生成随机数失败", err)
}
// 签名数据timestamp + nonce
signData := timestamp + nonce
signature := HmacSHA256Base64(c.clientSecret, signData)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, bytes.NewReader(bodyBytes))
if err != nil {
return NewSDKError(ErrCodeNetworkError, "创建请求失败", 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)
if c.debug {
fmt.Printf("[DEBUG] Headers: tenant-id=%s, client-id=%s, x-bfl-signature=%s\n", c.tenantID, c.clientID, signature)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return NewSDKError(ErrCodeNetworkError, "请求发送失败", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return NewSDKError(ErrCodeNetworkError, "读取响应失败", err)
}
if c.debug {
fmt.Printf("[DEBUG] Response Status: %s\n", resp.Status)
fmt.Printf("[DEBUG] Response Body: %s\n", string(respBody))
}
// 解析通用响应
//var commonResp CommonResponse
//if err := json.Unmarshal(respBody, &commonResp); err != nil {
// return NewSDKError(ErrCodeUnknown, "响应解析失败", err)
//}
//
//if commonResp.Code != 0 {
// return NewSDKError(commonResp.Code, commonResp.Msg, nil)
//}
// 如果response不为nil将commonResp.Data反序列化到response
if response != nil {
if err := json.Unmarshal(respBody, response); err != nil {
return NewSDKError(ErrCodeUnknown, "响应数据解析失败", err)
}
}
return nil
}
// ============================================================
// 接口1订单开票
// ============================================================
// CreateInvoice 提交订单开票申请
// 文档接口1 - 提交订单开票申请
func (c *Client) CreateInvoice(ctx context.Context, req *CreateInvoiceRequest) (*CreateInvoiceResponse, error) {
path := "OZu"
var resp CreateInvoiceResponse
if err := c.doRequest(ctx, path, req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// ============================================================
// 接口2开票状态查询
// ============================================================
// QueryInvoiceStatus 查询订单开票状态
// 文档接口2 - 查询订单开票状态
func (c *Client) QueryInvoiceStatus(ctx context.Context, req *QueryInvoiceStatusRequest) (*QueryInvoiceStatusResponse, error) {
path := "OZv"
var resp QueryInvoiceStatusResponse
if err := c.doRequest(ctx, path, req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// ============================================================
// 接口3创建付款单据
// ============================================================
// CreatePayment 创建付款单据
// 文档接口3 - 创建付款单据
func (c *Client) CreatePayment(ctx context.Context, req *CreatePaymentRequest) (*CreatePaymentResponse, error) {
path := "/api/v1/payment/create"
var resp CreatePaymentResponse
if err := c.doRequest(ctx, path, req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// ============================================================
// 接口4支付完成通知平台回调客户
// 此接口由客户提供平台调用。客户需实现HTTP Handler处理回调。
// ============================================================
// HandlePaymentNotification 处理支付完成通知
// 文档接口4 - 支付完成通知回调处理
// 该函数用于解析平台发起的支付完成通知请求返回SUCCESS或FAILED
// 注意:平台回调时,请求体为 NotificationRequest 结构,其中 Data 字段为 PaymentNotification 的JSON字符串
func HandlePaymentNotification(body []byte) (string, error) {
// 先解析外层通知结构
var notificationReq NotificationRequest
if err := json.Unmarshal(body, &notificationReq); err != nil {
return "FAILED", NewSDKError(ErrCodeInvalidParam, "解析通知请求失败", err)
}
// 根据bizType处理不同业务
switch notificationReq.BizType {
case "payment_notification":
// 解析内部支付通知数据
var paymentNotification PaymentNotification
if err := json.Unmarshal([]byte(notificationReq.Data), &paymentNotification); err != nil {
return "FAILED", NewSDKError(ErrCodeInvalidParam, "解析支付通知数据失败", err)
}
// 业务处理由调用方自行实现,此处仅返回成功
// 调用方应在此函数基础上扩展业务逻辑
_ = paymentNotification
default:
// 未知业务类型,记录日志后返回失败
return "FAILED", NewSDKError(ErrCodeInvalidParam, "未知业务类型: "+notificationReq.BizType, nil)
}
return "SUCCESS", nil
}
// ============================================================
// 接口5支付状态查询
// ============================================================
// QueryPaymentStatus 查询支付状态
// 文档接口5 - 查询支付状态
func (c *Client) QueryPaymentStatus(ctx context.Context, req *QueryPaymentStatusRequest) (*QueryPaymentStatusResponse, error) {
path := "/api/v1/payment/query"
var resp QueryPaymentStatusResponse
if err := c.doRequest(ctx, path, req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// ============================================================
// 接口6创建供应商
// ============================================================
// CreateSupplier 创建供应商
// 文档接口6 - 创建供应商
func (c *Client) CreateSupplier(ctx context.Context, req *CreateSupplierRequest) (*CreateSupplierResponse, error) {
path := "/api/v1/supplier/create"
var resp CreateSupplierResponse
if err := c.doRequest(ctx, path, req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// ============================================================
// 接口7根据名称查询供应商
// ============================================================
// QuerySupplierByName 根据供应商名称查询供应商
// 文档接口7 - 根据名称查询供应商
func (c *Client) QuerySupplierByName(ctx context.Context, req *QuerySupplierByNameRequest) (*QuerySupplierByNameResponse, error) {
path := "/api/v1/supplier/queryByName"
var resp QuerySupplierByNameResponse
if err := c.doRequest(ctx, path, req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// ============================================================
// 接口8更新供应商
// ============================================================
// UpdateSupplier 更新供应商信息
// 文档接口8 - 更新供应商
func (c *Client) UpdateSupplier(ctx context.Context, req *UpdateSupplierRequest) (*UpdateSupplierResponse, error) {
path := "/api/v1/supplier/update"
var resp UpdateSupplierResponse
if err := c.doRequest(ctx, path, req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// ============================================================
// 接口9根据用户自定义编码查询供应商
// ============================================================
// QuerySupplierByUserDefineCode 根据用户自定义编码查询供应商
// 文档接口9 - 根据用户自定义编码查询供应商
func (c *Client) QuerySupplierByUserDefineCode(ctx context.Context, req *QuerySupplierByUserDefineCodeRequest) (*QuerySupplierByUserDefineCodeResponse, error) {
path := "/api/v1/supplier/queryByCode"
var resp QuerySupplierByUserDefineCodeResponse
if err := c.doRequest(ctx, path, req, &resp); err != nil {
return nil, err
}
return &resp, nil
}