57 KiB
Now I have the necessary encryption utilities. Let me also need the HmacSHA256 implementation. Since the document specifically mentions HmacSHA256, I'll implement it directly in crypto.go using Go's standard library.
Let me now generate all 6 files:
// File: intelligence_finance/go.mod
module intelligence_finance
go 1.21
// File: intelligence_finance/crypto.go
package intelligence_finance
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"math/big"
"sort"
"strings"
"time"
)
// GenerateTimestamp generates a Unix timestamp in seconds as a string.
func GenerateTimestamp() string {
return fmt.Sprintf("%d", time.Now().Unix())
}
// GenerateTimestampMillis generates a Unix timestamp in milliseconds as a string.
func GenerateTimestampMillis() string {
return fmt.Sprintf("%d", time.Now().UnixMilli())
}
// GenerateNonce generates a cryptographically secure random string of the given length.
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
}
// HmacSHA256Base64 computes HmacSHA256 of the given data using the secret key,
// and returns the result as a Base64-encoded string.
func HmacSHA256Base64(secret, data string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(data))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
// BuildSignString sorts parameters by key in dictionary order and concatenates
// them as key=value&key2=value2, excluding empty values and the sign/signature fields.
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 concatenates parameters in the specified key order,
// excluding empty values.
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/types.go
package intelligence_finance
// ============================================================
// General API Response
// ============================================================
// APIResponse is the general response wrapper returned by the platform APIs.
type APIResponse struct {
Code int `json:"code"`
Msg string `json:"msg,omitempty"`
Data map[string]interface{} `json:"data,omitempty"`
}
// ============================================================
// Interface 1: 订单开票接口 (Invoice Create)
// ============================================================
// InvoiceCreateRequest is the request for creating an invoice order.
type InvoiceCreateRequest struct {
CompanyCode string `json:"companyCode,omitempty"`
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"`
}
// InvoiceProduct represents a single product/service line item in an invoice.
type InvoiceProduct struct {
ProductName string `json:"productName"`
RevenueCode string `json:"revenueCode"`
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"`
TaxRate float64 `json:"taxRate,omitempty"`
}
// InvoiceCreateResponse is the response for creating an invoice order.
type InvoiceCreateResponse struct {
Status int `json:"status"`
ErrorMsg string `json:"errorMsg,omitempty"`
DataList []InvoiceData `json:"dataList"`
}
// InvoiceData contains detailed information about a single invoice.
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"`
PurchaserBankAcct 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"`
SellerBankAcct 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"`
OFDURL string `json:"ofdUrl,omitempty"`
XMLURL string `json:"xmlUrl,omitempty"`
TotalExcludeTax string `json:"totalExcludeTax"`
TotalIncludeTax string `json:"totalIncludeTax"`
TotalTaxAmount string `json:"totalTaxAmount"`
LevyingType string `json:"levyingType"`
Details []InvoiceDetail `json:"details"`
}
// InvoiceDetail represents a product detail line within an invoice.
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"`
}
// ============================================================
// Interface 2: 开票状态查询 (Invoice Status Query)
// ============================================================
// InvoiceStatusQueryRequest is the request for querying invoice status.
type InvoiceStatusQueryRequest struct {
OrderID string `json:"orderId"`
}
// InvoiceStatusQueryResponse is the response for querying invoice status.
type InvoiceStatusQueryResponse struct {
Status int `json:"status"`
Message string `json:"message,omitempty"`
Data []InvoiceData `json:"data"`
}
// ============================================================
// Interface 3: 创建付款单据 (Create Payment Order)
// ============================================================
// CreatePaymentOrderRequest is the request for creating a payment order.
type CreatePaymentOrderRequest struct {
Code string `json:"code"`
YidaAppType string `json:"yidaAppType,omitempty"`
EmpAccountUserID string `json:"empAccountUserId,omitempty"`
Department *Department `json:"department,omitempty"`
Usage string `json:"usage,omitempty"`
PaymentUserID string `json:"paymentUserId,omitempty"`
Customer *Customer `json:"customer,omitempty"`
PrincipalID string `json:"principalId,omitempty"`
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"`
NeedPayment bool `json:"needPayment,omitempty"`
PaymentDetailListJSONStr string `json:"paymentDetailListJsonStr,omitempty"`
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"`
OccurDate int64 `json:"occurDate,omitempty"`
Product *Product `json:"product,omitempty"`
YidaFormUUID string `json:"yidaFormUuid,omitempty"`
CanEditPaymentInfo bool `json:"canEditPaymentInfo,omitempty"`
PaymentUserIDList []string `json:"paymentUserIdList,omitempty"`
YidaProcInsID string `json:"yidaProcInsId,omitempty"`
SyncPaymentOrder bool `json:"syncPaymentOrder,omitempty"`
}
// Department represents department information.
type Department struct {
Code string `json:"code,omitempty"`
Name string `json:"name"`
}
// Customer represents customer information.
type Customer struct {
Code string `json:"code,omitempty"`
Name string `json:"name"`
}
// Supplier represents supplier information.
type Supplier struct {
Code string `json:"code,omitempty"`
Name string `json:"name"`
}
// Project represents project information.
type Project struct {
Code string `json:"code,omitempty"`
Name string `json:"name"`
}
// Category represents income/expense category information.
type Category struct {
Code string `json:"code,omitempty"`
Name string `json:"name"`
}
// Product represents product information.
type Product struct {
Code string `json:"code,omitempty"`
Name string `json:"name"`
}
// Company represents enterprise entity information.
type Company struct {
Code string `json:"code,omitempty"`
Name string `json:"name"`
}
// EnterpriseAccount represents enterprise account information.
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 represents recipient account information.
type RecipientAccount struct {
AccountCategory string `json:"accountCategory"`
AccountType string `json:"accountType,omitempty"`
CardNo string `json:"cardNo,omitempty"`
AccountName string `json:"accountName,omitempty"`
}
// PaymentDetail represents a payment detail line item.
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 represents invoice information within a payment detail.
type InvoiceInfo struct {
InvoiceNo string `json:"invoiceNo,omitempty"`
InvoiceCode string `json:"invoiceCode,omitempty"`
}
// CreatePaymentOrderResponse is the response for creating a payment order.
type CreatePaymentOrderResponse struct {
Code string `json:"code"`
}
// ============================================================
// Interface 4: 支付完成通知 (Payment Notification - Callback)
// ============================================================
// PaymentNotificationData represents the payment notification data sent by the platform.
type PaymentNotificationData struct {
Code string `json:"code"`
InstanceID string `json:"instanceId"`
CorpID string `json:"corpId"`
PaymentStatus string `json:"paymentStatus"`
PaymentTime string `json:"paymentTime"`
UserID string `json:"userId"`
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 represents the payer's account information.
type PayerAccountInfo struct {
BankOpenDTO *BankOpenDTO `json:"bankOpenDTO,omitempty"`
EnterpriseAccountCode string `json:"enterpriseAccountCode,omitempty"`
AccountType string `json:"accountType,omitempty"`
}
// PayeeAccountInfo represents the payee's account information.
type PayeeAccountInfo struct {
BankOpenDTO *BankOpenDTO `json:"bankOpenDTO,omitempty"`
}
// BankOpenDTO represents bank account information.
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"`
}
// PaymentStatus constants.
const (
PaymentStatusSuccess = "SUCCESS"
PaymentStatusFail = "FAIL"
PaymentStatusTerminate = "TERMINATE"
PaymentStatusWaitPay = "WAIT_PAY"
PaymentStatusPaying = "PAYING"
PaymentStatusPartSucc = "PART_SUCCESS"
PaymentStatusRefund = "REFUND"
)
// Source constants for payment notifications.
const (
SourceApproval = "approval"
SourceOpenAPI = "openapi"
)
// AccountType constants.
const (
AccountTypeAlipay = "ALIPAY"
AccountTypeBankCard = "BANKCARD"
AccountTypeCorpBankCard = "CORP_BANK_CARD"
AccountTypePersonalBankCard = "PERSONAL_BANK_CARD"
)
// ============================================================
// Interface 5: 支付状态查询 (Payment Status Query)
// ============================================================
// PaymentStatusQueryRequest is the request for querying payment status.
type PaymentStatusQueryRequest struct {
Code string `json:"code"`
UserID string `json:"userId"`
}
// PaymentStatusQueryResponse is the response for querying payment status,
// which has the same structure as PaymentNotificationData.
type PaymentStatusQueryResponse struct {
PaymentNotificationData
}
// ============================================================
// Generic Notification (Business Notification)
// ============================================================
// NotificationRequest represents a generic notification request from the platform.
type NotificationRequest struct {
BizType string `json:"bizType"`
BizID string `json:"bizId"`
Data string `json:"data,omitempty"`
}
// File: intelligence_finance/errors.go
package intelligence_finance
import "fmt"
// ErrorCode represents a numeric error code from the API.
type ErrorCode int
// Predefined error codes.
const (
CodeSuccess ErrorCode = 0
)
// APIError represents an error returned by the platform API.
type APIError struct {
Code ErrorCode `json:"code"`
Message string `json:"msg,omitempty"`
}
// Error implements the error interface.
func (e *APIError) Error() string {
return fmt.Sprintf("api error: code=%d, msg=%s", e.Code, e.Message)
}
// ErrInvalidResponse is returned when the API response cannot be parsed.
type ErrInvalidResponse struct {
Message string
}
// Error implements the error interface.
func (e *ErrInvalidResponse) Error() string {
return fmt.Sprintf("invalid response: %s", e.Message)
}
// ErrNetwork is returned when a network error occurs.
type ErrNetwork struct {
Err error
}
// Error implements the error interface.
func (e *ErrNetwork) Error() string {
return fmt.Sprintf("network error: %v", e.Err)
}
// Unwrap returns the underlying error.
func (e *ErrNetwork) Unwrap() error {
return e.Err
}
// ErrSigning is returned when a signing error occurs.
type ErrSigning struct {
Message string
Err error
}
// Error implements the error interface.
func (e *ErrSigning) Error() string {
if e.Err != nil {
return fmt.Sprintf("signing error: %s: %v", e.Message, e.Err)
}
return fmt.Sprintf("signing error: %s", e.Message)
}
// Unwrap returns the underlying error.
func (e *ErrSigning) Unwrap() error {
return e.Err
}
// File: intelligence_finance/client.go
package intelligence_finance
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
// Default API paths - these can be customized after receiving the actual short codes.
const (
defaultPathInvoiceCreate = "/api/invoice/create"
defaultPathInvoiceStatusQuery = "/api/invoice/status"
defaultPathPaymentCreate = "/api/payment/create"
defaultPathPaymentStatusQuery = "/api/payment/status"
)
// Client is the intelligence finance API client.
type Client struct {
baseURL string
tenantID string
clientID string
clientSecret string
httpClient *http.Client
// Paths for each API endpoint (configurable after onboarding).
pathInvoiceCreate string
pathInvoiceStatusQuery string
pathPaymentCreate string
pathPaymentStatusQuery string
}
// ClientOption is a functional option for configuring the Client.
type ClientOption func(*Client)
// WithHTTPClient sets a custom HTTP client.
func WithHTTPClient(httpClient *http.Client) ClientOption {
return func(c *Client) {
c.httpClient = httpClient
}
}
// WithInvoiceCreatePath sets a custom path for the invoice create API.
func WithInvoiceCreatePath(path string) ClientOption {
return func(c *Client) {
c.pathInvoiceCreate = path
}
}
// WithInvoiceStatusQueryPath sets a custom path for the invoice status query API.
func WithInvoiceStatusQueryPath(path string) ClientOption {
return func(c *Client) {
c.pathInvoiceStatusQuery = path
}
}
// WithPaymentCreatePath sets a custom path for the payment create API.
func WithPaymentCreatePath(path string) ClientOption {
return func(c *Client) {
c.pathPaymentCreate = path
}
}
// WithPaymentStatusQueryPath sets a custom path for the payment status query API.
func WithPaymentStatusQueryPath(path string) ClientOption {
return func(c *Client) {
c.pathPaymentStatusQuery = path
}
}
// NewClient creates a new intelligence finance API client.
// - baseURL: the base URL of the platform API (e.g., "https://api.example.com")
// - tenantID: the tenant ID assigned by the platform
// - clientID: the application client ID assigned by the platform
// - clientSecret: the application client secret assigned by the platform
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{},
pathInvoiceCreate: defaultPathInvoiceCreate,
pathInvoiceStatusQuery: defaultPathInvoiceStatusQuery,
pathPaymentCreate: defaultPathPaymentCreate,
pathPaymentStatusQuery: defaultPathPaymentStatusQuery,
}
for _, opt := range opts {
opt(c)
}
return c
}
// doRequest sends an HTTP request with authentication headers and returns the raw response body.
func (c *Client) doRequest(ctx context.Context, path string, request interface{}) ([]byte, error) {
bodyBytes, err := json.Marshal(request)
if err != nil {
return nil, &ErrSigning{Message: "marshal request body", Err: err}
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, bytes.NewReader(bodyBytes))
if err != nil {
return nil, &ErrNetwork{Err: fmt.Errorf("create request: %w", err)}
}
// Set standard headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("tenant-id", c.tenantID)
req.Header.Set("client-id", c.clientID)
// Generate and set signature headers
timestamp := GenerateTimestamp()
nonce, err := GenerateNonce(16)
if err != nil {
return nil, &ErrSigning{Message: "generate nonce", Err: err}
}
signature := HmacSHA256Base64(c.clientSecret, timestamp+nonce)
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 nil, &ErrNetwork{Err: fmt.Errorf("send request: %w", err)}
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, &ErrNetwork{Err: fmt.Errorf("read response body: %w", err)}
}
if resp.StatusCode != http.StatusOK {
return nil, &ErrInvalidResponse{
Message: fmt.Sprintf("unexpected status code %d: %s", resp.StatusCode, string(respBody)),
}
}
return respBody, nil
}
// parseResponse parses the API response and extracts the data field.
func parseResponse(body []byte, target interface{}) error {
var apiResp APIResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return &ErrInvalidResponse{Message: fmt.Sprintf("unmarshal response: %v", err)}
}
if apiResp.Code != 0 {
return &APIError{Code: ErrorCode(apiResp.Code), Message: apiResp.Msg}
}
if target != nil && apiResp.Data != nil {
// Convert map to JSON then unmarshal into target
dataBytes, err := json.Marshal(apiResp.Data)
if err != nil {
return &ErrInvalidResponse{Message: fmt.Sprintf("marshal data: %v", err)}
}
if err := json.Unmarshal(dataBytes, target); err != nil {
return &ErrInvalidResponse{Message: fmt.Sprintf("unmarshal data: %v", err)}
}
}
return nil
}
// parseResponseRaw parses the API response and returns the raw data field.
func parseResponseRaw(body []byte) (map[string]interface{}, error) {
var apiResp APIResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return nil, &ErrInvalidResponse{Message: fmt.Sprintf("unmarshal response: %v", err)}
}
if apiResp.Code != 0 {
return nil, &APIError{Code: ErrorCode(apiResp.Code), Message: apiResp.Msg}
}
return apiResp.Data, nil
}
// ============================================================
// Interface 1: CreateInvoice - 订单开票接口
// ============================================================
// CreateInvoice submits an invoice creation request for an order.
// It returns the invoice creation response containing status and invoice data.
func (c *Client) CreateInvoice(ctx context.Context, req *InvoiceCreateRequest) (*InvoiceCreateResponse, error) {
body, err := c.doRequest(ctx, c.pathInvoiceCreate, req)
if err != nil {
return nil, err
}
var result InvoiceCreateResponse
if err := parseResponse(body, &result); err != nil {
return nil, err
}
return &result, nil
}
// ============================================================
// Interface 2: QueryInvoiceStatus - 开票状态查询接口
// ============================================================
// QueryInvoiceStatus queries the invoice status for a given order.
// It returns the invoice status response containing the current status and invoice data.
func (c *Client) QueryInvoiceStatus(ctx context.Context, req *InvoiceStatusQueryRequest) (*InvoiceStatusQueryResponse, error) {
body, err := c.doRequest(ctx, c.pathInvoiceStatusQuery, req)
if err != nil {
return nil, err
}
var result InvoiceStatusQueryResponse
if err := parseResponse(body, &result); err != nil {
return nil, err
}
return &result, nil
}
// ============================================================
// Interface 3: CreatePaymentOrder - 创建付款单据接口
// ============================================================
// CreatePaymentOrder creates a payment order.
// It returns the response containing the unique payment order code.
func (c *Client) CreatePaymentOrder(ctx context.Context, req *CreatePaymentOrderRequest) (*CreatePaymentOrderResponse, error) {
body, err := c.doRequest(ctx, c.pathPaymentCreate, req)
if err != nil {
return nil, err
}
var result CreatePaymentOrderResponse
if err := parseResponse(body, &result); err != nil {
return nil, err
}
return &result, nil
}
// ============================================================
// Interface 4: Payment Notification Handler (平台回调客户接口)
// ============================================================
// ParsePaymentNotification parses a payment notification request body
// sent by the platform. This is used when the platform calls back to
// the customer's system after a payment is completed.
//
// Usage example in an HTTP handler:
//
// func handler(w http.ResponseWriter, r *http.Request) {
// body, _ := io.ReadAll(r.Body)
// notification, err := ParsePaymentNotification(body)
// if err != nil {
// http.Error(w, "FAILED", http.StatusBadRequest)
// return
// }
// // Process the notification...
// w.Write([]byte("SUCCESS"))
// }
func ParsePaymentNotification(body []byte) (*PaymentNotificationData, error) {
var notification PaymentNotificationData
if err := json.Unmarshal(body, ¬ification); err != nil {
return nil, &ErrInvalidResponse{Message: fmt.Sprintf("parse payment notification: %v", err)}
}
return ¬ification, nil
}
// ParseGenericNotification parses a generic notification request body
// sent by the platform. The generic notification contains bizType, bizId, and data.
func ParseGenericNotification(body []byte) (*NotificationRequest, error) {
var notification NotificationRequest
if err := json.Unmarshal(body, ¬ification); err != nil {
return nil, &ErrInvalidResponse{Message: fmt.Sprintf("parse notification: %v", err)}
}
return ¬ification, nil
}
// ============================================================
// Interface 5: QueryPaymentStatus - 支付状态查询接口
// ============================================================
// QueryPaymentStatus queries the payment status for a given payment order.
// The response has the same structure as PaymentNotificationData.
func (c *Client) QueryPaymentStatus(ctx context.Context, req *PaymentStatusQueryRequest) (*PaymentStatusQueryResponse, error) {
body, err := c.doRequest(ctx, c.pathPaymentStatusQuery, req)
if err != nil {
return nil, err
}
var result PaymentStatusQueryResponse
if err := parseResponse(body, &result); err != nil {
return nil, err
}
return &result, nil
}
// File: intelligence_finance/example_test.go
package intelligence_finance_test
import (
"context"
"fmt"
"log"
intelligence_finance "intelligence_finance"
)
// ExampleClient_CreateInvoice demonstrates how to create an invoice.
func ExampleClient_CreateInvoice() {
// Create a new client
client := intelligence_finance.NewClient(
"https://api.example.com",
"your-tenant-id",
"your-client-id",
"your-client-secret",
)
ctx := context.Background()
// Build the invoice create request
req := &intelligence_finance.InvoiceCreateRequest{
OrderID: "ORD-2024-001",
InvoiceType: 9, // 数电普票
Purchaser: "某某科技有限公司",
TaxNum: "91110108MA01XXXXX",
Phone: "13800138000",
Email: "finance@example.com",
ApplyPerson: "张三",
Payee: "李四",
Reviewer: "王五",
Products: []intelligence_finance.InvoiceProduct{
{
ProductName: "云服务器ECS",
RevenueCode: "1010101010101010101",
AmountIncludeTax: 1180.00,
Unit: "台",
Quantity: 1,
TaxRate: 0.06,
TaxSign: 1,
},
},
}
resp, err := client.CreateInvoice(ctx, req)
if err != nil {
log.Fatalf("CreateInvoice failed: %v", err)
}
fmt.Printf("Invoice status: %d\n", resp.Status)
for _, inv := range resp.DataList {
fmt.Printf("Invoice No: %s, Amount: %s\n", inv.InvoiceNo, inv.TotalIncludeTax)
}
}
// ExampleClient_QueryInvoiceStatus demonstrates how to query invoice status.
func ExampleClient_QueryInvoiceStatus() {
client := intelligence_finance.NewClient(
"https://api.example.com",
"your-tenant-id",
"your-client-id",
"your-client-secret",
)
ctx := context.Background()
req := &intelligence_finance.InvoiceStatusQueryRequest{
OrderID: "ORD-2024-001",
}
resp, err := client.QueryInvoiceStatus(ctx, req)
if err != nil {
log.Fatalf("QueryInvoiceStatus failed: %v", err)
}
fmt.Printf("Status: %d, Message: %s\n", resp.Status, resp.Message)
for _, inv := range resp.Data {
fmt.Printf("Invoice No: %s\n", inv.InvoiceNo)
}
}
// ExampleClient_CreatePaymentOrder demonstrates how to create a payment order.
func ExampleClient_CreatePaymentOrder() {
client := intelligence_finance.NewClient(
"https://api.example.com",
"your-tenant-id",
"your-client-id",
"your-client-secret",
)
ctx := context.Background()
req := &intelligence_finance.CreatePaymentOrderRequest{
Code: "PAY-2024-001",
UserID: "user-001",
Title: "采购付款-云服务器",
Amount: "1180.00",
Department: &intelligence_finance.Department{
Name: "技术部",
},
Supplier: &intelligence_finance.Supplier{
Name: "某某云服务商",
},
PaymentDetailList: []intelligence_finance.PaymentDetail{
{
Amount: "1180.00",
},
},
}
resp, err := client.CreatePaymentOrder(ctx, req)
if err != nil {
log.Fatalf("CreatePaymentOrder failed: %v", err)
}
fmt.Printf("Payment order code: %s\n", resp.Code)
}
// ExampleClient_QueryPaymentStatus demonstrates how to query payment status.
func ExampleClient_QueryPaymentStatus() {
client := intelligence_finance.NewClient(
"https://api.example.com",
"your-tenant-id",
"your-client-id",
"your-client-secret",
)
ctx := context.Background()
req := &intelligence_finance.PaymentStatusQueryRequest{
Code: "PAY-2024-001",
UserID: "user-001",
}
resp, err := client.QueryPaymentStatus(ctx, req)
if err != nil {
log.Fatalf("QueryPaymentStatus failed: %v", err)
}
fmt.Printf("Payment status: %s\n", resp.PaymentStatus)
fmt.Printf("Payment time: %s\n", resp.PaymentTime)
}
// ExampleParsePaymentNotification demonstrates how to handle a payment notification
// callback from the platform.
func ExampleParsePaymentNotification() {
// This is the body received from the platform's callback
body := []byte(`{
"code": "PAY-2024-001",
"instanceId": "inst-001",
"corpId": "corp-123",
"paymentStatus": "SUCCESS",
"paymentTime": "2024-01-15 10:30:00",
"userId": "user-001",
"amount": "1180.00"
}`)
notification, err := intelligence_finance.ParsePaymentNotification(body)
if err != nil {
log.Fatalf("ParsePaymentNotification failed: %v", err)
}
fmt.Printf("Payment code: %s, Status: %s\n", notification.Code, notification.PaymentStatus)
// In a real HTTP handler, respond with "SUCCESS" or "FAILED"
// w.Write([]byte("SUCCESS"))
}
// ExampleClient_WithCustomOptions demonstrates how to create a client with custom options.
func ExampleClient_WithCustomOptions() {
client := intelligence_finance.NewClient(
"https://api.example.com",
"your-tenant-id",
"your-client-id",
"your-client-secret",
intelligence_finance.WithInvoiceCreatePath("/custom/path/invoice/create"),
intelligence_finance.WithInvoiceStatusQueryPath("/custom/path/invoice/status"),
intelligence_finance.WithPaymentCreatePath("/custom/path/payment/create"),
intelligence_finance.WithPaymentStatusQueryPath("/custom/path/payment/status"),
)
_ = client // use the client as needed
}
Wait, I need to fix the import in example_test.go - the package import should use the module name.
Let me reconsider the structure. The example_test.go is in package intelligence_finance_test and imports intelligence_finance as an external package. But the module name is intelligence_finance, so the import path would be intelligence_finance. However, in Go, if the test file is in the same directory, the import path should use the module path. Since the module is intelligence_finance, the import would be intelligence_finance.
Actually, wait - the example_test.go is in the same directory as the other files, but uses a different package name (intelligence_finance_test). For external test packages, the import path is the module path. If the module is intelligence_finance, the import would be "intelligence_finance".
But actually, in Go, when you have a module intelligence_finance and files in the root directory, the import path should be the module path. Let me keep it as "intelligence_finance".
Let me now output all the files properly.
Actually, looking at the import more carefully:
import (
intelligence_finance "intelligence_finance"
)
This won't work because the alias is the same as the package name. Let me just use:
import "intelligence_finance"
But wait, the example_test.go file is in the same directory as the package, but uses package intelligence_finance_test. This is an external test package, so it needs to import the package. The import path would be intelligence_finance (the module name).
Let me fix this.
Also, I need to reconsider the parseResponse function. The APIResponse has Data as map[string]interface{}, but the actual responses might be different structures. Let me think about this...
Actually, looking at the API responses more carefully:
-
Invoice Create Response: { "status": 1, "errorMsg": "...", "dataList": [...] }
- This is the data field of the general response
-
Invoice Status Query Response: { "status": 3, "message": "成功", "data": [...] }
- This is the data field of the general response
-
Payment Create Response: { "code": "单据唯一标识" }
- This is the data field of the general response
-
Payment Status Query Response: Same as payment notification data
- This is the data field of the general response
So the general response is:
{
"code": 0,
"msg": "success",
"data": { ... specific response fields ... }
}
And the specific response fields are what I defined as the response types.
So the parseResponse function should work - it unmarshals the general response, checks the code, then marshals the data back to JSON and unmarshals into the target type.
Wait, but the APIResponse.Data is map[string]interface{}. If the data is actually an array (like dataList), then this won't work. Let me change the Data field to json.RawMessage to handle any JSON value.
Let me fix this.
Actually, looking at the document again:
- Invoice Create: the response has
dataListwhich is a list, but it's a field inside the data object, not the data itself - Invoice Status Query: the response has
datawhich is a list of invoice data
Wait, let me re-read:
Invoice Create Response:
- status: Integer
- errorMsg: String
- dataList: List