refactor(payment): 支付插件 - 宿主仅通过 RPC 包加载并保护订单版本
This commit is contained in:
@@ -257,6 +257,9 @@ func ValidateManifest(manifest Manifest) error {
|
||||
if len(manifest.Contributes.Providers) == 0 && !hasNonProviderContribution(manifest.Contributes) {
|
||||
return fmt.Errorf("plugin must declare at least one contribution")
|
||||
}
|
||||
if backend := strings.TrimSpace(manifest.Runtime.Backend); backend != "" && backend != "declarative" && backend != "rpc" && backend != "wasm" && backend != "trusted-backend" && !strings.HasPrefix(backend, "host:") {
|
||||
return fmt.Errorf("unsupported plugin backend %q", backend)
|
||||
}
|
||||
if len(manifest.Contributes.Providers) == 0 {
|
||||
return validatePaymentProviderContributions(manifest)
|
||||
}
|
||||
@@ -342,6 +345,16 @@ func validatePaymentProviderContributions(manifest Manifest) error {
|
||||
if policy.MinMinutes <= 0 || policy.DefaultMinutes < policy.MinMinutes || policy.MaxMinutes < policy.DefaultMinutes {
|
||||
return fmt.Errorf("payment provider contribution %q has invalid expiry policy", provider.ID)
|
||||
}
|
||||
for _, field := range provider.IdentityFields {
|
||||
if strings.TrimSpace(field) == "" || len(field) > 80 {
|
||||
return fmt.Errorf("payment provider contribution %q has invalid identity field %q", provider.ID, field)
|
||||
}
|
||||
}
|
||||
for _, response := range []ManifestPaymentResponse{provider.NotificationSuccess, provider.NotificationFailure} {
|
||||
if response.Status < 0 || response.Status > 599 {
|
||||
return fmt.Errorf("payment provider contribution %q has invalid notification response status", provider.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -34,6 +34,11 @@ func TestOfficialProtocolPackagesAreSelfContainedDeclarativePlugins(t *testing.T
|
||||
if err != nil {
|
||||
t.Fatalf("parse %s: %v", filepath.Base(path), err)
|
||||
}
|
||||
if len(pkg.Manifest.Contributes.PaymentProviders) > 0 {
|
||||
// Payment packages use the yingce.payment/v1 executable ABI rather than
|
||||
// the declarative AI provider contract covered by this catalog test.
|
||||
continue
|
||||
}
|
||||
if pkg.Manifest.APIVersion != "yingce.plugin/v2" {
|
||||
t.Fatalf("%s apiVersion = %q", filepath.Base(path), pkg.Manifest.APIVersion)
|
||||
}
|
||||
|
||||
@@ -104,13 +104,26 @@ func validatePluginPackagePath(name string) (string, error) {
|
||||
if name == "" || strings.ContainsRune(name, '\\') || strings.HasPrefix(name, "/") || path.IsAbs(name) || path.Clean(name) != name || strings.HasPrefix(name, "../") || name == ".." {
|
||||
return "", fmt.Errorf("invalid plugin package path %q", name)
|
||||
}
|
||||
if name == "manifest.json" || strings.HasPrefix(name, "web/") || strings.HasPrefix(name, "assets/") || strings.HasPrefix(name, "docs/") || name == "README.md" || name == "LICENSE" {
|
||||
if name == "manifest.json" || strings.HasPrefix(name, "web/") || strings.HasPrefix(name, "backend/") || strings.HasPrefix(name, "assets/") || strings.HasPrefix(name, "docs/") || name == "README.md" || name == "LICENSE" {
|
||||
return name, nil
|
||||
}
|
||||
return "", fmt.Errorf("plugin package path %q is outside the allowed package roots", name)
|
||||
}
|
||||
|
||||
func validatePluginPackageRuntime(manifest Manifest, files map[string][]byte) error {
|
||||
backend := strings.TrimSpace(manifest.Runtime.Backend)
|
||||
if backend == "rpc" || backend == "wasm" {
|
||||
entry := strings.TrimSpace(manifest.Runtime.BackendEntry)
|
||||
if !strings.HasPrefix(entry, "backend/") || path.Clean(entry) != entry {
|
||||
return fmt.Errorf("manifest.runtime.backendEntry must point inside backend/")
|
||||
}
|
||||
if _, ok := files[entry]; !ok {
|
||||
return fmt.Errorf("manifest runtime backend entry %q is missing from package", entry)
|
||||
}
|
||||
if backend == "wasm" && !strings.HasSuffix(strings.ToLower(entry), ".wasm") {
|
||||
return fmt.Errorf("wasm backend entry must have .wasm extension")
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(manifest.Entry) == "" {
|
||||
if strings.TrimSpace(manifest.Runtime.Web) != "" {
|
||||
return fmt.Errorf("manifest.runtime.web requires manifest.entry")
|
||||
|
||||
@@ -266,8 +266,9 @@ func (m *Manifest) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
|
||||
type ManifestRuntime struct {
|
||||
Backend string `json:"backend,omitempty"`
|
||||
Web string `json:"web,omitempty"`
|
||||
Backend string `json:"backend,omitempty"`
|
||||
BackendEntry string `json:"backendEntry,omitempty"`
|
||||
Web string `json:"web,omitempty"`
|
||||
}
|
||||
|
||||
type ManifestConfiguration struct {
|
||||
@@ -300,11 +301,14 @@ type ManifestContributions struct {
|
||||
}
|
||||
|
||||
type ManifestPaymentProvider struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Icon string `json:"icon"`
|
||||
CheckoutMode string `json:"checkoutMode"`
|
||||
ExpiryPolicy ManifestPaymentExpiryPolicy `json:"expiryPolicy"`
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Icon string `json:"icon"`
|
||||
CheckoutMode string `json:"checkoutMode"`
|
||||
ExpiryPolicy ManifestPaymentExpiryPolicy `json:"expiryPolicy"`
|
||||
IdentityFields []string `json:"identityFields,omitempty"`
|
||||
NotificationSuccess ManifestPaymentResponse `json:"notificationSuccess,omitempty"`
|
||||
NotificationFailure ManifestPaymentResponse `json:"notificationFailure,omitempty"`
|
||||
}
|
||||
|
||||
type ManifestPaymentExpiryPolicy struct {
|
||||
@@ -313,6 +317,12 @@ type ManifestPaymentExpiryPolicy struct {
|
||||
MaxMinutes int `json:"maxMinutes"`
|
||||
}
|
||||
|
||||
type ManifestPaymentResponse struct {
|
||||
Status int `json:"status,omitempty"`
|
||||
ContentType string `json:"contentType,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
type ManifestProvider struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
|
||||
Reference in New Issue
Block a user