feat(payment): 新增微信支付宝积分充值与对账
This commit is contained in:
1 parent
52158da7c2
commit
669b83b2f8
41 files changed
+6401
-136
No files matched your search
@@ -94,7 +94,7 @@ func newPluginRuntime(dataDir string) (*pluginRuntime, error) {
|
||||
return nil, fmt.Errorf("create plugin package directory: %w", err)
|
||||
}
|
||||
center := &pluginRuntime{registryPath: filepath.Join(dataDir, "plugin_registry.json"), packageDir: packageDir, plugins: make(map[string]pluginRecord)}
|
||||
if err := center.bootstrapOfficialPlugins(); err != nil {
|
||||
if err := center.bootstrapBuiltInPlugins(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := center.reload(); err != nil {
|
||||
@@ -103,7 +103,7 @@ func newPluginRuntime(dataDir string) (*pluginRuntime, error) {
|
||||
return center, nil
|
||||
}
|
||||
|
||||
func (c *pluginRuntime) bootstrapOfficialPlugins() error {
|
||||
func (c *pluginRuntime) bootstrapBuiltInPlugins() error {
|
||||
stored, err := c.readRegistry()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -120,7 +120,7 @@ func (c *pluginRuntime) bootstrapOfficialPlugins() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取官方插件目录失败:%w", err)
|
||||
}
|
||||
officialIDs := make(map[string]struct{}, len(entries)+2)
|
||||
builtInIDs := make(map[string]struct{}, len(entries)+2)
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !strings.HasSuffix(strings.ToLower(entry.Name()), ".yingce-plugin") {
|
||||
continue
|
||||
@@ -140,10 +140,10 @@ func (c *pluginRuntime) bootstrapOfficialPlugins() error {
|
||||
return fmt.Errorf("加载官方插件 %q:%w", pkg.Manifest.Metadata.ID, err)
|
||||
}
|
||||
id := pkg.Manifest.Metadata.ID
|
||||
if _, duplicate := officialIDs[id]; duplicate {
|
||||
if _, duplicate := builtInIDs[id]; duplicate {
|
||||
return fmt.Errorf("官方插件 ID %q 重复", id)
|
||||
}
|
||||
officialIDs[id] = struct{}{}
|
||||
builtInIDs[id] = struct{}{}
|
||||
manifest := pkg.Manifest
|
||||
record := byID[id]
|
||||
if len(record.Raw) > 0 {
|
||||
@@ -169,38 +169,43 @@ func (c *pluginRuntime) bootstrapOfficialPlugins() error {
|
||||
record.PackagePath, record.PackageSHA256, record.UpdatedAt = packageName, hash, now
|
||||
byID[id] = record
|
||||
}
|
||||
for _, workflow := range bundledWorkflowPluginManifests() {
|
||||
officialIDs[workflow.Metadata.ID] = struct{}{}
|
||||
data, err := json.Marshal(workflow)
|
||||
bundledManifests := append(bundledWorkflowPluginManifests(), bundledPaymentPluginManifests()...)
|
||||
for _, bundled := range bundledManifests {
|
||||
builtInIDs[bundled.Metadata.ID] = struct{}{}
|
||||
data, err := json.Marshal(bundled)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode official workflow plugin %s: %w", workflow.Metadata.ID, err)
|
||||
return fmt.Errorf("encode built-in plugin %s: %w", bundled.Metadata.ID, err)
|
||||
}
|
||||
record := byID[workflow.Metadata.ID]
|
||||
record := byID[bundled.Metadata.ID]
|
||||
if len(record.Raw) > 0 {
|
||||
var installed protocol.Manifest
|
||||
if err := json.Unmarshal(record.Raw, &installed); err != nil {
|
||||
return fmt.Errorf("decode bundled workflow plugin %s: %w", workflow.Metadata.ID, err)
|
||||
return fmt.Errorf("decode built-in plugin %s: %w", bundled.Metadata.ID, err)
|
||||
}
|
||||
workflow.Metadata.Enabled = installed.Metadata.Enabled
|
||||
data, err = json.Marshal(workflow)
|
||||
bundled.Metadata.Enabled = installed.Metadata.Enabled
|
||||
data, err = json.Marshal(bundled)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode bundled workflow plugin %s: %w", workflow.Metadata.ID, err)
|
||||
return fmt.Errorf("encode built-in plugin %s: %w", bundled.Metadata.ID, err)
|
||||
}
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
if record.InstalledAt.IsZero() {
|
||||
record.InstalledAt = now
|
||||
}
|
||||
source := PluginOriginOfficial
|
||||
if _, systemPayment := systemPaymentPolicies[bundled.Metadata.ID]; systemPayment {
|
||||
source = PluginOriginSystem
|
||||
}
|
||||
record.UpdatedAt = now
|
||||
record.ID, record.Raw, record.Source, record.PackagePath = workflow.Metadata.ID, data, PluginOriginOfficial, ""
|
||||
byID[workflow.Metadata.ID] = record
|
||||
record.ID, record.Raw, record.Source, record.PackagePath = bundled.Metadata.ID, data, source, ""
|
||||
byID[bundled.Metadata.ID] = record
|
||||
}
|
||||
result := make([]pluginRegistryRecord, 0, len(byID))
|
||||
for _, record := range byID {
|
||||
if record.Source == "bundled" || record.Source == PluginOriginOfficial {
|
||||
if _, exists := officialIDs[record.ID]; !exists {
|
||||
// Official records are reconciled from repository packages on every
|
||||
// startup, so removed packages cannot survive as stale providers.
|
||||
if isBuiltInPluginSource(record.Source) {
|
||||
if _, exists := builtInIDs[record.ID]; !exists {
|
||||
// Built-in records are reconciled from repository packages and host
|
||||
// manifests on every startup, so removed plugins cannot survive stale.
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -210,6 +215,15 @@ func (c *pluginRuntime) bootstrapOfficialPlugins() error {
|
||||
return c.writeRegistry(result)
|
||||
}
|
||||
|
||||
func isBuiltInPluginSource(source string) bool {
|
||||
switch strings.TrimSpace(source) {
|
||||
case "bundled", PluginOriginOfficial, PluginOriginSystem:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func officialPluginPackageDir() (string, error) {
|
||||
if configured := strings.TrimSpace(os.Getenv("CANVAS_OFFICIAL_PLUGIN_DIR")); configured != "" {
|
||||
info, err := os.Stat(configured)
|
||||
@@ -275,6 +289,31 @@ func (c *pluginRuntime) reload() error {
|
||||
return err
|
||||
}
|
||||
for id, record := range plugins {
|
||||
var manifest protocol.Manifest
|
||||
if err := json.Unmarshal(record.Raw, &manifest); err != nil {
|
||||
record.Error = err.Error()
|
||||
plugins[id] = record
|
||||
continue
|
||||
}
|
||||
if len(manifest.Contributes.PaymentProviders) > 0 {
|
||||
_, systemPayment := systemPaymentPolicies[id]
|
||||
if record.Source != PluginOriginSystem || !systemPayment {
|
||||
record.Metadata.Enabled = false
|
||||
record.Status = "invalid"
|
||||
record.Error = "首期支付插件只能使用系统宿主适配器"
|
||||
plugins[id] = record
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(manifest.Contributes.PaymentProviders) > 0 && len(manifest.Contributes.Providers) == 0 {
|
||||
if record.Metadata.Enabled {
|
||||
record.Status = "enabled"
|
||||
} else {
|
||||
record.Status = "disabled"
|
||||
}
|
||||
plugins[id] = record
|
||||
continue
|
||||
}
|
||||
adapters, loadErr := protocol.LoadInstalledProviders(record.Raw, nil)
|
||||
if loadErr != nil {
|
||||
record.Metadata.Enabled = false
|
||||
@@ -341,6 +380,9 @@ func (c *pluginRuntime) install(data []byte, fileName string) (PluginView, error
|
||||
return PluginView{}, err
|
||||
}
|
||||
manifest := pkg.Manifest
|
||||
if len(manifest.Contributes.PaymentProviders) > 0 {
|
||||
return PluginView{}, errors.New("首期支付插件只能使用系统宿主适配器")
|
||||
}
|
||||
if strings.HasPrefix(strings.TrimSpace(manifest.Runtime.Backend), "host:") {
|
||||
return PluginView{}, errors.New("上传插件不能使用宿主内置执行器")
|
||||
}
|
||||
@@ -350,8 +392,8 @@ func (c *pluginRuntime) install(data []byte, fileName string) (PluginView, error
|
||||
c.mu.RLock()
|
||||
existing, exists := c.plugins[manifest.Metadata.ID]
|
||||
c.mu.RUnlock()
|
||||
if exists && existing.Source == PluginOriginOfficial {
|
||||
return PluginView{}, fmt.Errorf("官方插件 %q 不能通过上传覆盖", manifest.Metadata.ID)
|
||||
if exists && isBuiltInPluginSource(existing.Source) {
|
||||
return PluginView{}, fmt.Errorf("内置插件 %q 不能通过上传覆盖", manifest.Metadata.ID)
|
||||
}
|
||||
manifest.Metadata.Enabled = !exists || existing.Metadata.Enabled
|
||||
manifestData, err := json.Marshal(manifest)
|
||||
@@ -458,7 +500,7 @@ func pluginManifestView(raw []byte, metadata protocol.Metadata, source string) P
|
||||
return PluginManifestView{
|
||||
ID: metadata.ID, Name: metadata.Name, Version: metadata.Version, APIVersion: "yingce.plugin/v1", Entry: manifest.Entry, Surfaces: manifest.Surfaces,
|
||||
Description: metadata.Description, Documentation: metadata.Documentation, Author: metadata.Vendor,
|
||||
Permissions: manifest.Permissions, Trusted: source == PluginOriginOfficial, Runtime: manifest.Runtime,
|
||||
Permissions: manifest.Permissions, Trusted: isBuiltInPluginSource(source), Runtime: manifest.Runtime,
|
||||
Configuration: manifest.Configuration, Contributes: manifest.Contributes,
|
||||
}
|
||||
}
|
||||
@@ -472,8 +514,8 @@ func (c *pluginRuntime) uninstall(id string) error {
|
||||
if !ok {
|
||||
return fmt.Errorf("插件 %q 不存在", id)
|
||||
}
|
||||
if record.Source == PluginOriginOfficial {
|
||||
return fmt.Errorf("官方插件 %q 不能卸载,可停用该插件", id)
|
||||
if isBuiltInPluginSource(record.Source) {
|
||||
return fmt.Errorf("内置插件 %q 不能卸载,可停用该插件", id)
|
||||
}
|
||||
stored, err := c.readRegistry()
|
||||
if err != nil {
|
||||
|
||||
Reference in new issue
Block a user