Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
09351e94
Commit
09351e94
authored
Apr 21, 2026
by
IanShaw027
Browse files
fix auth completion and payment resume hardening
parent
f11b7d51
Changes
8
Hide whitespace changes
Inline
Side-by-side
backend/internal/handler/auth_oauth_pending_flow.go
View file @
09351e94
...
@@ -1350,10 +1350,24 @@ func (h *AuthHandler) ExchangePendingOAuthCompletion(c *gin.Context) {
...
@@ -1350,10 +1350,24 @@ func (h *AuthHandler) ExchangePendingOAuthCompletion(c *gin.Context) {
return
return
}
}
if
!
adoptionDecision
.
hasDecision
()
{
if
!
adoptionDecision
.
hasDecision
()
{
response
.
Success
(
c
,
payload
)
adoptionRequired
,
_
:=
payload
[
"adoption_required"
]
.
(
bool
)
return
if
adoptionRequired
{
response
.
Success
(
c
,
payload
)
return
}
}
}
decision
,
err
:=
h
.
upsertPendingOAuthAdoptionDecision
(
c
,
session
.
ID
,
adoptionDecision
)
decisionReq
:=
adoptionDecision
if
!
decisionReq
.
hasDecision
()
{
adoptDisplayName
:=
false
adoptAvatar
:=
false
decisionReq
=
oauthAdoptionDecisionRequest
{
AdoptDisplayName
:
&
adoptDisplayName
,
AdoptAvatar
:
&
adoptAvatar
,
}
}
decision
,
err
:=
h
.
ensurePendingOAuthAdoptionDecision
(
c
,
session
.
ID
,
decisionReq
)
if
err
!=
nil
{
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
response
.
ErrorFrom
(
c
,
err
)
return
return
...
...
backend/internal/handler/auth_oauth_pending_flow_test.go
View file @
09351e94
...
@@ -523,6 +523,68 @@ func TestExchangePendingOAuthCompletionLoginFalseFalseBindsIdentityWithoutAdopti
...
@@ -523,6 +523,68 @@ func TestExchangePendingOAuthCompletionLoginFalseFalseBindsIdentityWithoutAdopti
require
.
NotNil
(
t
,
storedSession
.
ConsumedAt
)
require
.
NotNil
(
t
,
storedSession
.
ConsumedAt
)
}
}
func
TestExchangePendingOAuthCompletionLoginWithoutDecisionStillBindsIdentity
(
t
*
testing
.
T
)
{
handler
,
client
:=
newOAuthPendingFlowTestHandler
(
t
,
false
)
ctx
:=
context
.
Background
()
userEntity
,
err
:=
client
.
User
.
Create
()
.
SetEmail
(
"login-nodecision@example.com"
)
.
SetUsername
(
"legacy-name"
)
.
SetPasswordHash
(
"hash"
)
.
SetRole
(
service
.
RoleUser
)
.
SetStatus
(
service
.
StatusActive
)
.
Save
(
ctx
)
require
.
NoError
(
t
,
err
)
session
,
err
:=
client
.
PendingAuthSession
.
Create
()
.
SetSessionToken
(
"login-nodecision-session-token"
)
.
SetIntent
(
"login"
)
.
SetProviderType
(
"linuxdo"
)
.
SetProviderKey
(
"linuxdo"
)
.
SetProviderSubject
(
"login-nodecision-123"
)
.
SetTargetUserID
(
userEntity
.
ID
)
.
SetResolvedEmail
(
userEntity
.
Email
)
.
SetBrowserSessionKey
(
"login-nodecision-browser-session-key"
)
.
SetUpstreamIdentityClaims
(
map
[
string
]
any
{
"username"
:
"login-nodecision-user"
,
})
.
SetLocalFlowState
(
map
[
string
]
any
{
oauthCompletionResponseKey
:
map
[
string
]
any
{
"access_token"
:
"access-token"
,
},
})
.
SetExpiresAt
(
time
.
Now
()
.
UTC
()
.
Add
(
10
*
time
.
Minute
))
.
Save
(
ctx
)
require
.
NoError
(
t
,
err
)
recorder
:=
httptest
.
NewRecorder
()
ginCtx
,
_
:=
gin
.
CreateTestContext
(
recorder
)
req
:=
httptest
.
NewRequest
(
http
.
MethodPost
,
"/api/v1/auth/oauth/pending/exchange"
,
nil
)
req
.
AddCookie
(
&
http
.
Cookie
{
Name
:
oauthPendingSessionCookieName
,
Value
:
encodeCookieValue
(
session
.
SessionToken
)})
req
.
AddCookie
(
&
http
.
Cookie
{
Name
:
oauthPendingBrowserCookieName
,
Value
:
encodeCookieValue
(
"login-nodecision-browser-session-key"
)})
ginCtx
.
Request
=
req
handler
.
ExchangePendingOAuthCompletion
(
ginCtx
)
require
.
Equal
(
t
,
http
.
StatusOK
,
recorder
.
Code
)
identity
,
err
:=
client
.
AuthIdentity
.
Query
()
.
Where
(
authidentity
.
ProviderTypeEQ
(
"linuxdo"
),
authidentity
.
ProviderKeyEQ
(
"linuxdo"
),
authidentity
.
ProviderSubjectEQ
(
"login-nodecision-123"
),
)
.
Only
(
ctx
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
userEntity
.
ID
,
identity
.
UserID
)
storedSession
,
err
:=
client
.
PendingAuthSession
.
Query
()
.
Where
(
pendingauthsession
.
IDEQ
(
session
.
ID
))
.
Only
(
ctx
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
storedSession
.
ConsumedAt
)
}
func
TestExchangePendingOAuthCompletionBlocksBackendModeBeforeReturningTokenPayload
(
t
*
testing
.
T
)
{
func
TestExchangePendingOAuthCompletionBlocksBackendModeBeforeReturningTokenPayload
(
t
*
testing
.
T
)
{
handler
,
client
:=
newOAuthPendingFlowTestHandlerWithDependencies
(
t
,
oauthPendingFlowTestHandlerOptions
{
handler
,
client
:=
newOAuthPendingFlowTestHandlerWithDependencies
(
t
,
oauthPendingFlowTestHandlerOptions
{
settingValues
:
map
[
string
]
string
{
settingValues
:
map
[
string
]
string
{
...
...
backend/internal/service/payment_order_lifecycle.go
View file @
09351e94
...
@@ -190,8 +190,9 @@ func (s *PaymentService) VerifyOrderByOutTradeNo(ctx context.Context, outTradeNo
...
@@ -190,8 +190,9 @@ func (s *PaymentService) VerifyOrderByOutTradeNo(ctx context.Context, outTradeNo
return
o
,
nil
return
o
,
nil
}
}
// VerifyOrderPublic verifies payment status without user authentication.
// VerifyOrderPublic returns the currently persisted public order state without
// Used by the payment result page when the user's session has expired.
// triggering any upstream reconciliation. Signed resume-token recovery is the
// only public recovery path allowed to query upstream state.
func
(
s
*
PaymentService
)
VerifyOrderPublic
(
ctx
context
.
Context
,
outTradeNo
string
)
(
*
dbent
.
PaymentOrder
,
error
)
{
func
(
s
*
PaymentService
)
VerifyOrderPublic
(
ctx
context
.
Context
,
outTradeNo
string
)
(
*
dbent
.
PaymentOrder
,
error
)
{
o
,
err
:=
s
.
entClient
.
PaymentOrder
.
Query
()
.
o
,
err
:=
s
.
entClient
.
PaymentOrder
.
Query
()
.
Where
(
paymentorder
.
OutTradeNo
(
outTradeNo
))
.
Where
(
paymentorder
.
OutTradeNo
(
outTradeNo
))
.
...
@@ -199,15 +200,6 @@ func (s *PaymentService) VerifyOrderPublic(ctx context.Context, outTradeNo strin
...
@@ -199,15 +200,6 @@ func (s *PaymentService) VerifyOrderPublic(ctx context.Context, outTradeNo strin
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
infraerrors
.
NotFound
(
"NOT_FOUND"
,
"order not found"
)
return
nil
,
infraerrors
.
NotFound
(
"NOT_FOUND"
,
"order not found"
)
}
}
if
o
.
Status
==
OrderStatusPending
||
o
.
Status
==
OrderStatusExpired
{
result
:=
s
.
checkPaid
(
ctx
,
o
)
if
result
==
checkPaidResultAlreadyPaid
{
o
,
err
=
s
.
entClient
.
PaymentOrder
.
Get
(
ctx
,
o
.
ID
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"reload order: %w"
,
err
)
}
}
}
return
o
,
nil
return
o
,
nil
}
}
...
...
backend/internal/service/payment_resume_lookup_test.go
View file @
09351e94
...
@@ -200,3 +200,48 @@ func TestGetPublicOrderByResumeTokenChecksUpstreamForPendingOrder(t *testing.T)
...
@@ -200,3 +200,48 @@ func TestGetPublicOrderByResumeTokenChecksUpstreamForPendingOrder(t *testing.T)
require
.
Equal
(
t
,
order
.
ID
,
got
.
ID
)
require
.
Equal
(
t
,
order
.
ID
,
got
.
ID
)
require
.
Equal
(
t
,
1
,
provider
.
queryCount
)
require
.
Equal
(
t
,
1
,
provider
.
queryCount
)
}
}
func
TestVerifyOrderPublicDoesNotCheckUpstreamForPendingOrder
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
client
:=
newPaymentConfigServiceTestClient
(
t
)
user
,
err
:=
client
.
User
.
Create
()
.
SetEmail
(
"public-verify@example.com"
)
.
SetPasswordHash
(
"hash"
)
.
SetUsername
(
"public-verify-user"
)
.
Save
(
ctx
)
require
.
NoError
(
t
,
err
)
order
,
err
:=
client
.
PaymentOrder
.
Create
()
.
SetUserID
(
user
.
ID
)
.
SetUserEmail
(
user
.
Email
)
.
SetUserName
(
user
.
Username
)
.
SetAmount
(
88
)
.
SetPayAmount
(
88
)
.
SetFeeRate
(
0
)
.
SetRechargeCode
(
"PUBLIC-VERIFY"
)
.
SetOutTradeNo
(
"sub2_public_verify_pending"
)
.
SetPaymentType
(
payment
.
TypeAlipay
)
.
SetPaymentTradeNo
(
"trade-public-verify"
)
.
SetOrderType
(
payment
.
OrderTypeBalance
)
.
SetStatus
(
OrderStatusPending
)
.
SetExpiresAt
(
time
.
Now
()
.
Add
(
time
.
Hour
))
.
SetClientIP
(
"127.0.0.1"
)
.
SetSrcHost
(
"api.example.com"
)
.
Save
(
ctx
)
require
.
NoError
(
t
,
err
)
registry
:=
payment
.
NewRegistry
()
provider
:=
&
paymentResumeLookupProvider
{}
registry
.
Register
(
provider
)
svc
:=
&
PaymentService
{
entClient
:
client
,
registry
:
registry
,
providersLoaded
:
true
,
}
got
,
err
:=
svc
.
VerifyOrderPublic
(
ctx
,
order
.
OutTradeNo
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
order
.
ID
,
got
.
ID
)
require
.
Equal
(
t
,
0
,
provider
.
queryCount
)
}
backend/internal/service/payment_resume_service.go
View file @
09351e94
...
@@ -36,6 +36,9 @@ const (
...
@@ -36,6 +36,9 @@ const (
paymentResumeNotConfiguredCode
=
"PAYMENT_RESUME_NOT_CONFIGURED"
paymentResumeNotConfiguredCode
=
"PAYMENT_RESUME_NOT_CONFIGURED"
paymentResumeNotConfiguredMessage
=
"payment resume tokens require a configured signing key"
paymentResumeNotConfiguredMessage
=
"payment resume tokens require a configured signing key"
paymentResumeTokenTTL
=
24
*
time
.
Hour
wechatPaymentResumeTokenTTL
=
15
*
time
.
Minute
)
)
type
ResumeTokenClaims
struct
{
type
ResumeTokenClaims
struct
{
...
@@ -46,6 +49,7 @@ type ResumeTokenClaims struct {
...
@@ -46,6 +49,7 @@ type ResumeTokenClaims struct {
PaymentType
string
`json:"pt,omitempty"`
PaymentType
string
`json:"pt,omitempty"`
CanonicalReturnURL
string
`json:"ru,omitempty"`
CanonicalReturnURL
string
`json:"ru,omitempty"`
IssuedAt
int64
`json:"iat"`
IssuedAt
int64
`json:"iat"`
ExpiresAt
int64
`json:"exp,omitempty"`
}
}
type
WeChatPaymentResumeClaims
struct
{
type
WeChatPaymentResumeClaims
struct
{
...
@@ -58,6 +62,7 @@ type WeChatPaymentResumeClaims struct {
...
@@ -58,6 +62,7 @@ type WeChatPaymentResumeClaims struct {
RedirectTo
string
`json:"rd,omitempty"`
RedirectTo
string
`json:"rd,omitempty"`
Scope
string
`json:"scp,omitempty"`
Scope
string
`json:"scp,omitempty"`
IssuedAt
int64
`json:"iat"`
IssuedAt
int64
`json:"iat"`
ExpiresAt
int64
`json:"exp,omitempty"`
}
}
type
PaymentResumeService
struct
{
type
PaymentResumeService
struct
{
...
@@ -263,6 +268,9 @@ func (s *PaymentResumeService) CreateToken(claims ResumeTokenClaims) (string, er
...
@@ -263,6 +268,9 @@ func (s *PaymentResumeService) CreateToken(claims ResumeTokenClaims) (string, er
if
claims
.
IssuedAt
==
0
{
if
claims
.
IssuedAt
==
0
{
claims
.
IssuedAt
=
time
.
Now
()
.
Unix
()
claims
.
IssuedAt
=
time
.
Now
()
.
Unix
()
}
}
if
claims
.
ExpiresAt
==
0
{
claims
.
ExpiresAt
=
time
.
Now
()
.
Add
(
paymentResumeTokenTTL
)
.
Unix
()
}
return
s
.
createSignedToken
(
claims
)
return
s
.
createSignedToken
(
claims
)
}
}
...
@@ -277,6 +285,9 @@ func (s *PaymentResumeService) ParseToken(token string) (*ResumeTokenClaims, err
...
@@ -277,6 +285,9 @@ func (s *PaymentResumeService) ParseToken(token string) (*ResumeTokenClaims, err
if
claims
.
OrderID
<=
0
{
if
claims
.
OrderID
<=
0
{
return
nil
,
infraerrors
.
BadRequest
(
"INVALID_RESUME_TOKEN"
,
"resume token missing order id"
)
return
nil
,
infraerrors
.
BadRequest
(
"INVALID_RESUME_TOKEN"
,
"resume token missing order id"
)
}
}
if
err
:=
validatePaymentResumeExpiry
(
claims
.
ExpiresAt
,
"INVALID_RESUME_TOKEN"
,
"resume token has expired"
);
err
!=
nil
{
return
nil
,
err
}
return
&
claims
,
nil
return
&
claims
,
nil
}
}
...
@@ -291,6 +302,9 @@ func (s *PaymentResumeService) CreateWeChatPaymentResumeToken(claims WeChatPayme
...
@@ -291,6 +302,9 @@ func (s *PaymentResumeService) CreateWeChatPaymentResumeToken(claims WeChatPayme
if
claims
.
IssuedAt
==
0
{
if
claims
.
IssuedAt
==
0
{
claims
.
IssuedAt
=
time
.
Now
()
.
Unix
()
claims
.
IssuedAt
=
time
.
Now
()
.
Unix
()
}
}
if
claims
.
ExpiresAt
==
0
{
claims
.
ExpiresAt
=
time
.
Now
()
.
Add
(
wechatPaymentResumeTokenTTL
)
.
Unix
()
}
if
normalized
:=
NormalizeVisibleMethod
(
claims
.
PaymentType
);
normalized
!=
""
{
if
normalized
:=
NormalizeVisibleMethod
(
claims
.
PaymentType
);
normalized
!=
""
{
claims
.
PaymentType
=
normalized
claims
.
PaymentType
=
normalized
}
}
...
@@ -319,6 +333,9 @@ func (s *PaymentResumeService) ParseWeChatPaymentResumeToken(token string) (*WeC
...
@@ -319,6 +333,9 @@ func (s *PaymentResumeService) ParseWeChatPaymentResumeToken(token string) (*WeC
if
claims
.
OpenID
==
""
{
if
claims
.
OpenID
==
""
{
return
nil
,
infraerrors
.
BadRequest
(
"INVALID_WECHAT_PAYMENT_RESUME_TOKEN"
,
"wechat payment resume token missing openid"
)
return
nil
,
infraerrors
.
BadRequest
(
"INVALID_WECHAT_PAYMENT_RESUME_TOKEN"
,
"wechat payment resume token missing openid"
)
}
}
if
err
:=
validatePaymentResumeExpiry
(
claims
.
ExpiresAt
,
"INVALID_WECHAT_PAYMENT_RESUME_TOKEN"
,
"wechat payment resume token has expired"
);
err
!=
nil
{
return
nil
,
err
}
if
normalized
:=
NormalizeVisibleMethod
(
claims
.
PaymentType
);
normalized
!=
""
{
if
normalized
:=
NormalizeVisibleMethod
(
claims
.
PaymentType
);
normalized
!=
""
{
claims
.
PaymentType
=
normalized
claims
.
PaymentType
=
normalized
}
}
...
@@ -355,6 +372,16 @@ func (s *PaymentResumeService) parseSignedToken(token string, dest any) error {
...
@@ -355,6 +372,16 @@ func (s *PaymentResumeService) parseSignedToken(token string, dest any) error {
return
json
.
Unmarshal
(
payload
,
dest
)
return
json
.
Unmarshal
(
payload
,
dest
)
}
}
func
validatePaymentResumeExpiry
(
expiresAt
int64
,
code
,
message
string
)
error
{
if
expiresAt
<=
0
{
return
nil
}
if
time
.
Now
()
.
Unix
()
>
expiresAt
{
return
infraerrors
.
BadRequest
(
code
,
message
)
}
return
nil
}
func
(
s
*
PaymentResumeService
)
sign
(
payload
string
)
string
{
func
(
s
*
PaymentResumeService
)
sign
(
payload
string
)
string
{
mac
:=
hmac
.
New
(
sha256
.
New
,
s
.
signingKey
)
mac
:=
hmac
.
New
(
sha256
.
New
,
s
.
signingKey
)
_
,
_
=
mac
.
Write
([]
byte
(
payload
))
_
,
_
=
mac
.
Write
([]
byte
(
payload
))
...
...
backend/internal/service/payment_resume_service_test.go
View file @
09351e94
...
@@ -11,6 +11,7 @@ import (
...
@@ -11,6 +11,7 @@ import (
"net/url"
"net/url"
"strconv"
"strconv"
"testing"
"testing"
"time"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/Wei-Shaw/sub2api/internal/payment"
)
)
...
@@ -175,6 +176,26 @@ func TestParseTokenRejectsFallbackSignedTokenWhenSigningKeyMissing(t *testing.T)
...
@@ -175,6 +176,26 @@ func TestParseTokenRejectsFallbackSignedTokenWhenSigningKeyMissing(t *testing.T)
}
}
}
}
func
TestParseTokenRejectsExpiredToken
(
t
*
testing
.
T
)
{
t
.
Parallel
()
svc
:=
NewPaymentResumeService
([]
byte
(
"0123456789abcdef0123456789abcdef"
))
token
,
err
:=
svc
.
CreateToken
(
ResumeTokenClaims
{
OrderID
:
42
,
UserID
:
7
,
IssuedAt
:
time
.
Now
()
.
Add
(
-
25
*
time
.
Hour
)
.
Unix
(),
ExpiresAt
:
time
.
Now
()
.
Add
(
-
1
*
time
.
Hour
)
.
Unix
(),
})
if
err
!=
nil
{
t
.
Fatalf
(
"CreateToken returned error: %v"
,
err
)
}
_
,
err
=
svc
.
ParseToken
(
token
)
if
err
==
nil
{
t
.
Fatal
(
"ParseToken should reject expired tokens"
)
}
}
func
TestWeChatPaymentResumeTokenRoundTrip
(
t
*
testing
.
T
)
{
func
TestWeChatPaymentResumeTokenRoundTrip
(
t
*
testing
.
T
)
{
t
.
Parallel
()
t
.
Parallel
()
...
@@ -233,6 +254,26 @@ func TestParseWeChatPaymentResumeTokenRejectsFallbackSignedTokenWhenSigningKeyMi
...
@@ -233,6 +254,26 @@ func TestParseWeChatPaymentResumeTokenRejectsFallbackSignedTokenWhenSigningKeyMi
}
}
}
}
func
TestParseWeChatPaymentResumeTokenRejectsExpiredToken
(
t
*
testing
.
T
)
{
t
.
Parallel
()
svc
:=
NewPaymentResumeService
([]
byte
(
"0123456789abcdef0123456789abcdef"
))
token
,
err
:=
svc
.
CreateWeChatPaymentResumeToken
(
WeChatPaymentResumeClaims
{
OpenID
:
"openid-123"
,
PaymentType
:
payment
.
TypeWxpay
,
IssuedAt
:
time
.
Now
()
.
Add
(
-
30
*
time
.
Minute
)
.
Unix
(),
ExpiresAt
:
time
.
Now
()
.
Add
(
-
1
*
time
.
Minute
)
.
Unix
(),
})
if
err
!=
nil
{
t
.
Fatalf
(
"CreateWeChatPaymentResumeToken returned error: %v"
,
err
)
}
_
,
err
=
svc
.
ParseWeChatPaymentResumeToken
(
token
)
if
err
==
nil
{
t
.
Fatal
(
"ParseWeChatPaymentResumeToken should reject expired tokens"
)
}
}
func
TestNormalizeVisibleMethodSource
(
t
*
testing
.
T
)
{
func
TestNormalizeVisibleMethodSource
(
t
*
testing
.
T
)
{
t
.
Parallel
()
t
.
Parallel
()
...
...
frontend/src/views/user/PaymentResultView.vue
View file @
09351e94
...
@@ -219,7 +219,6 @@ onMounted(async () => {
...
@@ -219,7 +219,6 @@ onMounted(async () => {
const
routeOrderId
=
Number
(
route
.
query
.
order_id
)
||
0
const
routeOrderId
=
Number
(
route
.
query
.
order_id
)
||
0
const
outTradeNo
=
String
(
route
.
query
.
out_trade_no
||
''
)
const
outTradeNo
=
String
(
route
.
query
.
out_trade_no
||
''
)
let
orderId
=
0
let
orderId
=
0
let
canUseLegacyPublicVerify
=
false
if
(
resumeToken
&&
typeof
window
!==
'
undefined
'
)
{
if
(
resumeToken
&&
typeof
window
!==
'
undefined
'
)
{
const
restored
=
readPaymentRecoverySnapshot
(
const
restored
=
readPaymentRecoverySnapshot
(
...
@@ -264,23 +263,12 @@ onMounted(async () => {
...
@@ -264,23 +263,12 @@ onMounted(async () => {
const
hasLegacyFallbackContext
=
typeof
route
.
query
.
trade_status
===
'
string
'
const
hasLegacyFallbackContext
=
typeof
route
.
query
.
trade_status
===
'
string
'
&&
route
.
query
.
trade_status
.
trim
()
!==
''
&&
route
.
query
.
trade_status
.
trim
()
!==
''
if
(
!
order
.
value
&&
!
resumeToken
&&
!
orderId
&&
outTradeNo
&&
hasLegacyFallbackContext
)
{
if
(
!
order
.
value
&&
!
resumeToken
&&
!
orderId
&&
outTradeNo
&&
hasLegacyFallbackContext
)
{
canUseLegacyPublicVerify
=
true
returnInfo
.
value
=
{
returnInfo
.
value
=
{
outTradeNo
,
outTradeNo
,
money
:
String
(
route
.
query
.
money
||
''
),
money
:
String
(
route
.
query
.
money
||
''
),
type
:
String
(
route
.
query
.
type
||
''
),
type
:
String
(
route
.
query
.
type
||
''
),
tradeStatus
:
String
(
route
.
query
.
trade_status
||
''
),
tradeStatus
:
String
(
route
.
query
.
trade_status
||
''
),
}
}
try
{
const
result
=
await
paymentAPI
.
verifyOrderPublic
(
outTradeNo
)
order
.
value
=
result
.
data
}
catch
(
_err
:
unknown
)
{
try
{
const
result
=
await
paymentAPI
.
verifyOrder
(
outTradeNo
)
order
.
value
=
result
.
data
}
catch
(
_e
:
unknown
)
{
/* fall through */
}
}
}
}
const
refreshOrder
=
async
():
Promise
<
PaymentOrder
|
null
>
=>
{
const
refreshOrder
=
async
():
Promise
<
PaymentOrder
|
null
>
=>
{
...
@@ -292,20 +280,6 @@ onMounted(async () => {
...
@@ -292,20 +280,6 @@ onMounted(async () => {
return
await
paymentStore
.
pollOrderStatus
(
orderId
)
return
await
paymentStore
.
pollOrderStatus
(
orderId
)
}
}
if
(
canUseLegacyPublicVerify
&&
outTradeNo
)
{
try
{
const
result
=
await
paymentAPI
.
verifyOrderPublic
(
outTradeNo
)
return
result
.
data
}
catch
(
_err
:
unknown
)
{
try
{
const
result
=
await
paymentAPI
.
verifyOrder
(
outTradeNo
)
return
result
.
data
}
catch
(
_e
:
unknown
)
{
return
null
}
}
}
return
null
return
null
}
}
...
...
frontend/src/views/user/__tests__/PaymentResultView.spec.ts
View file @
09351e94
...
@@ -225,16 +225,13 @@ describe('PaymentResultView', () => {
...
@@ -225,16 +225,13 @@ describe('PaymentResultView', () => {
expect
(
verifyOrder
).
not
.
toHaveBeenCalled
()
expect
(
verifyOrder
).
not
.
toHaveBeenCalled
()
})
})
it
(
'
keeps legacy
out_trade_no verification
as a fallback when no order
context is available
'
,
async
()
=>
{
it
(
'
does not use anonymous
out_trade_no verification
when no signed resume
context is available
'
,
async
()
=>
{
routeState
.
query
=
{
routeState
.
query
=
{
out_trade_no
:
'
legacy-123
'
,
out_trade_no
:
'
legacy-123
'
,
trade_status
:
'
TRADE_SUCCESS
'
,
trade_status
:
'
TRADE_SUCCESS
'
,
}
}
verifyOrderPublic
.
mockResolvedValue
({
data
:
orderFactory
(
'
PAID
'
),
})
const
wrapper
=
mount
(
PaymentResultView
,
{
mount
(
PaymentResultView
,
{
global
:
{
global
:
{
stubs
:
{
stubs
:
{
OrderStatusBadge
:
true
,
OrderStatusBadge
:
true
,
...
@@ -244,8 +241,8 @@ describe('PaymentResultView', () => {
...
@@ -244,8 +241,8 @@ describe('PaymentResultView', () => {
await
flushPromises
()
await
flushPromises
()
expect
(
verifyOrderPublic
).
toHaveBeenCalled
With
(
'
legacy-123
'
)
expect
(
verifyOrderPublic
).
not
.
toHaveBeenCalled
(
)
expect
(
wrapper
.
text
()).
toContain
(
'
payment.result.success
'
)
expect
(
verifyOrder
).
not
.
toHaveBeenCalled
(
)
})
})
it
(
'
does not use public out_trade_no verification for bare order numbers without legacy return markers
'
,
async
()
=>
{
it
(
'
does not use public out_trade_no verification for bare order numbers without legacy return markers
'
,
async
()
=>
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment