添加文件: intelligence_finance_v1/client.go
This commit is contained in:
parent
92ed272cae
commit
531d27171d
|
|
@ -0,0 +1,297 @@
|
||||||
|
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(commonResp.Data, response); err != nil {
|
||||||
|
return NewSDKError(ErrCodeUnknown, "响应数据解析失败", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 接口1:订单开票
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// CreateInvoice 提交订单开票申请
|
||||||
|
// 文档:接口1 - 提交订单开票申请
|
||||||
|
func (c *Client) CreateInvoice(ctx context.Context, req *CreateInvoiceRequest) (*CreateInvoiceResponse, error) {
|
||||||
|
path := "/api/v1/invoice/create"
|
||||||
|
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 := "/api/v1/invoice/query"
|
||||||
|
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, ¬ificationReq); 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
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue