fix(pay):修复订单回调和超时未关闭问题
This commit is contained in:
1 parent
2797760844
commit
6712ec88df
5 files changed
+94
-5
No files matched your search
@@ -172,6 +172,9 @@ var (
|
||||
}
|
||||
return nil
|
||||
})
|
||||
cs.Register("order_expiration", "*/5 * * * *", func(ctx context.Context) error {
|
||||
return task.ExpirePendingOrders(ctx)
|
||||
})
|
||||
cs.StartBackground(ctx)
|
||||
|
||||
// Initialize update manager
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package public
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
|
||||
"github.com/qianfree/team-api/internal/logic/payment"
|
||||
@@ -31,10 +33,24 @@ func HandlePaymentCallback(r *ghttp.Request) {
|
||||
// 仅查询订单状态决定跳转目标,履约由异步回调完成。
|
||||
func HandlePaymentEpayReturn(r *ghttp.Request) {
|
||||
orderNo := r.GetQuery("out_trade_no").String()
|
||||
paid := payment.QueryOrderPaid(r.Context(), orderNo)
|
||||
if orderNo == "" {
|
||||
r.Response.RedirectTo("/console/wallet?pay=fail")
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
// 异步 notify 可能晚于浏览器回跳到达:最多轮询约 4 秒等待回调入账。
|
||||
// 仍未到账不一定是失败(回调可能仍在途),引导到"处理中"页由前端继续刷新,
|
||||
// 避免给用户展示误导性的"失败"。
|
||||
paid := payment.QueryOrderPaid(ctx, orderNo)
|
||||
for i := 0; !paid && i < 8; i++ {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
paid = payment.QueryOrderPaid(ctx, orderNo)
|
||||
}
|
||||
|
||||
if paid {
|
||||
r.Response.RedirectTo("/console/wallet?pay=success")
|
||||
return
|
||||
}
|
||||
r.Response.RedirectTo("/console/wallet?pay=fail")
|
||||
r.Response.RedirectTo("/console/wallet?pay=processing")
|
||||
}
|
||||
@@ -57,9 +57,42 @@ func LoadChannelConfig(ctx context.Context, channelType string) (jsonStr string,
|
||||
|
||||
// SaveChannelConfig 将渠道配置 JSON 保存到 sys_options。
|
||||
func SaveChannelConfig(ctx context.Context, channelType string, configJSON string) error {
|
||||
// 启用线上支付渠道前,必须先配置全局回调地址,否则网关异步通知无法回传,
|
||||
// 支付成功也无法入账(发起支付时还有 RequireCallbackBaseURL 二次校验)。
|
||||
if err := validateChannelEnable(ctx, channelType, configJSON); err != nil {
|
||||
return err
|
||||
}
|
||||
return lcommon.Config().SetOption(ctx, channelConfigKey(channelType), configJSON)
|
||||
}
|
||||
|
||||
// validateChannelEnable 校验"启用渠道"的前置条件:启用易支付要求回调地址已配置。
|
||||
func validateChannelEnable(ctx context.Context, channelType string, configJSON string) error {
|
||||
if channelType != "epay" {
|
||||
return nil
|
||||
}
|
||||
cfg, err := ParseChannelConfig(channelType, configJSON)
|
||||
if err != nil || cfg == nil {
|
||||
return nil // JSON 合法性由调用方校验;此处只关心启用前置条件
|
||||
}
|
||||
ec, ok := cfg.(*EpayConfig)
|
||||
if !ok || !ec.IsEnabled {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(GetCallbackBaseURL(ctx)) == "" {
|
||||
return lcommon.NewBadRequestError("请先在「支付设置」中配置回调基础URL,再启用支付渠道;否则支付成功后无法回调入账")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RequireCallbackBaseURL 发起支付前的二次防护:回调地址未配置则拒绝发起。
|
||||
// 渠道启用时已拦截一次,这里防止"启用后被清空回调地址"等场景下仍能发起支付。
|
||||
func RequireCallbackBaseURL(ctx context.Context) error {
|
||||
if strings.TrimSpace(GetCallbackBaseURL(ctx)) == "" {
|
||||
return lcommon.NewBusinessError(422, "支付回调地址未配置,请联系管理员在「支付设置」中配置回调基础URL")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListAllChannels 返回所有已注册渠道的配置(含未配置的,返回默认值)。
|
||||
func ListAllChannels(ctx context.Context) []map[string]interface{} {
|
||||
result := make([]map[string]interface{}, 0, len(channelTypes))
|
||||
|
||||
@@ -187,6 +187,9 @@ func (s *sTenant) OrderPay(ctx context.Context, req *v1.TenantOrderPayReq) (*v1.
|
||||
if req.PaymentChannel == "" {
|
||||
return nil, lcommon.NewBusinessError(422, "请选择支付渠道")
|
||||
}
|
||||
if err := payment.RequireCallbackBaseURL(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//
|
||||
orderNo, finalAmount, currency, orderType, _, err := getOrderForPay(ctx, tenantID, orderID)
|
||||
@@ -342,6 +345,9 @@ func (s *sTenant) RechargeCreate(ctx context.Context, req *v1.TenantRechargeCrea
|
||||
}
|
||||
|
||||
// 2. 从 sys_options 加载渠道配置
|
||||
if err := payment.RequireCallbackBaseURL(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg, err := payment.GetChannelConfigAndProvider(ctx, req.PaymentChannel)
|
||||
if err != nil {
|
||||
return nil, lcommon.NewBusinessError(422, err.Error())
|
||||
|
||||
@@ -17,7 +17,7 @@ const selectedPaymentMethod = ref('')
|
||||
const paymentInfo = ref<any>(null)
|
||||
|
||||
// Pay result notification
|
||||
const payResult = ref<'success' | 'fail' | ''>('')
|
||||
const payResult = ref<'success' | 'fail' | 'processing' | ''>('')
|
||||
|
||||
// Frozen items
|
||||
const showFrozenModal = ref(false)
|
||||
@@ -239,11 +239,19 @@ onMounted(() => {
|
||||
|
||||
// Handle pay result from return URL
|
||||
const pay = route.query.pay as string
|
||||
if (pay === 'success' || pay === 'fail') {
|
||||
if (pay === 'success' || pay === 'fail' || pay === 'processing') {
|
||||
payResult.value = pay
|
||||
setTimeout(() => { payResult.value = '' }, 5000)
|
||||
if (pay === 'success') {
|
||||
setTimeout(() => { payResult.value = '' }, 5000)
|
||||
fetchWallet()
|
||||
} else if (pay === 'processing') {
|
||||
// 异步回调可能仍在途:延迟刷新余额,到账后自动更新;超时后提示联系客服
|
||||
fetchWallet()
|
||||
setTimeout(fetchWallet, 3000)
|
||||
setTimeout(fetchWallet, 8000)
|
||||
setTimeout(() => { payResult.value = '' }, 12000)
|
||||
} else {
|
||||
setTimeout(() => { payResult.value = '' }, 5000)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -295,6 +303,18 @@ onBeforeUnmount(() => {
|
||||
<Icon name="x" size="sm" />
|
||||
</button>
|
||||
</div>
|
||||
<div v-else-if="payResult === 'processing'" class="pay-banner pay-banner-processing">
|
||||
<div class="pay-banner-icon">
|
||||
<Icon name="infoCircle" size="sm" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-semibold">支付结果确认中</p>
|
||||
<p class="text-xs opacity-80 mt-0.5">正在等待到账,余额会在确认后自动更新</p>
|
||||
</div>
|
||||
<button class="opacity-60 hover:opacity-100 transition-opacity p-1" @click="payResult = ''">
|
||||
<Icon name="x" size="sm" />
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- ============================================ -->
|
||||
@@ -713,6 +733,17 @@ onBeforeUnmount(() => {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-shrink: 0; color: #dc2626;
|
||||
}
|
||||
.pay-banner-processing {
|
||||
background-color: #fffbeb;
|
||||
border: 1px solid rgba(252, 211, 77, 0.6);
|
||||
color: #92400e;
|
||||
}
|
||||
.pay-banner-processing .pay-banner-icon {
|
||||
height: 2rem; width: 2rem; border-radius: 9999px;
|
||||
background-color: #fef3c7;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-shrink: 0; color: #d97706;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
Balance Hero Card
|
||||
|
||||
Reference in new issue
Block a user