Files
jnc-server/app/main/api/internal/service/yunyinSignPayService.go
2026-01-16 21:12:11 +08:00

1164 lines
48 KiB
Go
Raw 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 service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"jnc-server/app/main/api/internal/config"
"net/http"
"time"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/redis"
)
const (
// Redis key 前缀
YunYinSignPayTokenKey = "yunyin_sign_pay:token"
YunYinSignPayUserIdKey = "yunyin_sign_pay:user_id"
TokenCacheExpire = 2 * time.Hour // token 缓存2小时
UserIdCacheExpire = 2 * time.Hour // 操作ID缓存2小时
)
// YunYinSignPayService 云印签支付服务
type YunYinSignPayService struct {
config config.YunYinSignPayConfig
client *http.Client
redis *redis.Redis
}
// NewYunYinSignPayService 创建云印签支付服务实例
func NewYunYinSignPayService(c config.Config, redisClient *redis.Redis) *YunYinSignPayService {
return &YunYinSignPayService{
config: c.YunYinSignPay,
client: &http.Client{
Timeout: 30 * time.Second,
},
redis: redisClient,
}
}
// GetAccessTokenRequest 获取token请求
type GetAccessTokenRequest struct {
AppID string `json:"appId"`
AppSecret string `json:"appSecret"`
}
// GetAccessTokenResponse 获取token响应
type GetAccessTokenResponse struct {
Code interface{} `json:"code"` // 可能是字符串"200"或数字200
Msg string `json:"msg"`
Data *AccessTokenData `json:"data,omitempty"`
}
// GetCodeInt 获取 code 的 int 值
func (r *GetAccessTokenResponse) GetCodeInt() int {
switch v := r.Code.(type) {
case int:
return v
case float64:
return int(v)
case string:
if v == "200" {
return 200
}
return 0
default:
return 0
}
}
// AccessTokenData token数据
type AccessTokenData struct {
AccessToken string `json:"accessToken"`
ExpireTime int64 `json:"expireTime,omitempty"`
}
// GetAccessToken 获取访问token带缓存
func (y *YunYinSignPayService) GetAccessToken(ctx context.Context) (string, error) {
logx.Infof("[云印签API] GetAccessToken: 开始获取AccessTokenRedis Key: %s", YunYinSignPayTokenKey)
// 先从Redis获取
token, err := y.redis.GetCtx(ctx, YunYinSignPayTokenKey)
if err == nil && token != "" {
logx.Infof("[云印签API] GetAccessToken: 从Redis获取token成功Token长度: %d", len(token))
return token, nil
}
if err != nil {
logx.Infof("[云印签API] GetAccessToken: Redis查询失败或token不存在错误: %v将从API获取", err)
}
// Redis中没有调用API获取
logx.Infof("[云印签API] GetAccessToken: 从API获取tokenAPI地址: %s/service/getAccessToken, AppID: %s", y.config.ApiURL, y.config.AppID)
reqBody := GetAccessTokenRequest{
AppID: y.config.AppID,
AppSecret: y.config.AppSecret,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
logx.Errorf("[云印签API] GetAccessToken: 序列化请求参数失败: %v", err)
return "", fmt.Errorf("序列化请求参数失败: %v", err)
}
logx.Infof("[云印签API] GetAccessToken: 请求体: %s", string(jsonData))
url := fmt.Sprintf("%s/service/getAccessToken", y.config.ApiURL)
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonData))
if err != nil {
logx.Errorf("[云印签API] GetAccessToken: 创建请求失败: %v", err)
return "", fmt.Errorf("创建请求失败: %v", err)
}
req.Header.Set("Content-Type", "application/json")
logx.Infof("[云印签API] GetAccessToken: 发送请求URL: %s, Method: POST", url)
resp, err := y.client.Do(req)
if err != nil {
logx.Errorf("[云印签API] GetAccessToken: HTTP请求失败: %v", err)
return "", fmt.Errorf("请求失败: %v", err)
}
defer resp.Body.Close()
logx.Infof("[云印签API] GetAccessToken: 收到响应,状态码: %d", resp.StatusCode)
body, err := io.ReadAll(resp.Body)
if err != nil {
logx.Errorf("[云印签API] GetAccessToken: 读取响应体失败: %v", err)
return "", fmt.Errorf("读取响应失败: %v", err)
}
logx.Infof("[云印签API] GetAccessToken: 响应体: %s", string(body))
var tokenResp GetAccessTokenResponse
if err := json.Unmarshal(body, &tokenResp); err != nil {
logx.Errorf("[云印签API] GetAccessToken: 解析响应失败: %v, 响应内容: %s", err, string(body))
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
}
logx.Infof("[云印签API] GetAccessToken: 响应解析成功Code: %v, Msg: %s", tokenResp.Code, tokenResp.Msg)
// 检查响应码(可能是字符串"200"或数字200
codeInt := tokenResp.GetCodeInt()
if codeInt != 200 {
logx.Errorf("[云印签API] GetAccessToken: API返回错误Code: %v, Msg: %s", tokenResp.Code, tokenResp.Msg)
return "", fmt.Errorf("获取token失败: %s", tokenResp.Msg)
}
if tokenResp.Data == nil || tokenResp.Data.AccessToken == "" {
logx.Errorf("[云印签API] GetAccessToken: token数据为空响应: %+v", tokenResp)
return "", fmt.Errorf("token数据为空")
}
accessToken := tokenResp.Data.AccessToken
logx.Infof("[云印签API] GetAccessToken: 获取token成功Token长度: %d, ExpireTime: %d", len(accessToken), tokenResp.Data.ExpireTime)
// 存储到Redis2小时过期
err = y.redis.SetexCtx(ctx, YunYinSignPayTokenKey, accessToken, int(TokenCacheExpire.Seconds()))
if err != nil {
logx.Errorf("[云印签API] GetAccessToken: 存储token到Redis失败: %v", err)
// 不返回错误因为token已经获取成功
} else {
logx.Infof("[云印签API] GetAccessToken: token已缓存到Redis过期时间: %d秒", int(TokenCacheExpire.Seconds()))
}
logx.Infof("[云印签API] GetAccessToken: 完成返回token")
return accessToken, nil
}
// GetUserIdRequest 获取操作ID请求
type GetUserIdRequest struct {
Mobile string `json:"mobile"`
}
// GetUserIdResponse 获取操作ID响应
type GetUserIdResponse struct {
Code interface{} `json:"code"` // 可能是字符串"200"或数字200
Msg string `json:"msg"`
Data []UserIdData `json:"data,omitempty"` // data 是数组
}
// GetCodeInt 获取 code 的 int 值
func (r *GetUserIdResponse) GetCodeInt() int {
switch v := r.Code.(type) {
case int:
return v
case float64:
return int(v)
case string:
if v == "200" {
return 200
}
return 0
default:
return 0
}
}
// UserIdData 操作ID数据
type UserIdData struct {
UserId string `json:"userId"`
UserName string `json:"userName,omitempty"`
Mobile string `json:"mobile,omitempty"`
UserStatus int `json:"userStatus,omitempty"`
RealName string `json:"realName,omitempty"`
RealNameStatus int `json:"realNameStatus,omitempty"`
}
// GetUserId 获取操作ID带缓存
func (y *YunYinSignPayService) GetUserId(ctx context.Context, accessToken string) (string, error) {
logx.Infof("[云印签API] GetUserId: 开始获取操作IDRedis Key: %s, Mobile: %s", YunYinSignPayUserIdKey, y.config.Mobile)
// 先从Redis获取
userId, err := y.redis.GetCtx(ctx, YunYinSignPayUserIdKey)
if err == nil && userId != "" {
logx.Infof("[云印签API] GetUserId: 从Redis获取操作ID成功UserID: %s", userId)
return userId, nil
}
if err != nil {
logx.Infof("[云印签API] GetUserId: Redis查询失败或操作ID不存在错误: %v将从API获取", err)
}
// Redis中没有调用API获取
logx.Infof("[云印签API] GetUserId: 从API获取操作IDAPI地址: %s/member/infoByCorpId, Mobile: %s", y.config.ApiURL, y.config.Mobile)
reqBody := GetUserIdRequest{
Mobile: y.config.Mobile,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
logx.Errorf("[云印签API] GetUserId: 序列化请求参数失败: %v", err)
return "", fmt.Errorf("序列化请求参数失败: %v", err)
}
logx.Infof("[云印签API] GetUserId: 请求体: %s", string(jsonData))
url := fmt.Sprintf("%s/member/infoByCorpId", y.config.ApiURL)
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonData))
if err != nil {
logx.Errorf("[云印签API] GetUserId: 创建请求失败: %v", err)
return "", fmt.Errorf("创建请求失败: %v", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("appId", y.config.AppID)
req.Header.Set("accessToken", accessToken)
logx.Infof("[云印签API] GetUserId: 发送请求URL: %s, Method: POST, AppID: %s, AccessToken长度: %d", url, y.config.AppID, len(accessToken))
resp, err := y.client.Do(req)
if err != nil {
logx.Errorf("[云印签API] GetUserId: HTTP请求失败: %v", err)
return "", fmt.Errorf("请求失败: %v", err)
}
defer resp.Body.Close()
logx.Infof("[云印签API] GetUserId: 收到响应,状态码: %d", resp.StatusCode)
body, err := io.ReadAll(resp.Body)
if err != nil {
logx.Errorf("[云印签API] GetUserId: 读取响应体失败: %v", err)
return "", fmt.Errorf("读取响应失败: %v", err)
}
logx.Infof("[云印签API] GetUserId: 响应体: %s", string(body))
var userIdResp GetUserIdResponse
if err := json.Unmarshal(body, &userIdResp); err != nil {
logx.Errorf("[云印签API] GetUserId: 解析响应失败: %v, 响应内容: %s", err, string(body))
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
}
logx.Infof("[云印签API] GetUserId: 响应解析成功Code: %v, Msg: %s", userIdResp.Code, userIdResp.Msg)
// 检查响应码(可能是字符串"200"或数字200
codeInt := userIdResp.GetCodeInt()
if codeInt != 200 {
logx.Errorf("[云印签API] GetUserId: API返回错误Code: %v, Msg: %s", userIdResp.Code, userIdResp.Msg)
return "", fmt.Errorf("获取操作ID失败: %s", userIdResp.Msg)
}
if len(userIdResp.Data) == 0 {
logx.Errorf("[云印签API] GetUserId: 操作ID数据为空响应: %+v", userIdResp)
return "", fmt.Errorf("操作ID数据为空")
}
// 获取操作ID从数组中取第一个元素
operationUserId := userIdResp.Data[0].UserId
if operationUserId == "" {
logx.Errorf("[云印签API] GetUserId: 操作ID为空响应数据: %+v", userIdResp.Data[0])
return "", fmt.Errorf("操作ID为空")
}
logx.Infof("[云印签API] GetUserId: 获取操作ID成功UserID: %s, 用户名: %s, 手机号: %s",
operationUserId, userIdResp.Data[0].UserName, userIdResp.Data[0].Mobile)
// 存储到Redis2小时过期
err = y.redis.SetexCtx(ctx, YunYinSignPayUserIdKey, operationUserId, int(UserIdCacheExpire.Seconds()))
if err != nil {
logx.Errorf("[云印签API] GetUserId: 存储操作ID到Redis失败: %v", err)
// 不返回错误因为操作ID已经获取成功
} else {
logx.Infof("[云印签API] GetUserId: 操作ID已缓存到Redis过期时间: %d秒", int(UserIdCacheExpire.Seconds()))
}
logx.Infof("[云印签API] GetUserId: 完成返回操作ID: %s", operationUserId)
return operationUserId, nil
}
// ParticipantInfo 参与者信息
type ParticipantInfo struct {
ParticipantFlag string `json:"participantFlag"`
PsnAccount string `json:"psnAccount"`
PsnName string `json:"psnName"`
ParticipantCorpName string `json:"participantCorpName,omitempty"`
ParticipantType int `json:"participantType"`
PayeeContractFlag int `json:"payeeContractFlag,omitempty"`
Payee *PayeeInfo `json:"payee,omitempty"`
}
// PayeeInfo 收款方信息
type PayeeInfo struct {
Amount float64 `json:"amount"`
Priority string `json:"priority"`
}
// FillComponent 填充组件
type FillComponent struct {
ComponentKey string `json:"componentKey"`
ComponentValue string `json:"componentValue"`
}
// StartSignFlowRequest 发起签署请求
type StartSignFlowRequest struct {
TemplateCode string `json:"templateCode"`
TemplateName string `json:"templateName"`
FlowType int `json:"flowType"` // 流程类型0-正常签署流程2-临时签署流程(买家信息后补)
AutoFill int `json:"autoFill"`
SourceOrderCode string `json:"sourceOrderCode"`
ParticipantList []ParticipantInfo `json:"participantList"`
FillComponents []FillComponent `json:"fillComponents"`
}
// StartSignFlowResponse 发起签署响应
type StartSignFlowResponse struct {
Code interface{} `json:"code"` // 可能是 int 或 string
Msg string `json:"msg"`
Data *StartSignFlowData `json:"data,omitempty"`
}
// StartSignFlowData 发起签署数据API响应
type StartSignFlowData struct {
FlowID interface{} `json:"flowId,omitempty"` // 可能是 int64 或 string
FlowStatus int `json:"flowStatus,omitempty"`
FlowDesc string `json:"flowDesc,omitempty"`
FlowTitle string `json:"flowTitle,omitempty"`
NextStatus int `json:"nextStatus,omitempty"`
ResultStatus interface{} `json:"resultStatus,omitempty"`
ErrorMessage interface{} `json:"errorMessage,omitempty"`
SourceOrderCode string `json:"sourceOrderCode,omitempty"`
ParticipantList []ParticipantItem `json:"participantList,omitempty"`
}
// GetFlowIDString 获取 flowId 的字符串值
func (d *StartSignFlowData) GetFlowIDString() string {
switch v := d.FlowID.(type) {
case string:
return v
case int:
return fmt.Sprintf("%d", v)
case int64:
return fmt.Sprintf("%d", v)
case float64:
return fmt.Sprintf("%.0f", v)
default:
return fmt.Sprintf("%v", v)
}
}
// StartSignFlowResult 发起签署流程返回结果
type StartSignFlowResult struct {
ParticipantID string // 签署方2的参与者ID
TaskID string // 流程IDflowId
}
// ParticipantItem 参与者项(响应中的)
type ParticipantItem struct {
ParticipantID interface{} `json:"participantId"` // 可能是 int64 或 string
ParticipantFlag string `json:"participantFlag"`
SignStatus interface{} `json:"signStatus,omitempty"`
ParticipantCorpID interface{} `json:"participantCorpId,omitempty"` // 可能是 int64 或 string
ParticipantCorpName string `json:"participantCorpName,omitempty"`
ParticipantType int `json:"participantType"`
ParticipateBizType []string `json:"participateBizType,omitempty"`
PsnID interface{} `json:"psnId,omitempty"` // 可能是 int64 或 string
PsnName string `json:"psnName,omitempty"`
PayeeContractFlag interface{} `json:"payeeContractFlag,omitempty"`
Payee interface{} `json:"payee,omitempty"`
}
// GetParticipantIDString 获取 participantId 的字符串值
func (p *ParticipantItem) GetParticipantIDString() string {
switch v := p.ParticipantID.(type) {
case string:
return v
case int:
return fmt.Sprintf("%d", v)
case int64:
return fmt.Sprintf("%d", v)
case float64:
return fmt.Sprintf("%.0f", v)
default:
return fmt.Sprintf("%v", v)
}
}
// GetCodeInt 获取 code 的 int 值
func (r *StartSignFlowResponse) GetCodeInt() int {
switch v := r.Code.(type) {
case int:
return v
case float64:
return int(v)
case string:
if v == "200" {
return 200
}
return 0
default:
return 0
}
}
// StartSignFlow 发起签署流程
func (y *YunYinSignPayService) StartSignFlow(ctx context.Context, accessToken, userId string, req *StartSignFlowRequest) (*StartSignFlowResult, error) {
logx.Infof("[云印签API] StartSignFlow: 开始发起签署流程,订单号: %s, 模板代码: %s, 模板名称: %s, 用户ID: %s",
req.SourceOrderCode, req.TemplateCode, req.TemplateName, userId)
logx.Infof("[云印签API] StartSignFlow: 参与者列表,数量: %d", len(req.ParticipantList))
for i, p := range req.ParticipantList {
logx.Infof("[云印签API] StartSignFlow: 参与者[%d],标志: %s, 账号: %s, 姓名: %s, 类型: %d, 企业名: %s",
i, p.ParticipantFlag, p.PsnAccount, p.PsnName, p.ParticipantType, p.ParticipantCorpName)
if p.Payee != nil {
logx.Infof("[云印签API] StartSignFlow: 参与者[%d]收款信息,金额: %.2f, 优先级: %s", i, p.Payee.Amount, p.Payee.Priority)
}
}
jsonData, err := json.Marshal(req)
if err != nil {
logx.Errorf("[云印签API] StartSignFlow: 序列化请求参数失败: %v", err)
return nil, fmt.Errorf("序列化请求参数失败: %v", err)
}
logx.Infof("[云印签API] StartSignFlow: 请求体: %s", string(jsonData))
url := fmt.Sprintf("%s/signTask/startSignFlow", y.config.ApiURL)
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonData))
if err != nil {
logx.Errorf("[云印签API] StartSignFlow: 创建请求失败: %v", err)
return nil, fmt.Errorf("创建请求失败: %v", err)
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("appId", y.config.AppID)
httpReq.Header.Set("accessToken", accessToken)
httpReq.Header.Set("userId", userId)
httpReq.Header.Set("source", "pc")
logx.Infof("[云印签API] StartSignFlow: 发送请求URL: %s, Method: POST, AppID: %s, UserID: %s, AccessToken长度: %d",
url, y.config.AppID, userId, len(accessToken))
resp, err := y.client.Do(httpReq)
if err != nil {
logx.Errorf("[云印签API] StartSignFlow: HTTP请求失败: %v", err)
return nil, fmt.Errorf("请求失败: %v", err)
}
defer resp.Body.Close()
logx.Infof("[云印签API] StartSignFlow: 收到响应,状态码: %d", resp.StatusCode)
body, err := io.ReadAll(resp.Body)
if err != nil {
logx.Errorf("[云印签API] StartSignFlow: 读取响应体失败: %v", err)
return nil, fmt.Errorf("读取响应失败: %v", err)
}
logx.Infof("[云印签API] StartSignFlow: 响应体: %s", string(body))
var signFlowResp StartSignFlowResponse
if err := json.Unmarshal(body, &signFlowResp); err != nil {
logx.Errorf("[云印签API] StartSignFlow: 解析响应失败: %v, 响应内容: %s", err, string(body))
return nil, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
}
logx.Infof("[云印签API] StartSignFlow: 响应解析成功Code: %v, Msg: %s", signFlowResp.Code, signFlowResp.Msg)
// 检查响应码(可能是字符串"200"或数字200
codeInt := signFlowResp.GetCodeInt()
if codeInt != 200 {
logx.Errorf("[云印签API] StartSignFlow: API返回错误Code: %v, Msg: %s", signFlowResp.Code, signFlowResp.Msg)
return nil, fmt.Errorf("发起签署失败: %s", signFlowResp.Msg)
}
if signFlowResp.Data == nil {
logx.Errorf("[云印签API] StartSignFlow: 签署数据为空")
return nil, fmt.Errorf("签署数据为空")
}
flowIDStr := signFlowResp.Data.GetFlowIDString()
logx.Infof("[云印签API] StartSignFlow: 签署数据流程ID: %s, 流程状态: %d, 流程描述: %s, 参与者数量: %d",
flowIDStr, signFlowResp.Data.FlowStatus, signFlowResp.Data.FlowDesc, len(signFlowResp.Data.ParticipantList))
// 从 participantList 中找到签署方2的 participantId
var participantID2 string
for i, participant := range signFlowResp.Data.ParticipantList {
participantIDStr := participant.GetParticipantIDString()
logx.Infof("[云印签API] StartSignFlow: 响应参与者[%d],标志: %s, 参与者ID: %s, 类型: %d",
i, participant.ParticipantFlag, participantIDStr, participant.ParticipantType)
if participant.ParticipantFlag == "签署方2" {
participantID2 = participantIDStr
logx.Infof("[云印签API] StartSignFlow: 找到签署方2参与者ID: %s", participantID2)
break
}
}
if participantID2 == "" {
logx.Errorf("[云印签API] StartSignFlow: 未找到签署方2的参与者ID参与者列表: %+v", signFlowResp.Data.ParticipantList)
return nil, fmt.Errorf("未找到签署方2的参与者ID")
}
logx.Infof("[云印签API] StartSignFlow: 发起签署流程成功,订单号: %s, 流程ID: %s, 签署方2参与者ID: %s",
req.SourceOrderCode, flowIDStr, participantID2)
// 返回结果包含签署方2的参与者ID和流程ID
return &StartSignFlowResult{
ParticipantID: participantID2,
TaskID: flowIDStr, // 使用 flowId 作为 taskId
}, nil
}
// GetPaymentURLRequest 获取支付链接请求
type GetPaymentURLRequest struct {
ParticipantID string `json:"participantId"`
Type int `json:"type"` // 0=微信支付1=支付宝支付
}
// GetPaymentURLResponse 获取支付链接响应
type GetPaymentURLResponse struct {
Code interface{} `json:"code"` // 可能是字符串"200"或数字200
Msg string `json:"msg"`
Data *PaymentURLData `json:"data,omitempty"`
}
// GetCodeInt 获取 code 的 int 值
func (r *GetPaymentURLResponse) GetCodeInt() int {
switch v := r.Code.(type) {
case int:
return v
case float64:
return int(v)
case string:
if v == "200" {
return 200
}
return 0
default:
return 0
}
}
// PaymentURLData 支付链接数据
type PaymentURLData struct {
PayURL string `json:"payUrl,omitempty"`
URL string `json:"url,omitempty"`
Path string `json:"path,omitempty"` // 支付宝小程序链接路径
QrCode string `json:"qrCode,omitempty"` // 二维码链接
AppSameFlag string `json:"appSameFlag,omitempty"` // 是否同应用标识
AppID interface{} `json:"appId,omitempty"` // 应用ID
}
// GetPaymentURL 获取支付链接
func (y *YunYinSignPayService) GetPaymentURL(ctx context.Context, accessToken, userId string, participantID string, payType int) (string, error) {
logx.Infof("[云印签API] GetPaymentURL: 开始获取支付链接参与者ID: %s, 支付类型: %d(0=微信,1=支付宝), 用户ID: %s",
participantID, payType, userId)
reqBody := GetPaymentURLRequest{
ParticipantID: participantID,
Type: payType,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
logx.Errorf("[云印签API] GetPaymentURL: 序列化请求参数失败: %v", err)
return "", fmt.Errorf("序列化请求参数失败: %v", err)
}
logx.Infof("[云印签API] GetPaymentURL: 请求体: %s", string(jsonData))
url := fmt.Sprintf("%s/signTask/contract/appletForParticipant", y.config.ApiURL)
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonData))
if err != nil {
logx.Errorf("[云印签API] GetPaymentURL: 创建请求失败: %v", err)
return "", fmt.Errorf("创建请求失败: %v", err)
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("appId", y.config.AppID)
httpReq.Header.Set("accessToken", accessToken)
httpReq.Header.Set("userId", userId)
httpReq.Header.Set("source", "pc")
logx.Infof("[云印签API] GetPaymentURL: 发送请求URL: %s, Method: POST, AppID: %s, UserID: %s, AccessToken长度: %d",
url, y.config.AppID, userId, len(accessToken))
resp, err := y.client.Do(httpReq)
if err != nil {
logx.Errorf("[云印签API] GetPaymentURL: HTTP请求失败: %v", err)
return "", fmt.Errorf("请求失败: %v", err)
}
defer resp.Body.Close()
logx.Infof("[云印签API] GetPaymentURL: 收到响应,状态码: %d", resp.StatusCode)
body, err := io.ReadAll(resp.Body)
if err != nil {
logx.Errorf("[云印签API] GetPaymentURL: 读取响应体失败: %v", err)
return "", fmt.Errorf("读取响应失败: %v", err)
}
logx.Infof("[云印签API] GetPaymentURL: 响应体: %s", string(body))
var paymentURLResp GetPaymentURLResponse
if err := json.Unmarshal(body, &paymentURLResp); err != nil {
logx.Errorf("[云印签API] GetPaymentURL: 解析响应失败: %v, 响应内容: %s", err, string(body))
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
}
logx.Infof("[云印签API] GetPaymentURL: 响应解析成功Code: %v, Msg: %s", paymentURLResp.Code, paymentURLResp.Msg)
// 检查响应码(可能是字符串"200"或数字200
codeInt := paymentURLResp.GetCodeInt()
if codeInt != 200 {
logx.Errorf("[云印签API] GetPaymentURL: API返回错误Code: %v, Msg: %s", paymentURLResp.Code, paymentURLResp.Msg)
return "", fmt.Errorf("获取支付链接失败: %s", paymentURLResp.Msg)
}
if paymentURLResp.Data == nil {
logx.Errorf("[云印签API] GetPaymentURL: 支付链接数据为空")
return "", fmt.Errorf("支付链接数据为空")
}
// 优先返回 PayURL如果没有则返回 URL再没有则返回 Path支付宝小程序链接
payURL := paymentURLResp.Data.PayURL
if payURL == "" {
payURL = paymentURLResp.Data.URL
if payURL != "" {
logx.Infof("[云印签API] GetPaymentURL: PayURL为空使用URL字段: %s", payURL)
}
}
if payURL == "" {
payURL = paymentURLResp.Data.Path
if payURL != "" {
logx.Infof("[云印签API] GetPaymentURL: PayURL和URL为空使用Path字段: %s", payURL)
}
}
if payURL == "" {
logx.Errorf("[云印签API] GetPaymentURL: 支付链接为空,响应数据: %+v", paymentURLResp.Data)
return "", fmt.Errorf("支付链接为空")
}
logx.Infof("[云印签API] GetPaymentURL: 获取支付链接成功参与者ID: %s, 支付链接: %s", participantID, payURL)
return payURL, nil
}
// CreateYunYinSignPayOrderResult 创建云印签支付订单结果
type CreateYunYinSignPayOrderResult struct {
PayURL string // 支付链接
ParticipantID string // 参与者ID
TaskID string // 任务ID
}
// CreateYunYinSignPayOrder 创建云印签支付订单(封装完整流程)
func (y *YunYinSignPayService) CreateYunYinSignPayOrder(ctx context.Context, userMobile, userName string, amount float64, outTradeNo string, payType int) (*CreateYunYinSignPayOrderResult, error) {
logx.Infof("[云印签API] CreateYunYinSignPayOrder: 开始创建支付订单,订单号: %s, 用户手机: %s, 用户姓名: %s, 金额: %.2f, 支付类型: %d",
outTradeNo, userMobile, userName, amount, payType)
// 1. 获取token和操作ID带缓存
logx.Infof("[云印签API] CreateYunYinSignPayOrder: 步骤1-获取AccessToken订单号: %s", outTradeNo)
accessToken, err := y.GetAccessToken(ctx)
if err != nil {
logx.Errorf("[云印签API] CreateYunYinSignPayOrder: 获取AccessToken失败订单号: %s, 错误: %v", outTradeNo, err)
return nil, fmt.Errorf("获取云印签token失败: %v", err)
}
logx.Infof("[云印签API] CreateYunYinSignPayOrder: AccessToken获取成功订单号: %s", outTradeNo)
logx.Infof("[云印签API] CreateYunYinSignPayOrder: 步骤2-获取操作ID订单号: %s", outTradeNo)
operationUserId, err := y.GetUserId(ctx, accessToken)
if err != nil {
logx.Errorf("[云印签API] CreateYunYinSignPayOrder: 获取操作ID失败订单号: %s, 错误: %v", outTradeNo, err)
return nil, fmt.Errorf("获取云印签操作ID失败: %v", err)
}
logx.Infof("[云印签API] CreateYunYinSignPayOrder: 操作ID获取成功订单号: %s, 操作ID: %s", outTradeNo, operationUserId)
// 2. 构建参与者列表
logx.Infof("[云印签API] CreateYunYinSignPayOrder: 步骤3-构建参与者列表,订单号: %s", outTradeNo)
// 使用固定的虚拟手机号作为签署方2的占位手机号flowType=2时后续会在小程序端通过授权更新
virtualMobile := "18111111111"
participantList := []ParticipantInfo{
// 签署方1我方
{
ParticipantFlag: "签署方1",
PsnAccount: y.config.Mobile,
PsnName: y.config.Name,
ParticipantCorpName: y.config.CorpName,
ParticipantType: 1, // 1表示企业
},
// 签署方2用户支付方
{
ParticipantFlag: "签署方2",
PsnAccount: virtualMobile, // 使用固定的虚拟手机号(占位符)
PsnName: func() string {
if userName != "" {
return userName
}
return "用户"
}(),
ParticipantType: 0, // 0表示个人
PayeeContractFlag: 1,
Payee: &PayeeInfo{
Amount: amount,
Priority: "1",
},
},
}
logx.Infof("[云印签API] CreateYunYinSignPayOrder: 参与者列表构建完成,订单号: %s, 签署方1: %s(%s), 签署方2: %s(虚拟手机号:%s,实际手机号:%s), 金额: %.2f",
outTradeNo, y.config.Name, y.config.Mobile, userName, virtualMobile, userMobile, amount)
// 3. 发起签署流程
logx.Infof("[云印签API] CreateYunYinSignPayOrder: 步骤4-发起签署流程,订单号: %s, 模板代码: %s", outTradeNo, y.config.TemplateCode)
startSignFlowReq := &StartSignFlowRequest{
TemplateCode: y.config.TemplateCode,
TemplateName: y.config.TemplateName,
FlowType: 2, // 0-正常签署流程签署方信息需准确完整2-临时签署流程(签署方二信息可为占位符)
AutoFill: 1,
SourceOrderCode: outTradeNo,
ParticipantList: participantList,
FillComponents: []FillComponent{}, // 可以根据需要填充
}
signFlowData, err := y.StartSignFlow(ctx, accessToken, operationUserId, startSignFlowReq)
if err != nil {
logx.Errorf("[云印签API] CreateYunYinSignPayOrder: 发起签署流程失败,订单号: %s, 错误: %v", outTradeNo, err)
return nil, fmt.Errorf("发起云印签签署失败: %v", err)
}
if signFlowData.ParticipantID == "" {
logx.Errorf("[云印签API] CreateYunYinSignPayOrder: 签署流程返回的参与者ID为空订单号: %s", outTradeNo)
return nil, fmt.Errorf("签署流程返回的参与者ID为空")
}
logx.Infof("[云印签API] CreateYunYinSignPayOrder: 签署流程发起成功,订单号: %s, 任务ID: %s, 参与者ID: %s",
outTradeNo, signFlowData.TaskID, signFlowData.ParticipantID)
// 4. 获取支付链接
logx.Infof("[云印签API] CreateYunYinSignPayOrder: 步骤5-获取支付链接,订单号: %s, 参与者ID: %s, 支付类型: %d",
outTradeNo, signFlowData.ParticipantID, payType)
payURL, err := y.GetPaymentURL(ctx, accessToken, operationUserId, signFlowData.ParticipantID, payType)
if err != nil {
logx.Errorf("[云印签API] CreateYunYinSignPayOrder: 获取支付链接失败,订单号: %s, 错误: %v", outTradeNo, err)
return nil, fmt.Errorf("获取云印签支付链接失败: %v", err)
}
result := &CreateYunYinSignPayOrderResult{
PayURL: payURL,
ParticipantID: signFlowData.ParticipantID,
TaskID: signFlowData.TaskID,
}
logx.Infof("[云印签API] CreateYunYinSignPayOrder: 创建支付订单完成,订单号: %s, 任务ID: %s, 参与者ID: %s, 支付链接: %s",
outTradeNo, result.TaskID, result.ParticipantID, result.PayURL)
return result, nil
}
// QueryPayeeBillRequest 查询收款单请求
type QueryPayeeBillRequest struct {
SourceOrderCode string `json:"sourceOrderCode,omitempty"` // 来源订单号
PayOrderCode string `json:"payOrderCode,omitempty"` // 支付单号
ContractCode string `json:"contractCode,omitempty"` // 合同号
PayStatus int `json:"payStatus,omitempty"` // 支付状态: 1-待支付, 2-支付成功, 3-退款成功
ChannelOrderNo string `json:"channelOrderNo,omitempty"` // 渠道单号
PayName string `json:"payName,omitempty"` // 付款方名称
ParticipantType int `json:"participantType,omitempty"` // 付款方类型: 1-企业, 0-个人
PayeeName string `json:"payeeName,omitempty"` // 收款方名称
PayeeCorpName string `json:"payeeCorpName,omitempty"` // 收款方企业名称
FlowStartTime string `json:"flowStartTime,omitempty"` // 查询范围的开始时间
FlowFinishTime string `json:"flowFinishTime,omitempty"` // 查询范围的结束时间
ListPageNo int `json:"listPageNo,omitempty"` // 当前页码从1开始
ListPageSize int `json:"listPageSize,omitempty"` // 每页返回的记录数量
}
// QueryPayeeBillResponse 查询收款单响应
type QueryPayeeBillResponse struct {
Code interface{} `json:"code"` // 返回码,可能是字符串"200"或数字200
Msg string `json:"msg"` // 返回码的描述信息
TotalCount int `json:"totalCount"` // 满足查询条件的总记录数
ListPageCount int `json:"listPageCount"` // 总页数
Data []PayeeBillItem `json:"data"` // 收款单记录列表
}
// PayeeBillItem 收款单记录
type PayeeBillItem struct {
ID int64 `json:"id"` // 消息ID
ContractName string `json:"contractName"` // 合同名称
ContractCode string `json:"contractCode"` // 合同号
FlowCode string `json:"flowCode"` // 签署流程编码
PayOrderCode string `json:"payOrderCode"` // 支付单号
Amount float64 `json:"amount"` // 支付金额
PayStatus int `json:"payStatus"` // 支付状态: 0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已退款
RefundAmount float64 `json:"refundAmount"` // 退款金额
ParticipateID int64 `json:"participateId"` // 付款参与方ID
PsnName string `json:"psnName"` // 付款方经办人/个人名称
PsnAccount string `json:"psnAccount"` // 付款方经办人手机
ParticipantType int `json:"participantType"` // 付款方类型 (1-企业, 2-个人)
ParticipantCorpName string `json:"participantCorpName"` // 付款方企业名称
ParticipateBizType string `json:"participateBizType"` // 付款方参与方式
PayeeCorpName string `json:"payeeCorpName"` // 收款方企业名称
CreateTime string `json:"createTime"` // 订单创建时间
ChannelOrderNo string `json:"channelOrderNo"` // 渠道单号 (如微信或支付宝的交易号)
ProviderCode string `json:"providerCode"` // 支付方式编码
CallbackStatus int `json:"callbackStatus"` // 异步回调通知状态
}
// QueryPayeeBillResult 查询收款单结果
type QueryPayeeBillResult struct {
PayStatus int // 支付状态: 0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已退款
PayOrderCode string // 支付单号
ChannelOrderNo string // 渠道单号
Amount float64 // 支付金额
RefundAmount float64 // 退款金额
}
// GetCodeInt 获取 code 的 int 值
func (r *QueryPayeeBillResponse) GetCodeInt() int {
switch v := r.Code.(type) {
case int:
return v
case float64:
return int(v)
case string:
if v == "200" {
return 200
}
return 0
default:
return 0
}
}
// QueryPayeeBill 查询收款单根据sourceOrderCode查询
func (y *YunYinSignPayService) QueryPayeeBill(ctx context.Context, sourceOrderCode string) (*QueryPayeeBillResult, error) {
logx.Infof("[云印签API] QueryPayeeBill: 开始查询收款单,订单号: %s", sourceOrderCode)
// 1. 获取token和操作ID带缓存
logx.Infof("[云印签API] QueryPayeeBill: 步骤1-获取AccessToken订单号: %s", sourceOrderCode)
accessToken, err := y.GetAccessToken(ctx)
if err != nil {
logx.Errorf("[云印签API] QueryPayeeBill: 获取AccessToken失败订单号: %s, 错误: %v", sourceOrderCode, err)
return nil, fmt.Errorf("获取云印签token失败: %v", err)
}
logx.Infof("[云印签API] QueryPayeeBill: 步骤2-获取操作ID订单号: %s", sourceOrderCode)
operationUserId, err := y.GetUserId(ctx, accessToken)
if err != nil {
logx.Errorf("[云印签API] QueryPayeeBill: 获取操作ID失败订单号: %s, 错误: %v", sourceOrderCode, err)
return nil, fmt.Errorf("获取云印签操作ID失败: %v", err)
}
// 2. 构建查询请求
logx.Infof("[云印签API] QueryPayeeBill: 步骤3-构建查询请求,订单号: %s", sourceOrderCode)
reqBody := QueryPayeeBillRequest{
SourceOrderCode: sourceOrderCode,
ListPageNo: 1,
ListPageSize: 10, // 只需要查询第一条匹配的记录
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
logx.Errorf("[云印签API] QueryPayeeBill: 序列化请求参数失败,订单号: %s, 错误: %v", sourceOrderCode, err)
return nil, fmt.Errorf("序列化请求参数失败: %v", err)
}
logx.Infof("[云印签API] QueryPayeeBill: 请求体: %s", string(jsonData))
// 3. 调用查询API
url := fmt.Sprintf("%s/signFlowBill/payeeBillList", y.config.ApiURL)
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonData))
if err != nil {
logx.Errorf("[云印签API] QueryPayeeBill: 创建请求失败,订单号: %s, 错误: %v", sourceOrderCode, err)
return nil, fmt.Errorf("创建请求失败: %v", err)
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("appId", y.config.AppID)
httpReq.Header.Set("accessToken", accessToken)
httpReq.Header.Set("userId", operationUserId)
httpReq.Header.Set("source", "pc")
logx.Infof("[云印签API] QueryPayeeBill: 发送请求URL: %s, Method: POST, 订单号: %s", url, sourceOrderCode)
resp, err := y.client.Do(httpReq)
if err != nil {
logx.Errorf("[云印签API] QueryPayeeBill: HTTP请求失败订单号: %s, 错误: %v", sourceOrderCode, err)
return nil, fmt.Errorf("请求失败: %v", err)
}
defer resp.Body.Close()
logx.Infof("[云印签API] QueryPayeeBill: 收到响应,订单号: %s, 状态码: %d", sourceOrderCode, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
if err != nil {
logx.Errorf("[云印签API] QueryPayeeBill: 读取响应体失败,订单号: %s, 错误: %v", sourceOrderCode, err)
return nil, fmt.Errorf("读取响应失败: %v", err)
}
logx.Infof("[云印签API] QueryPayeeBill: 响应体,订单号: %s, 内容: %s", sourceOrderCode, string(body))
var queryResp QueryPayeeBillResponse
if err := json.Unmarshal(body, &queryResp); err != nil {
logx.Errorf("[云印签API] QueryPayeeBill: 解析响应失败,订单号: %s, 错误: %v, 响应内容: %s", sourceOrderCode, err, string(body))
return nil, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
}
logx.Infof("[云印签API] QueryPayeeBill: 响应解析成功,订单号: %s, Code: %v, Msg: %s, 总记录数: %d, 总页数: %d",
sourceOrderCode, queryResp.Code, queryResp.Msg, queryResp.TotalCount, queryResp.ListPageCount)
// 4. 检查响应码
codeInt := queryResp.GetCodeInt()
if codeInt != 200 {
logx.Errorf("[云印签API] QueryPayeeBill: API返回错误订单号: %s, Code: %v, Msg: %s", sourceOrderCode, queryResp.Code, queryResp.Msg)
return nil, fmt.Errorf("查询收款单失败: %s", queryResp.Msg)
}
// 5. 查找匹配的记录根据sourceOrderCode
if len(queryResp.Data) == 0 {
logx.Infof("[云印签API] QueryPayeeBill: 未找到匹配的收款单记录,订单号: %s", sourceOrderCode)
return nil, fmt.Errorf("未找到匹配的收款单记录")
}
// 取第一条记录因为我们已经用sourceOrderCode精确查询
billItem := queryResp.Data[0]
logx.Infof("[云印签API] QueryPayeeBill: 找到收款单记录,订单号: %s, 支付单号: %s, 支付状态: %d, 金额: %.2f, 退款金额: %.2f, 渠道单号: %s, 参与方ID: %d",
sourceOrderCode, billItem.PayOrderCode, billItem.PayStatus, billItem.Amount, billItem.RefundAmount, billItem.ChannelOrderNo, billItem.ParticipateID)
result := &QueryPayeeBillResult{
PayStatus: billItem.PayStatus,
PayOrderCode: billItem.PayOrderCode,
ChannelOrderNo: billItem.ChannelOrderNo,
Amount: billItem.Amount,
RefundAmount: billItem.RefundAmount,
}
logx.Infof("[云印签API] QueryPayeeBill: 查询收款单完成,订单号: %s, 支付状态: %d", sourceOrderCode, result.PayStatus)
return result, nil
}
// RefundPayeeBillRequest 退款请求
type RefundPayeeBillRequest struct {
SourceOrderCode string `json:"sourceOrderCode,omitempty"` // 来源订单号(与 participateId 二选一)
ParticipateId int64 `json:"participateId,omitempty"` // 参与方ID与 sourceOrderCode 二选一)
RefundAmount float64 `json:"refundAmount"` // 退款金额(必填)
RefundReason string `json:"refundReason,omitempty"` // 退款原因(可选)
}
// RefundPayeeBillResponse 退款响应
type RefundPayeeBillResponse struct {
Code interface{} `json:"code"` // 返回码,可能是字符串"200"或数字200
Msg string `json:"msg"` // 返回码的描述信息
Data interface{} `json:"data,omitempty"`
}
// GetCodeInt 获取 code 的 int 值
func (r *RefundPayeeBillResponse) GetCodeInt() int {
switch v := r.Code.(type) {
case int:
return v
case float64:
return int(v)
case string:
if v == "200" {
return 200
}
return 0
default:
return 0
}
}
// RefundPayeeBill 发起退款
func (y *YunYinSignPayService) RefundPayeeBill(ctx context.Context, sourceOrderCode string, participateId int64, refundAmount float64, refundReason string) error {
logx.Infof("[云印签API] RefundPayeeBill: 开始发起退款,订单号: %s, 参与方ID: %d, 退款金额: %.2f, 退款原因: %s",
sourceOrderCode, participateId, refundAmount, refundReason)
// 1. 获取token和操作ID带缓存
logx.Infof("[云印签API] RefundPayeeBill: 步骤1-获取AccessToken订单号: %s", sourceOrderCode)
accessToken, err := y.GetAccessToken(ctx)
if err != nil {
logx.Errorf("[云印签API] RefundPayeeBill: 获取AccessToken失败订单号: %s, 错误: %v", sourceOrderCode, err)
return fmt.Errorf("获取云印签token失败: %v", err)
}
logx.Infof("[云印签API] RefundPayeeBill: 步骤2-获取操作ID订单号: %s", sourceOrderCode)
operationUserId, err := y.GetUserId(ctx, accessToken)
if err != nil {
logx.Errorf("[云印签API] RefundPayeeBill: 获取操作ID失败订单号: %s, 错误: %v", sourceOrderCode, err)
return fmt.Errorf("获取云印签操作ID失败: %v", err)
}
// 2. 如果只提供了 sourceOrderCode需要先查询收款单获取 participateId
if participateId == 0 && sourceOrderCode != "" {
logx.Infof("[云印签API] RefundPayeeBill: 参与方ID为空通过订单号查询订单号: %s", sourceOrderCode)
// 查询收款单列表获取 participateId
reqBody := QueryPayeeBillRequest{
SourceOrderCode: sourceOrderCode,
ListPageNo: 1,
ListPageSize: 10,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
logx.Errorf("[云印签API] RefundPayeeBill: 序列化查询请求失败,订单号: %s, 错误: %v", sourceOrderCode, err)
return fmt.Errorf("序列化查询请求失败: %v", err)
}
logx.Infof("[云印签API] RefundPayeeBill: 查询请求体: %s", string(jsonData))
url := fmt.Sprintf("%s/signFlowBill/payeeBillList", y.config.ApiURL)
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonData))
if err != nil {
logx.Errorf("[云印签API] RefundPayeeBill: 创建查询请求失败,订单号: %s, 错误: %v", sourceOrderCode, err)
return fmt.Errorf("创建查询请求失败: %v", err)
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("appId", y.config.AppID)
httpReq.Header.Set("accessToken", accessToken)
httpReq.Header.Set("userId", operationUserId)
httpReq.Header.Set("source", "pc")
logx.Infof("[云印签API] RefundPayeeBill: 发送查询请求URL: %s, 订单号: %s", url, sourceOrderCode)
resp, err := y.client.Do(httpReq)
if err != nil {
logx.Errorf("[云印签API] RefundPayeeBill: 查询请求失败,订单号: %s, 错误: %v", sourceOrderCode, err)
return fmt.Errorf("查询请求失败: %v", err)
}
defer resp.Body.Close()
logx.Infof("[云印签API] RefundPayeeBill: 收到查询响应,订单号: %s, 状态码: %d", sourceOrderCode, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
if err != nil {
logx.Errorf("[云印签API] RefundPayeeBill: 读取查询响应失败,订单号: %s, 错误: %v", sourceOrderCode, err)
return fmt.Errorf("读取查询响应失败: %v", err)
}
logx.Infof("[云印签API] RefundPayeeBill: 查询响应体,订单号: %s, 内容: %s", sourceOrderCode, string(body))
var queryResp QueryPayeeBillResponse
if err := json.Unmarshal(body, &queryResp); err != nil {
logx.Errorf("[云印签API] RefundPayeeBill: 解析查询响应失败,订单号: %s, 错误: %v, 响应内容: %s", sourceOrderCode, err, string(body))
return fmt.Errorf("解析查询响应失败: %v, 响应内容: %s", err, string(body))
}
logx.Infof("[云印签API] RefundPayeeBill: 查询响应解析成功,订单号: %s, Code: %v, Msg: %s, 记录数: %d",
sourceOrderCode, queryResp.Code, queryResp.Msg, len(queryResp.Data))
codeInt := queryResp.GetCodeInt()
if codeInt != 200 {
logx.Errorf("[云印签API] RefundPayeeBill: 查询收款单失败,订单号: %s, Code: %v, Msg: %s", sourceOrderCode, queryResp.Code, queryResp.Msg)
return fmt.Errorf("查询收款单失败: %s", queryResp.Msg)
}
if len(queryResp.Data) == 0 {
logx.Errorf("[云印签API] RefundPayeeBill: 未找到匹配的收款单记录,订单号: %s", sourceOrderCode)
return fmt.Errorf("未找到匹配的收款单记录,订单号: %s", sourceOrderCode)
}
// 获取第一条记录的 participateId
billItem := queryResp.Data[0]
participateId = billItem.ParticipateID
logx.Infof("[云印签API] RefundPayeeBill: 通过订单号查询到参与方ID订单号: %s, 参与方ID: %d, 支付单号: %s",
sourceOrderCode, participateId, billItem.PayOrderCode)
}
// 3. 验证参数
if participateId == 0 {
logx.Errorf("[云印签API] RefundPayeeBill: 参与方ID不能为空订单号: %s", sourceOrderCode)
return fmt.Errorf("参与方ID不能为空请提供 participateId 或 sourceOrderCode")
}
if refundAmount <= 0 {
logx.Errorf("[云印签API] RefundPayeeBill: 退款金额必须大于0订单号: %s, 退款金额: %.2f", sourceOrderCode, refundAmount)
return fmt.Errorf("退款金额必须大于0")
}
// 4. 构建退款请求
logx.Infof("[云印签API] RefundPayeeBill: 步骤3-构建退款请求,订单号: %s, 参与方ID: %d, 退款金额: %.2f",
sourceOrderCode, participateId, refundAmount)
refundReq := RefundPayeeBillRequest{
RefundAmount: refundAmount,
RefundReason: refundReason,
}
// 优先使用 participateId
if participateId > 0 {
refundReq.ParticipateId = participateId
logx.Infof("[云印签API] RefundPayeeBill: 使用参与方ID订单号: %s, 参与方ID: %d", sourceOrderCode, participateId)
} else if sourceOrderCode != "" {
// 如果 participateId 仍然为0使用 sourceOrderCode
refundReq.SourceOrderCode = sourceOrderCode
logx.Infof("[云印签API] RefundPayeeBill: 使用订单号,订单号: %s", sourceOrderCode)
}
jsonData, err := json.Marshal(refundReq)
if err != nil {
logx.Errorf("[云印签API] RefundPayeeBill: 序列化退款请求失败,订单号: %s, 错误: %v", sourceOrderCode, err)
return fmt.Errorf("序列化退款请求失败: %v", err)
}
logx.Infof("[云印签API] RefundPayeeBill: 退款请求体: %s", string(jsonData))
// 5. 调用退款API
url := fmt.Sprintf("%s/signFlowBill/refund", y.config.ApiURL)
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonData))
if err != nil {
logx.Errorf("[云印签API] RefundPayeeBill: 创建退款请求失败,订单号: %s, 错误: %v", sourceOrderCode, err)
return fmt.Errorf("创建退款请求失败: %v", err)
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("appId", y.config.AppID)
httpReq.Header.Set("accessToken", accessToken)
httpReq.Header.Set("userId", operationUserId)
httpReq.Header.Set("source", "pc")
logx.Infof("[云印签API] RefundPayeeBill: 发送退款请求URL: %s, Method: POST, 订单号: %s, 参与方ID: %d, 退款金额: %.2f",
url, sourceOrderCode, participateId, refundAmount)
resp, err := y.client.Do(httpReq)
if err != nil {
logx.Errorf("[云印签API] RefundPayeeBill: HTTP请求失败订单号: %s, 错误: %v", sourceOrderCode, err)
return fmt.Errorf("退款请求失败: %v", err)
}
defer resp.Body.Close()
logx.Infof("[云印签API] RefundPayeeBill: 收到退款响应,订单号: %s, 状态码: %d", sourceOrderCode, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
if err != nil {
logx.Errorf("[云印签API] RefundPayeeBill: 读取退款响应失败,订单号: %s, 错误: %v", sourceOrderCode, err)
return fmt.Errorf("读取退款响应失败: %v", err)
}
logx.Infof("[云印签API] RefundPayeeBill: 退款响应体,订单号: %s, 内容: %s", sourceOrderCode, string(body))
var refundResp RefundPayeeBillResponse
if err := json.Unmarshal(body, &refundResp); err != nil {
logx.Errorf("[云印签API] RefundPayeeBill: 解析退款响应失败,订单号: %s, 错误: %v, 响应内容: %s", sourceOrderCode, err, string(body))
return fmt.Errorf("解析退款响应失败: %v, 响应内容: %s", err, string(body))
}
logx.Infof("[云印签API] RefundPayeeBill: 退款响应解析成功,订单号: %s, Code: %v, Msg: %s", sourceOrderCode, refundResp.Code, refundResp.Msg)
// 6. 检查响应码
codeInt := refundResp.GetCodeInt()
if codeInt != 200 {
logx.Errorf("[云印签API] RefundPayeeBill: API返回错误订单号: %s, Code: %v, Msg: %s", sourceOrderCode, refundResp.Code, refundResp.Msg)
return fmt.Errorf("退款失败: %s", refundResp.Msg)
}
logx.Infof("[云印签API] RefundPayeeBill: 退款成功,订单号: %s, 参与方ID: %d, 退款金额: %.2f, 退款原因: %s",
sourceOrderCode, participateId, refundAmount, refundReason)
return nil
}