优化提现功能,新增静默请求选项以支持无弹窗请求,提升用户体验。同时,更新微信转账逻辑,确保在未初始化转账客户端时正确处理状态,增强系统的灵活性和可维护性。调整API响应字段,确保一致性,提升代码可读性。
This commit is contained in:
@@ -17,7 +17,9 @@ WECHAT_NOTIFY_URL=https://soul.quwanzhi.com/api/miniprogram/pay/notify
|
||||
|
||||
# 微信转账配置(API v3)
|
||||
WECHAT_APIV3_KEY=wx3e31b068be59ddc131b068be59ddc2
|
||||
# 公钥证书(本地或 OSS):https://karuocert.oss-cn-shenzhen.aliyuncs.com/1318592501/apiclient_cert.pem
|
||||
WECHAT_CERT_PATH=certs/apiclient_cert.pem
|
||||
# 私钥(线上用 OSS):https://karuocert.oss-cn-shenzhen.aliyuncs.com/1318592501/apiclient_key.pem
|
||||
WECHAT_KEY_PATH=certs/apiclient_key.pem
|
||||
WECHAT_SERIAL_NO=4A1DB62CD5C9BE0B6FC51C30621D6F99686E75C5
|
||||
WECHAT_TRANSFER_URL=https://soul.quwanzhi.com/api/payment/wechat/transfer/notify
|
||||
|
||||
@@ -15,14 +15,14 @@ type Config struct {
|
||||
TrustedProxies []string
|
||||
CORSOrigins []string
|
||||
Version string // APP_VERSION,打包/部署前写在 .env,/health 返回
|
||||
|
||||
|
||||
// 微信小程序配置
|
||||
WechatAppID string
|
||||
WechatAppSecret string
|
||||
WechatMchID string
|
||||
WechatMchKey string
|
||||
WechatNotifyURL string
|
||||
|
||||
|
||||
// 微信转账配置(API v3)
|
||||
WechatAPIv3Key string
|
||||
WechatCertPath string
|
||||
@@ -37,8 +37,7 @@ var defaultCORSOrigins = []string{
|
||||
"http://127.0.0.1:5174",
|
||||
"https://soul.quwanzhi.com",
|
||||
"http://soul.quwanzhi.com",
|
||||
"https://soulapi.quwanzhi.com",
|
||||
"http://soulapi.quwanzhi.com",
|
||||
"http://souladmin.quwanzhi.com",
|
||||
}
|
||||
|
||||
// parseCORSOrigins 从环境变量 CORS_ORIGINS 读取(逗号分隔),未设置则用默认值
|
||||
@@ -80,7 +79,7 @@ func Load() (*Config, error) {
|
||||
if version == "" {
|
||||
version = "0.0.0"
|
||||
}
|
||||
|
||||
|
||||
// 微信配置
|
||||
wechatAppID := os.Getenv("WECHAT_APPID")
|
||||
if wechatAppID == "" {
|
||||
@@ -102,7 +101,7 @@ func Load() (*Config, error) {
|
||||
if wechatNotifyURL == "" {
|
||||
wechatNotifyURL = "https://soul.quwanzhi.com/api/miniprogram/pay/notify" // 默认回调地址
|
||||
}
|
||||
|
||||
|
||||
// 转账配置
|
||||
wechatAPIv3Key := os.Getenv("WECHAT_APIV3_KEY")
|
||||
if wechatAPIv3Key == "" {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"soul-api/internal/database"
|
||||
"soul-api/internal/model"
|
||||
"soul-api/internal/wechat"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -77,12 +79,15 @@ func AdminWithdrawalsList(c *gin.Context) {
|
||||
}
|
||||
|
||||
// AdminWithdrawalsAction PUT /api/admin/withdrawals 审核/打款
|
||||
// approve:先调微信转账接口打款,成功则标为 processing,失败则标为 failed 并返回错误。
|
||||
// 若未初始化微信转账客户端,则仅将状态标为 success(线下打款后批准)。
|
||||
// reject:直接标为 failed。
|
||||
func AdminWithdrawalsAction(c *gin.Context) {
|
||||
var body struct {
|
||||
ID string `json:"id"`
|
||||
Action string `json:"action"`
|
||||
ID string `json:"id"`
|
||||
Action string `json:"action"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
Reason string `json:"reason"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil || body.ID == "" {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "缺少 id 或请求体无效"})
|
||||
@@ -95,25 +100,125 @@ func AdminWithdrawalsAction(c *gin.Context) {
|
||||
if reason == "" && body.Action == "reject" {
|
||||
reason = "管理员拒绝"
|
||||
}
|
||||
var newStatus string
|
||||
|
||||
db := database.DB()
|
||||
now := time.Now()
|
||||
|
||||
switch body.Action {
|
||||
case "approve":
|
||||
newStatus = "success"
|
||||
case "reject":
|
||||
newStatus = "failed"
|
||||
err := db.Model(&model.Withdrawal{}).Where("id = ?", body.ID).Updates(map[string]interface{}{
|
||||
"status": "failed",
|
||||
"error_message": reason,
|
||||
"fail_reason": reason,
|
||||
"processed_at": now,
|
||||
}).Error
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "已拒绝"})
|
||||
return
|
||||
|
||||
case "approve":
|
||||
var w model.Withdrawal
|
||||
if err := db.Where("id = ?", body.ID).First(&w).Error; err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "提现记录不存在"})
|
||||
return
|
||||
}
|
||||
st := ""
|
||||
if w.Status != nil {
|
||||
st = *w.Status
|
||||
}
|
||||
if st != "pending" && st != "processing" && st != "pending_confirm" {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "当前状态不允许批准"})
|
||||
return
|
||||
}
|
||||
|
||||
openID := ""
|
||||
if w.WechatOpenid != nil && *w.WechatOpenid != "" {
|
||||
openID = *w.WechatOpenid
|
||||
}
|
||||
if openID == "" {
|
||||
var u model.User
|
||||
if err := db.Where("id = ?", w.UserID).First(&u).Error; err == nil && u.OpenID != nil {
|
||||
openID = *u.OpenID
|
||||
}
|
||||
}
|
||||
if openID == "" {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "用户未绑定微信 openid,无法打款"})
|
||||
return
|
||||
}
|
||||
|
||||
// 调用微信转账接口;未初始化时仅标记为已打款(线下打款)
|
||||
outBatchNo := wechat.GenerateTransferBatchNo()
|
||||
outDetailNo := wechat.GenerateTransferDetailNo()
|
||||
remark := "提现"
|
||||
if w.Remark != nil && *w.Remark != "" {
|
||||
remark = *w.Remark
|
||||
}
|
||||
amountFen := int(w.Amount * 100)
|
||||
if amountFen < 1 {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "提现金额异常"})
|
||||
return
|
||||
}
|
||||
params := wechat.TransferParams{
|
||||
OutBatchNo: outBatchNo,
|
||||
OutDetailNo: outDetailNo,
|
||||
OpenID: openID,
|
||||
Amount: amountFen,
|
||||
Remark: remark,
|
||||
BatchName: "用户提现",
|
||||
BatchRemark: fmt.Sprintf("用户 %s 提现 %.2f 元", w.UserID, w.Amount),
|
||||
}
|
||||
|
||||
result, err := wechat.InitiateTransfer(params)
|
||||
if err != nil {
|
||||
// 未初始化转账客户端:仅标记为 success,提示线下打款
|
||||
if err.Error() == "转账客户端未初始化" {
|
||||
_ = db.Model(&w).Updates(map[string]interface{}{
|
||||
"status": "success",
|
||||
"processed_at": now,
|
||||
}).Error
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "已标记为已打款。当前未接入微信转账,请线下打款。",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 其他打款失败:记失败原因
|
||||
failMsg := err.Error()
|
||||
_ = db.Model(&w).Updates(map[string]interface{}{
|
||||
"status": "failed",
|
||||
"fail_reason": failMsg,
|
||||
"error_message": failMsg,
|
||||
"processed_at": now,
|
||||
}).Error
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"error": "发起打款失败",
|
||||
"message": failMsg,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 打款已受理,更新为处理中并保存微信批次号
|
||||
processingStatus := "processing"
|
||||
batchID := result.BatchID
|
||||
_ = db.Model(&w).Updates(map[string]interface{}{
|
||||
"status": processingStatus,
|
||||
"batch_no": outBatchNo,
|
||||
"detail_no": outDetailNo,
|
||||
"batch_id": batchID,
|
||||
"processed_at": now,
|
||||
}).Error
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "已发起打款,微信处理中",
|
||||
"data": gin.H{"batch_id": batchID},
|
||||
})
|
||||
return
|
||||
|
||||
default:
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "action 须为 approve 或 reject"})
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
err := database.DB().Model(&model.Withdrawal{}).Where("id = ?", body.ID).Updates(map[string]interface{}{
|
||||
"status": newStatus,
|
||||
"error_message": reason,
|
||||
"processed_at": now,
|
||||
}).Error
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "操作成功"})
|
||||
}
|
||||
|
||||
@@ -235,17 +235,18 @@ func ReferralData(c *gin.Context) {
|
||||
totalVisits = int(visitCount)
|
||||
}
|
||||
|
||||
// 5. 提现统计
|
||||
// 5. 提现统计(与小程序可提现逻辑一致:可提现 = 累计佣金 - 已提现 - 待审核)
|
||||
// 待审核 = pending + processing + pending_confirm,与 /api/withdraw/pending-confirm 口径一致
|
||||
var pendingWithdraw struct{ Total float64 }
|
||||
db.Model(&model.Withdrawal{}).
|
||||
Select("COALESCE(SUM(amount), 0) as total").
|
||||
Where("user_id = ? AND status = 'pending'", userId).
|
||||
Where("user_id = ? AND status IN ?", userId, []string{"pending", "processing", "pending_confirm"}).
|
||||
Scan(&pendingWithdraw)
|
||||
|
||||
var successWithdraw struct{ Total float64 }
|
||||
db.Model(&model.Withdrawal{}).
|
||||
Select("COALESCE(SUM(amount), 0) as total").
|
||||
Where("user_id = ? AND status = 'success'", userId).
|
||||
Where("user_id = ? AND status = ?", userId, "success").
|
||||
Scan(&successWithdraw)
|
||||
|
||||
pendingWithdrawAmount := pendingWithdraw.Total
|
||||
|
||||
@@ -1,152 +1,135 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"soul-api/internal/database"
|
||||
"soul-api/internal/model"
|
||||
"soul-api/internal/wechat"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// WithdrawPost POST /api/withdraw 创建提现申请并发起微信转账
|
||||
// computeAvailableWithdraw 与小程序 / referral 页可提现逻辑一致:可提现 = 累计佣金 - 已提现 - 待审核
|
||||
// 用于 referral/data 展示与 withdraw 接口二次查库校验(不信任前端传参)
|
||||
func computeAvailableWithdraw(db *gorm.DB, userID string) (available, totalCommission, withdrawn, pending float64, minAmount float64) {
|
||||
distributorShare := 0.9
|
||||
minAmount = 10
|
||||
var cfg model.SystemConfig
|
||||
if err := db.Where("config_key = ?", "referral_config").First(&cfg).Error; err == nil {
|
||||
var config map[string]interface{}
|
||||
if _ = json.Unmarshal(cfg.ConfigValue, &config); config != nil {
|
||||
if share, ok := config["distributorShare"].(float64); ok {
|
||||
distributorShare = share / 100
|
||||
}
|
||||
if m, ok := config["minWithdrawAmount"].(float64); ok {
|
||||
minAmount = m
|
||||
}
|
||||
}
|
||||
}
|
||||
var sumOrder struct{ Total float64 }
|
||||
db.Model(&model.Order{}).Where("referrer_id = ? AND status = ?", userID, "paid").
|
||||
Select("COALESCE(SUM(amount), 0) as total").Scan(&sumOrder)
|
||||
totalCommission = sumOrder.Total * distributorShare
|
||||
var w struct{ Total float64 }
|
||||
db.Model(&model.Withdrawal{}).Where("user_id = ? AND status = ?", userID, "success").
|
||||
Select("COALESCE(SUM(amount), 0)").Scan(&w)
|
||||
withdrawn = w.Total
|
||||
db.Model(&model.Withdrawal{}).Where("user_id = ? AND status IN ?", userID, []string{"pending", "processing", "pending_confirm"}).
|
||||
Select("COALESCE(SUM(amount), 0)").Scan(&w)
|
||||
pending = w.Total
|
||||
available = math.Max(0, totalCommission-withdrawn-pending)
|
||||
return available, totalCommission, withdrawn, pending, minAmount
|
||||
}
|
||||
|
||||
// generateWithdrawID 生成提现单号(不依赖 wechat 包)
|
||||
func generateWithdrawID() string {
|
||||
return fmt.Sprintf("WD%d%06d", time.Now().Unix(), time.Now().UnixNano()%1000000)
|
||||
}
|
||||
|
||||
// WithdrawPost POST /api/withdraw 创建提现申请(仅落库待审核,不调用微信打款接口)
|
||||
// 可提现逻辑与小程序 referral 页一致;二次查库校验防止超额。打款由管理端审核后手动/后续接入官方接口再处理。
|
||||
func WithdrawPost(c *gin.Context) {
|
||||
var req struct {
|
||||
UserID string `json:"userId" binding:"required"`
|
||||
Amount float64 `json:"amount" binding:"required"`
|
||||
UserName string `json:"userName"` // 可选,实名校验用
|
||||
UserName string `json:"userName"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "参数错误"})
|
||||
return
|
||||
}
|
||||
|
||||
// 金额验证
|
||||
if req.Amount <= 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "提现金额必须大于0"})
|
||||
return
|
||||
}
|
||||
if req.Amount < 1 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "最低提现金额为1元"})
|
||||
|
||||
db := database.DB()
|
||||
available, _, _, _, minWithdrawAmount := computeAvailableWithdraw(db, req.UserID)
|
||||
if req.Amount > available {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"success": false,
|
||||
"message": fmt.Sprintf("可提现金额不足(当前可提现:%.2f元)", available),
|
||||
})
|
||||
return
|
||||
}
|
||||
if req.Amount < minWithdrawAmount {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"success": false,
|
||||
"message": fmt.Sprintf("最低提现金额为%.0f元", minWithdrawAmount),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
db := database.DB()
|
||||
|
||||
// 查询用户信息,获取 openid 和待提现金额
|
||||
var user model.User
|
||||
if err := db.Where("id = ?", req.UserID).First(&user).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "用户不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
if user.OpenID == nil || *user.OpenID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "用户未绑定微信"})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查待结算收益是否足够
|
||||
pendingEarnings := 0.0
|
||||
if user.PendingEarnings != nil {
|
||||
pendingEarnings = *user.PendingEarnings
|
||||
}
|
||||
|
||||
if pendingEarnings < req.Amount {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"success": false,
|
||||
"message": fmt.Sprintf("可提现金额不足(当前:%.2f元)", pendingEarnings),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 生成转账单号
|
||||
outBatchNo := wechat.GenerateTransferBatchNo()
|
||||
outDetailNo := wechat.GenerateTransferDetailNo()
|
||||
|
||||
// 创建提现记录
|
||||
withdrawID := generateWithdrawID()
|
||||
status := "pending"
|
||||
remark := req.Remark
|
||||
if remark == "" {
|
||||
remark = "提现"
|
||||
}
|
||||
|
||||
withdrawal := model.Withdrawal{
|
||||
ID: outDetailNo,
|
||||
UserID: req.UserID,
|
||||
Amount: req.Amount,
|
||||
Status: &status,
|
||||
BatchNo: &outBatchNo,
|
||||
DetailNo: &outDetailNo,
|
||||
Remark: &remark,
|
||||
ID: withdrawID,
|
||||
UserID: req.UserID,
|
||||
Amount: req.Amount,
|
||||
Status: &status,
|
||||
}
|
||||
|
||||
if err := db.Create(&withdrawal).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "创建提现记录失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 发起微信转账
|
||||
transferAmount := int(req.Amount * 100) // 转为分
|
||||
transferParams := wechat.TransferParams{
|
||||
OutBatchNo: outBatchNo,
|
||||
OutDetailNo: outDetailNo,
|
||||
OpenID: *user.OpenID,
|
||||
Amount: transferAmount,
|
||||
UserName: req.UserName,
|
||||
Remark: remark,
|
||||
BatchName: "用户提现",
|
||||
BatchRemark: fmt.Sprintf("用户 %s 提现 %.2f 元", req.UserID, req.Amount),
|
||||
}
|
||||
|
||||
result, err := wechat.InitiateTransfer(transferParams)
|
||||
if err != nil {
|
||||
// 转账失败,更新提现状态为失败
|
||||
failedStatus := "failed"
|
||||
failReason := fmt.Sprintf("发起转账失败: %v", err)
|
||||
db.Model(&withdrawal).Updates(map[string]interface{}{
|
||||
"status": failedStatus,
|
||||
"fail_reason": failReason,
|
||||
})
|
||||
|
||||
if err := db.Select("ID", "UserID", "Amount", "Status").Create(&withdrawal).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"success": false,
|
||||
"message": "发起转账失败,请稍后重试",
|
||||
"message": "创建提现记录失败",
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新提现记录状态
|
||||
processingStatus := "processing"
|
||||
batchID := result.BatchID
|
||||
db.Model(&withdrawal).Updates(map[string]interface{}{
|
||||
"status": processingStatus,
|
||||
"batch_id": batchID,
|
||||
})
|
||||
|
||||
// 扣减用户的待结算收益,增加已提现金额
|
||||
db.Model(&user).Updates(map[string]interface{}{
|
||||
"pending_earnings": db.Raw("pending_earnings - ?", req.Amount),
|
||||
"withdrawn_earnings": db.Raw("COALESCE(withdrawn_earnings, 0) + ?", req.Amount),
|
||||
})
|
||||
|
||||
fmt.Printf("[Withdraw] 用户 %s 提现 %.2f 元,转账批次号: %s\n", req.UserID, req.Amount, outBatchNo)
|
||||
_ = db.Model(&withdrawal).Updates(map[string]interface{}{
|
||||
"remark": remark,
|
||||
"wechat_openid": user.OpenID,
|
||||
}).Error
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "提现申请已提交,预计2小时内到账",
|
||||
"message": "提现申请已提交,审核通过后将打款至您的微信零钱",
|
||||
"data": map[string]interface{}{
|
||||
"id": withdrawal.ID,
|
||||
"amount": req.Amount,
|
||||
"status": processingStatus,
|
||||
"out_batch_no": outBatchNo,
|
||||
"batch_id": result.BatchID,
|
||||
"created_at": withdrawal.CreatedAt,
|
||||
"id": withdrawal.ID,
|
||||
"amount": req.Amount,
|
||||
"status": "pending",
|
||||
"created_at": withdrawal.CreatedAt,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"soul-api/internal/config"
|
||||
@@ -45,11 +48,28 @@ func InitTransfer(c *config.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadPrivateKey 加载商户私钥
|
||||
// loadPrivateKey 加载商户私钥。path 支持本地路径或 http(s) 链接(如 OSS 地址)。
|
||||
func loadPrivateKey(path string) (*rsa.PrivateKey, error) {
|
||||
privateKeyBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取私钥文件失败: %w", err)
|
||||
var privateKeyBytes []byte
|
||||
if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
|
||||
resp, err := http.Get(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("从链接获取私钥失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("获取私钥返回异常状态: %d", resp.StatusCode)
|
||||
}
|
||||
privateKeyBytes, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取私钥内容失败: %w", err)
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
privateKeyBytes, err = os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取私钥文件失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(privateKeyBytes)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
1
soul-api/tmp/build-errors.log
Normal file
1
soul-api/tmp/build-errors.log
Normal file
@@ -0,0 +1 @@
|
||||
exit status 1exit status 1exit status 1exit status 1
|
||||
Binary file not shown.
Reference in New Issue
Block a user