mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-05-05 05:30:44 +08:00
fix wxpay config contract and h5 scene info
This commit is contained in:
@@ -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
|
|||||||
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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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 },
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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 密钥',
|
||||||
|
|||||||
Reference in New Issue
Block a user