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
07f23aaa
Commit
07f23aaa
authored
Apr 21, 2026
by
IanShaw027
Browse files
fix wxpay config contract and h5 scene info
parent
2626e8f2
Changes
6
Hide whitespace changes
Inline
Side-by-side
backend/internal/payment/provider/wxpay.go
View file @
07f23aaa
...
@@ -250,13 +250,12 @@ func (w *Wxpay) prepayNative(ctx context.Context, c *core.Client, req payment.Cr
...
@@ -250,13 +250,12 @@ func (w *Wxpay) prepayNative(ctx context.Context, c *core.Client, req payment.Cr
func
(
w
*
Wxpay
)
prepayH5
(
ctx
context
.
Context
,
c
*
core
.
Client
,
req
payment
.
CreatePaymentRequest
,
notifyURL
string
,
totalFen
int64
)
(
*
payment
.
CreatePaymentResponse
,
error
)
{
func
(
w
*
Wxpay
)
prepayH5
(
ctx
context
.
Context
,
c
*
core
.
Client
,
req
payment
.
CreatePaymentRequest
,
notifyURL
string
,
totalFen
int64
)
(
*
payment
.
CreatePaymentResponse
,
error
)
{
svc
:=
h5
.
H5ApiService
{
Client
:
c
}
svc
:=
h5
.
H5ApiService
{
Client
:
c
}
cur
:=
wxpayCurrency
cur
:=
wxpayCurrency
tp
:=
wxpayH5Type
resp
,
_
,
err
:=
wxpayH5Prepay
(
ctx
,
svc
,
h5
.
PrepayRequest
{
resp
,
_
,
err
:=
wxpayH5Prepay
(
ctx
,
svc
,
h5
.
PrepayRequest
{
Appid
:
core
.
String
(
w
.
config
[
"appId"
]),
Mchid
:
core
.
String
(
w
.
config
[
"mchId"
]),
Appid
:
core
.
String
(
w
.
config
[
"appId"
]),
Mchid
:
core
.
String
(
w
.
config
[
"mchId"
]),
Description
:
core
.
String
(
req
.
Subject
),
OutTradeNo
:
core
.
String
(
req
.
OrderID
),
Description
:
core
.
String
(
req
.
Subject
),
OutTradeNo
:
core
.
String
(
req
.
OrderID
),
NotifyUrl
:
core
.
String
(
notifyURL
),
NotifyUrl
:
core
.
String
(
notifyURL
),
Amount
:
&
h5
.
Amount
{
Total
:
core
.
Int64
(
totalFen
),
Currency
:
&
cur
},
Amount
:
&
h5
.
Amount
{
Total
:
core
.
Int64
(
totalFen
),
Currency
:
&
cur
},
SceneInfo
:
&
h5
.
SceneInfo
{
PayerClientIp
:
core
.
String
(
req
.
ClientIP
),
H5Info
:
&
h5
.
H5Info
{
Type
:
&
tp
}
},
SceneInfo
:
&
h5
.
SceneInfo
{
PayerClientIp
:
core
.
String
(
req
.
ClientIP
),
H5Info
:
buildWxpayH5Info
(
w
.
config
)
},
})
})
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"wxpay h5 prepay: %w"
,
err
)
return
nil
,
fmt
.
Errorf
(
"wxpay h5 prepay: %w"
,
err
)
...
@@ -272,6 +271,18 @@ func (w *Wxpay) prepayH5(ctx context.Context, c *core.Client, req payment.Create
...
@@ -272,6 +271,18 @@ func (w *Wxpay) prepayH5(ctx context.Context, c *core.Client, req payment.Create
return
&
payment
.
CreatePaymentResponse
{
TradeNo
:
req
.
OrderID
,
PayURL
:
h5URL
},
nil
return
&
payment
.
CreatePaymentResponse
{
TradeNo
:
req
.
OrderID
,
PayURL
:
h5URL
},
nil
}
}
func
buildWxpayH5Info
(
config
map
[
string
]
string
)
*
h5
.
H5Info
{
tp
:=
wxpayH5Type
info
:=
&
h5
.
H5Info
{
Type
:
&
tp
}
if
appName
:=
strings
.
TrimSpace
(
config
[
"h5AppName"
]);
appName
!=
""
{
info
.
AppName
=
core
.
String
(
appName
)
}
if
appURL
:=
strings
.
TrimSpace
(
config
[
"h5AppUrl"
]);
appURL
!=
""
{
info
.
AppUrl
=
core
.
String
(
appURL
)
}
return
info
}
func
resolveWxpayCreateMode
(
req
payment
.
CreatePaymentRequest
)
(
string
,
error
)
{
func
resolveWxpayCreateMode
(
req
payment
.
CreatePaymentRequest
)
(
string
,
error
)
{
if
strings
.
TrimSpace
(
req
.
OpenID
)
!=
""
{
if
strings
.
TrimSpace
(
req
.
OpenID
)
!=
""
{
return
wxpayModeJSAPI
,
nil
return
wxpayModeJSAPI
,
nil
...
...
backend/internal/payment/provider/wxpay_test.go
View file @
07f23aaa
...
@@ -487,3 +487,86 @@ func TestCreatePaymentWithOpenIDReturnsJSAPIResult(t *testing.T) {
...
@@ -487,3 +487,86 @@ func TestCreatePaymentWithOpenIDReturnsJSAPIResult(t *testing.T) {
t
.
Fatalf
(
"jsapi paySign = %q, want %q"
,
resp
.
JSAPI
.
PaySign
,
"signed-payload"
)
t
.
Fatalf
(
"jsapi paySign = %q, want %q"
,
resp
.
JSAPI
.
PaySign
,
"signed-payload"
)
}
}
}
}
func
TestCreatePaymentMobileH5IncludesConfiguredSceneInfo
(
t
*
testing
.
T
)
{
origJSAPIPrepay
:=
wxpayJSAPIPrepayWithRequestPayment
origNativePrepay
:=
wxpayNativePrepay
origH5Prepay
:=
wxpayH5Prepay
t
.
Cleanup
(
func
()
{
wxpayJSAPIPrepayWithRequestPayment
=
origJSAPIPrepay
wxpayNativePrepay
=
origNativePrepay
wxpayH5Prepay
=
origH5Prepay
})
jsapiCalls
:=
0
nativeCalls
:=
0
h5Calls
:=
0
wxpayJSAPIPrepayWithRequestPayment
=
func
(
ctx
context
.
Context
,
svc
jsapi
.
JsapiApiService
,
req
jsapi
.
PrepayRequest
)
(
*
jsapi
.
PrepayWithRequestPaymentResponse
,
*
core
.
APIResult
,
error
)
{
jsapiCalls
++
return
&
jsapi
.
PrepayWithRequestPaymentResponse
{},
nil
,
nil
}
wxpayNativePrepay
=
func
(
ctx
context
.
Context
,
svc
native
.
NativeApiService
,
req
native
.
PrepayRequest
)
(
*
native
.
PrepayResponse
,
*
core
.
APIResult
,
error
)
{
nativeCalls
++
return
&
native
.
PrepayResponse
{},
nil
,
nil
}
wxpayH5Prepay
=
func
(
ctx
context
.
Context
,
svc
h5
.
H5ApiService
,
req
h5
.
PrepayRequest
)
(
*
h5
.
PrepayResponse
,
*
core
.
APIResult
,
error
)
{
h5Calls
++
if
req
.
SceneInfo
==
nil
{
t
.
Fatal
(
"expected scene_info, got nil"
)
}
if
got
:=
wxSV
(
req
.
SceneInfo
.
PayerClientIp
);
got
!=
"203.0.113.10"
{
t
.
Fatalf
(
"scene_info payer_client_ip = %q, want %q"
,
got
,
"203.0.113.10"
)
}
if
req
.
SceneInfo
.
H5Info
==
nil
{
t
.
Fatal
(
"expected scene_info.h5_info, got nil"
)
}
if
got
:=
wxSV
(
req
.
SceneInfo
.
H5Info
.
Type
);
got
!=
wxpayH5Type
{
t
.
Fatalf
(
"scene_info.h5_info.type = %q, want %q"
,
got
,
wxpayH5Type
)
}
if
got
:=
wxSV
(
req
.
SceneInfo
.
H5Info
.
AppName
);
got
!=
"Sub2API"
{
t
.
Fatalf
(
"scene_info.h5_info.app_name = %q, want %q"
,
got
,
"Sub2API"
)
}
if
got
:=
wxSV
(
req
.
SceneInfo
.
H5Info
.
AppUrl
);
got
!=
"https://app.example.com"
{
t
.
Fatalf
(
"scene_info.h5_info.app_url = %q, want %q"
,
got
,
"https://app.example.com"
)
}
return
&
h5
.
PrepayResponse
{
H5Url
:
core
.
String
(
"https://wx.tenpay.example/h5pay?prepay_id=1"
),
},
nil
,
nil
}
provider
:=
&
Wxpay
{
config
:
map
[
string
]
string
{
"appId"
:
"wx123"
,
"mchId"
:
"mch123"
,
"h5AppName"
:
"Sub2API"
,
"h5AppUrl"
:
"https://app.example.com"
,
},
coreClient
:
&
core
.
Client
{},
}
resp
,
err
:=
provider
.
CreatePayment
(
context
.
Background
(),
payment
.
CreatePaymentRequest
{
OrderID
:
"sub2_99"
,
Amount
:
"66.88"
,
PaymentType
:
payment
.
TypeWxpay
,
Subject
:
"Balance Recharge"
,
NotifyURL
:
"https://merchant.example/payment/notify"
,
ReturnURL
:
"https://merchant.example/payment/result?resume_token=resume-99"
,
ClientIP
:
"203.0.113.10"
,
IsMobile
:
true
,
})
if
err
!=
nil
{
t
.
Fatalf
(
"unexpected error: %v"
,
err
)
}
if
jsapiCalls
!=
0
{
t
.
Fatalf
(
"jsapi prepay calls = %d, want 0"
,
jsapiCalls
)
}
if
nativeCalls
!=
0
{
t
.
Fatalf
(
"native prepay calls = %d, want 0"
,
nativeCalls
)
}
if
h5Calls
!=
1
{
t
.
Fatalf
(
"h5 prepay calls = %d, want 1"
,
h5Calls
)
}
if
!
strings
.
Contains
(
resp
.
PayURL
,
"redirect_url="
)
{
t
.
Fatalf
(
"pay_url = %q, want redirect_url query appended"
,
resp
.
PayURL
)
}
}
frontend/src/components/payment/__tests__/providerConfig.spec.ts
0 → 100644
View file @
07f23aaa
import
{
describe
,
expect
,
it
}
from
'
vitest
'
import
{
PROVIDER_CONFIG_FIELDS
}
from
'
@/components/payment/providerConfig
'
function
findField
(
key
:
string
)
{
const
fields
=
PROVIDER_CONFIG_FIELDS
.
wxpay
||
[]
return
fields
.
find
(
field
=>
field
.
key
===
key
)
}
describe
(
'
PROVIDER_CONFIG_FIELDS.wxpay
'
,
()
=>
{
it
(
'
keeps admin form validation aligned with backend-required credentials
'
,
()
=>
{
expect
(
findField
(
'
publicKeyId
'
)?.
optional
).
toBeFalsy
()
expect
(
findField
(
'
certSerial
'
)?.
optional
).
toBeFalsy
()
})
it
(
'
exposes optional mp and H5 metadata fields for WeChat-specific flows
'
,
()
=>
{
expect
(
findField
(
'
mpAppId
'
)?.
optional
).
toBe
(
true
)
expect
(
findField
(
'
h5AppName
'
)?.
optional
).
toBe
(
true
)
expect
(
findField
(
'
h5AppUrl
'
)?.
optional
).
toBe
(
true
)
})
})
frontend/src/components/payment/providerConfig.ts
View file @
07f23aaa
...
@@ -83,12 +83,15 @@ export const PROVIDER_CONFIG_FIELDS: Record<string, ConfigFieldDef[]> = {
...
@@ -83,12 +83,15 @@ export const PROVIDER_CONFIG_FIELDS: Record<string, ConfigFieldDef[]> = {
],
],
wxpay
:
[
wxpay
:
[
{
key
:
'
appId
'
,
label
:
'
App ID
'
,
sensitive
:
false
},
{
key
:
'
appId
'
,
label
:
'
App ID
'
,
sensitive
:
false
},
{
key
:
'
mpAppId
'
,
label
:
''
,
sensitive
:
false
,
optional
:
true
},
{
key
:
'
mchId
'
,
label
:
''
,
sensitive
:
false
},
{
key
:
'
mchId
'
,
label
:
''
,
sensitive
:
false
},
{
key
:
'
privateKey
'
,
label
:
''
,
sensitive
:
true
},
{
key
:
'
privateKey
'
,
label
:
''
,
sensitive
:
true
},
{
key
:
'
apiV3Key
'
,
label
:
''
,
sensitive
:
true
},
{
key
:
'
apiV3Key
'
,
label
:
''
,
sensitive
:
true
},
{
key
:
'
publicKey
'
,
label
:
''
,
sensitive
:
true
},
{
key
:
'
publicKey
'
,
label
:
''
,
sensitive
:
true
},
{
key
:
'
publicKeyId
'
,
label
:
''
,
sensitive
:
false
,
optional
:
true
},
{
key
:
'
publicKeyId
'
,
label
:
''
,
sensitive
:
false
},
{
key
:
'
certSerial
'
,
label
:
''
,
sensitive
:
false
,
optional
:
true
},
{
key
:
'
certSerial
'
,
label
:
''
,
sensitive
:
false
},
{
key
:
'
h5AppName
'
,
label
:
''
,
sensitive
:
false
,
optional
:
true
},
{
key
:
'
h5AppUrl
'
,
label
:
''
,
sensitive
:
false
,
optional
:
true
},
],
],
stripe
:
[
stripe
:
[
{
key
:
'
secretKey
'
,
label
:
''
,
sensitive
:
true
},
{
key
:
'
secretKey
'
,
label
:
''
,
sensitive
:
true
},
...
...
frontend/src/i18n/locales/en.ts
View file @
07f23aaa
...
@@ -4655,10 +4655,13 @@ export default {
...
@@ -4655,10 +4655,13 @@ export default {
callbackBaseUrl
:
'
Callback Base URL
'
,
callbackBaseUrl
:
'
Callback Base URL
'
,
field_privateKey
:
'
Private Key
'
,
field_privateKey
:
'
Private Key
'
,
field_publicKey
:
'
Public Key
'
,
field_publicKey
:
'
Public Key
'
,
field_mpAppId
:
'
MP App ID
'
,
field_mchId
:
'
Merchant ID
'
,
field_mchId
:
'
Merchant ID
'
,
field_apiV3Key
:
'
API v3 Key
'
,
field_apiV3Key
:
'
API v3 Key
'
,
field_publicKeyId
:
'
Public Key ID
'
,
field_publicKeyId
:
'
Public Key ID
'
,
field_certSerial
:
'
Certificate Serial
'
,
field_certSerial
:
'
Certificate Serial
'
,
field_h5AppName
:
'
H5 App Name
'
,
field_h5AppUrl
:
'
H5 App URL
'
,
field_secretKey
:
'
Secret Key
'
,
field_secretKey
:
'
Secret Key
'
,
field_publishableKey
:
'
Publishable Key
'
,
field_publishableKey
:
'
Publishable Key
'
,
field_webhookSecret
:
'
Webhook Secret
'
,
field_webhookSecret
:
'
Webhook Secret
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
07f23aaa
...
@@ -4819,10 +4819,13 @@ export default {
...
@@ -4819,10 +4819,13 @@ export default {
callbackBaseUrl
:
'
回调基础地址
'
,
callbackBaseUrl
:
'
回调基础地址
'
,
field_privateKey
:
'
私钥
'
,
field_privateKey
:
'
私钥
'
,
field_publicKey
:
'
公钥
'
,
field_publicKey
:
'
公钥
'
,
field_mpAppId
:
'
公众号 App ID
'
,
field_mchId
:
'
商户号
'
,
field_mchId
:
'
商户号
'
,
field_apiV3Key
:
'
API v3 密钥
'
,
field_apiV3Key
:
'
API v3 密钥
'
,
field_publicKeyId
:
'
公钥 ID
'
,
field_publicKeyId
:
'
公钥 ID
'
,
field_certSerial
:
'
证书序列号
'
,
field_certSerial
:
'
证书序列号
'
,
field_h5AppName
:
'
H5 应用名称
'
,
field_h5AppUrl
:
'
H5 应用地址
'
,
field_secretKey
:
'
密钥
'
,
field_secretKey
:
'
密钥
'
,
field_publishableKey
:
'
公开密钥
'
,
field_publishableKey
:
'
公开密钥
'
,
field_webhookSecret
:
'
Webhook 密钥
'
,
field_webhookSecret
:
'
Webhook 密钥
'
,
...
...
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