fix(payment): restore upgrade-safe payment flows

This commit is contained in:
IanShaw027
2026-04-22 14:57:16 +08:00
parent 36aed35957
commit 1aab084ecb
14 changed files with 645 additions and 68 deletions

View File

@@ -116,6 +116,17 @@ var providerSensitiveConfigFields = map[string]map[string]struct{}{
payment.TypeStripe: {"secretkey": {}, "webhooksecret": {}},
}
// providerPendingOrderProtectedConfigFields lists config keys that cannot be
// changed while the instance has in-progress orders. This includes secrets plus
// all provider identity fields that are snapshotted into orders or used by
// webhook/refund verification.
var providerPendingOrderProtectedConfigFields = map[string]map[string]struct{}{
payment.TypeEasyPay: {"pkey": {}, "pid": {}},
payment.TypeAlipay: {"privatekey": {}, "publickey": {}, "alipaypublickey": {}, "appid": {}},
payment.TypeWxpay: {"privatekey": {}, "apiv3key": {}, "publickey": {}, "appid": {}, "mpappid": {}, "mchid": {}, "publickeyid": {}, "certserial": {}},
payment.TypeStripe: {"secretkey": {}, "webhooksecret": {}},
}
func isSensitiveProviderConfigField(providerKey, fieldName string) bool {
fields, ok := providerSensitiveConfigFields[providerKey]
if !ok {
@@ -125,6 +136,28 @@ func isSensitiveProviderConfigField(providerKey, fieldName string) bool {
return found
}
func hasPendingOrderProtectedConfigChange(providerKey string, currentConfig, nextConfig map[string]string) bool {
fields, ok := providerPendingOrderProtectedConfigFields[providerKey]
if !ok {
return false
}
for fieldName := range fields {
if providerConfigFieldValue(currentConfig, fieldName) != providerConfigFieldValue(nextConfig, fieldName) {
return true
}
}
return false
}
func providerConfigFieldValue(config map[string]string, fieldName string) string {
for key, value := range config {
if strings.EqualFold(key, fieldName) {
return value
}
}
return ""
}
func (s *PaymentConfigService) countPendingOrders(ctx context.Context, providerInstanceID int64) (int, error) {
return s.entClient.PaymentOrder.Query().
Where(
@@ -190,6 +223,18 @@ func (s *PaymentConfigService) UpdateProviderInstance(ctx context.Context, id in
if err != nil {
return nil, fmt.Errorf("load provider instance: %w", err)
}
var pendingOrderCount *int
getPendingOrderCount := func() (int, error) {
if pendingOrderCount != nil {
return *pendingOrderCount, nil
}
count, err := s.countPendingOrders(ctx, id)
if err != nil {
return 0, fmt.Errorf("check pending orders: %w", err)
}
pendingOrderCount = &count
return count, nil
}
nextEnabled := current.Enabled
if req.Enabled != nil {
nextEnabled = *req.Enabled
@@ -201,18 +246,20 @@ func (s *PaymentConfigService) UpdateProviderInstance(ctx context.Context, id in
if err := s.validateVisibleMethodEnablementConflicts(ctx, id, current.ProviderKey, nextSupportedTypes, nextEnabled); err != nil {
return nil, err
}
var mergedConfig map[string]string
if req.Config != nil {
hasSensitive := false
for k, v := range req.Config {
if v != "" && isSensitiveProviderConfigField(current.ProviderKey, k) {
hasSensitive = true
break
}
currentConfig, err := s.decryptConfig(current.Config)
if err != nil {
return nil, fmt.Errorf("decrypt existing config: %w", err)
}
if hasSensitive {
count, err := s.countPendingOrders(ctx, id)
mergedConfig, err = s.mergeConfig(ctx, id, req.Config)
if err != nil {
return nil, err
}
if hasPendingOrderProtectedConfigChange(current.ProviderKey, currentConfig, mergedConfig) {
count, err := getPendingOrderCount()
if err != nil {
return nil, fmt.Errorf("check pending orders: %w", err)
return nil, err
}
if count > 0 {
return nil, infraerrors.Conflict("PENDING_ORDERS", "instance has pending orders").
@@ -221,9 +268,9 @@ func (s *PaymentConfigService) UpdateProviderInstance(ctx context.Context, id in
}
}
if req.Enabled != nil && !*req.Enabled {
count, err := s.countPendingOrders(ctx, id)
count, err := getPendingOrderCount()
if err != nil {
return nil, fmt.Errorf("check pending orders: %w", err)
return nil, err
}
if count > 0 {
return nil, infraerrors.Conflict("PENDING_ORDERS", "instance has pending orders").
@@ -237,13 +284,6 @@ func (s *PaymentConfigService) UpdateProviderInstance(ctx context.Context, id in
if req.Enabled != nil {
finalEnabled = *req.Enabled
}
var mergedConfig map[string]string
if req.Config != nil {
mergedConfig, err = s.mergeConfig(ctx, id, req.Config)
if err != nil {
return nil, err
}
}
if finalEnabled {
configToValidate := mergedConfig
if configToValidate == nil {
@@ -269,9 +309,9 @@ func (s *PaymentConfigService) UpdateProviderInstance(ctx context.Context, id in
}
if req.SupportedTypes != nil {
// Check pending orders before removing payment types
count, err := s.countPendingOrders(ctx, id)
count, err := getPendingOrderCount()
if err != nil {
return nil, fmt.Errorf("check pending orders: %w", err)
return nil, err
}
if count > 0 {
// Load current instance to compare types