fix: narrow Remnashop settings import
This commit is contained in:
@@ -61,6 +61,7 @@ except Exception: # pragma: no cover - defensive fallback for minimal tooling.
|
|||||||
|
|
||||||
SOURCE = "remnashop"
|
SOURCE = "remnashop"
|
||||||
REMNASHOP_ENCRYPTED_PREFIX = "enc_"
|
REMNASHOP_ENCRYPTED_PREFIX = "enc_"
|
||||||
|
PLACEHOLDER_SETTING_VALUES = {"change_me", "changeme"}
|
||||||
GIB = 1024**3
|
GIB = 1024**3
|
||||||
UUID_RE = re.compile(
|
UUID_RE = re.compile(
|
||||||
r"\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-"
|
r"\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-"
|
||||||
@@ -230,7 +231,13 @@ def read_remnashop_env_file(path: Optional[str]) -> dict[str, str]:
|
|||||||
return parse_remnashop_env_text(Path(path).read_text(encoding="utf-8"))
|
return parse_remnashop_env_text(Path(path).read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def _is_placeholder_setting_value(value: Any) -> bool:
|
||||||
|
return isinstance(value, str) and value.strip().lower() in PLACEHOLDER_SETTING_VALUES
|
||||||
|
|
||||||
|
|
||||||
def _clean_url(value: Any) -> Optional[str]:
|
def _clean_url(value: Any) -> Optional[str]:
|
||||||
|
if _is_placeholder_setting_value(value):
|
||||||
|
return None
|
||||||
text_value = str(value or "").strip().rstrip("/")
|
text_value = str(value or "").strip().rstrip("/")
|
||||||
return text_value or None
|
return text_value or None
|
||||||
|
|
||||||
@@ -259,26 +266,23 @@ def _source_public_base_from_env(env: dict[str, str]) -> Optional[str]:
|
|||||||
|
|
||||||
|
|
||||||
def _support_link_from_username(value: Any) -> Optional[str]:
|
def _support_link_from_username(value: Any) -> Optional[str]:
|
||||||
|
if _is_placeholder_setting_value(value):
|
||||||
|
return None
|
||||||
username = str(value or "").strip().lstrip("@")
|
username = str(value or "").strip().lstrip("@")
|
||||||
if not username:
|
if not username:
|
||||||
return None
|
return None
|
||||||
return f"https://t.me/{username}"
|
return f"https://t.me/{username}"
|
||||||
|
|
||||||
|
|
||||||
def _mini_app_url_from_env(value: Any) -> Optional[str]:
|
|
||||||
raw = str(value or "").strip()
|
|
||||||
if not raw:
|
|
||||||
return None
|
|
||||||
if raw.lower() in {"true", "false", "0", "1"}:
|
|
||||||
return None
|
|
||||||
return raw if raw.startswith("https://") else None
|
|
||||||
|
|
||||||
|
|
||||||
def _add_override(overrides: dict[str, Any], key: str, value: Any) -> None:
|
def _add_override(overrides: dict[str, Any], key: str, value: Any) -> None:
|
||||||
if value is None:
|
if value is None:
|
||||||
return
|
return
|
||||||
if isinstance(value, str) and not value.strip():
|
if isinstance(value, str):
|
||||||
return
|
value = value.strip()
|
||||||
|
if not value:
|
||||||
|
return
|
||||||
|
if value.lower() in PLACEHOLDER_SETTING_VALUES:
|
||||||
|
return
|
||||||
overrides[key] = value
|
overrides[key] = value
|
||||||
|
|
||||||
|
|
||||||
@@ -293,11 +297,6 @@ def remnashop_env_overrides(env: dict[str, str]) -> dict[str, Any]:
|
|||||||
_support_link_from_username(env.get("BOT_SUPPORT_USERNAME")),
|
_support_link_from_username(env.get("BOT_SUPPORT_USERNAME")),
|
||||||
)
|
)
|
||||||
_add_override(overrides, "DEFAULT_LANGUAGE", env.get("APP_DEFAULT_LOCALE"))
|
_add_override(overrides, "DEFAULT_LANGUAGE", env.get("APP_DEFAULT_LOCALE"))
|
||||||
_add_override(
|
|
||||||
overrides,
|
|
||||||
"SUBSCRIPTION_MINI_APP_URL",
|
|
||||||
_mini_app_url_from_env(env.get("BOT_MINI_APP")),
|
|
||||||
)
|
|
||||||
return overrides
|
return overrides
|
||||||
|
|
||||||
|
|
||||||
@@ -458,17 +457,24 @@ def remnashop_payment_gateway_overrides(
|
|||||||
if gateway_type == "CRYPTOPAY":
|
if gateway_type == "CRYPTOPAY":
|
||||||
_add_override(overrides, "CRYPTOPAY_ENABLED", active)
|
_add_override(overrides, "CRYPTOPAY_ENABLED", active)
|
||||||
_add_override(overrides, "CRYPTOPAY_TOKEN", settings.get("api_key"))
|
_add_override(overrides, "CRYPTOPAY_TOKEN", settings.get("api_key"))
|
||||||
if currency:
|
if currency and currency != "RUB":
|
||||||
_add_override(overrides, "CRYPTOPAY_ASSET", currency)
|
warnings.append(
|
||||||
|
f"CryptoPay source currency was {currency}; Minishop keeps payment currency "
|
||||||
|
"controlled by tariffs/default currency. Configure CRYPTOPAY_ASSET manually "
|
||||||
|
"if this instance needs a different default."
|
||||||
|
)
|
||||||
return _provider_mapping_result(gateway_type, ["cryptopay"], overrides, warnings)
|
return _provider_mapping_result(gateway_type, ["cryptopay"], overrides, warnings)
|
||||||
|
|
||||||
if gateway_type == "HELEKET":
|
if gateway_type == "HELEKET":
|
||||||
_add_override(overrides, "HELEKET_ENABLED", active)
|
_add_override(overrides, "HELEKET_ENABLED", active)
|
||||||
_add_override(overrides, "HELEKET_MERCHANT_ID", settings.get("merchant_id"))
|
_add_override(overrides, "HELEKET_MERCHANT_ID", settings.get("merchant_id"))
|
||||||
_add_override(overrides, "HELEKET_API_KEY", settings.get("api_key"))
|
_add_override(overrides, "HELEKET_API_KEY", settings.get("api_key"))
|
||||||
if currency:
|
if currency and currency != "RUB":
|
||||||
_add_override(overrides, "HELEKET_CURRENCY", currency)
|
warnings.append(
|
||||||
_add_override(overrides, "HELEKET_SUPPORTED_CURRENCIES", currency)
|
f"Heleket source currency was {currency}; Minishop keeps payment currency "
|
||||||
|
"controlled by tariffs/default currency. Configure HELEKET_CURRENCY manually "
|
||||||
|
"if this instance needs a different default."
|
||||||
|
)
|
||||||
return _provider_mapping_result(gateway_type, ["heleket"], overrides, warnings)
|
return _provider_mapping_result(gateway_type, ["heleket"], overrides, warnings)
|
||||||
|
|
||||||
if gateway_type == "FREEKASSA":
|
if gateway_type == "FREEKASSA":
|
||||||
@@ -492,8 +498,12 @@ def remnashop_payment_gateway_overrides(
|
|||||||
_add_override(overrides, "PLATEGA_SECRET", settings.get("api_key"))
|
_add_override(overrides, "PLATEGA_SECRET", settings.get("api_key"))
|
||||||
_add_override(overrides, "PLATEGA_PAYMENT_METHOD", settings.get("payment_method"))
|
_add_override(overrides, "PLATEGA_PAYMENT_METHOD", settings.get("payment_method"))
|
||||||
_add_override(overrides, "PLATEGA_SBP_METHOD", settings.get("payment_method"))
|
_add_override(overrides, "PLATEGA_SBP_METHOD", settings.get("payment_method"))
|
||||||
if currency:
|
if currency and currency != "RUB":
|
||||||
_add_override(overrides, "PLATEGA_SUPPORTED_CURRENCIES", currency)
|
warnings.append(
|
||||||
|
f"Platega source currency was {currency}; Minishop keeps payment currency "
|
||||||
|
"controlled by tariffs/default currency. Configure PLATEGA_SUPPORTED_CURRENCIES "
|
||||||
|
"manually if this instance needs a different currency."
|
||||||
|
)
|
||||||
return _provider_mapping_result(gateway_type, ["platega_sbp"], overrides, warnings)
|
return _provider_mapping_result(gateway_type, ["platega_sbp"], overrides, warnings)
|
||||||
|
|
||||||
return _provider_mapping_result(gateway_type, [], overrides, warnings)
|
return _provider_mapping_result(gateway_type, [], overrides, warnings)
|
||||||
@@ -972,12 +982,15 @@ class RemnashopImporter:
|
|||||||
"REMNAWAVE_WEBHOOK_SECRET",
|
"REMNAWAVE_WEBHOOK_SECRET",
|
||||||
"BOT_SUPPORT_USERNAME",
|
"BOT_SUPPORT_USERNAME",
|
||||||
"APP_DEFAULT_LOCALE",
|
"APP_DEFAULT_LOCALE",
|
||||||
"BOT_MINI_APP",
|
|
||||||
)
|
)
|
||||||
if self.source_env.get(key)
|
if self.source_env.get(key)
|
||||||
|
and not _is_placeholder_setting_value(self.source_env.get(key))
|
||||||
),
|
),
|
||||||
"has_app_crypt_key": bool(self.source_env.get("APP_CRYPT_KEY")),
|
"has_app_crypt_key": bool(self.source_env.get("APP_CRYPT_KEY")),
|
||||||
"source_urls": remnashop_source_urls_from_env(self.source_env),
|
"source_urls": remnashop_source_urls_from_env(self.source_env),
|
||||||
|
"ignored_keys_present": sorted(
|
||||||
|
key for key in ("BOT_MINI_APP",) if self.source_env.get(key)
|
||||||
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return written
|
return written
|
||||||
@@ -1595,13 +1608,16 @@ class RemnashopImporter:
|
|||||||
"REMNAWAVE_WEBHOOK_SECRET",
|
"REMNAWAVE_WEBHOOK_SECRET",
|
||||||
"BOT_SUPPORT_USERNAME",
|
"BOT_SUPPORT_USERNAME",
|
||||||
"APP_DEFAULT_LOCALE",
|
"APP_DEFAULT_LOCALE",
|
||||||
"BOT_MINI_APP",
|
|
||||||
"APP_DOMAIN",
|
"APP_DOMAIN",
|
||||||
"APP_CRYPT_KEY",
|
"APP_CRYPT_KEY",
|
||||||
)
|
)
|
||||||
if self.source_env.get(key)
|
if self.source_env.get(key)
|
||||||
|
and not _is_placeholder_setting_value(self.source_env.get(key))
|
||||||
),
|
),
|
||||||
"source_urls": remnashop_source_urls_from_env(self.source_env),
|
"source_urls": remnashop_source_urls_from_env(self.source_env),
|
||||||
|
"ignored_keys_present": sorted(
|
||||||
|
key for key in ("BOT_MINI_APP",) if self.source_env.get(key)
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
env_override_keys = await self.import_env_settings()
|
env_override_keys = await self.import_env_settings()
|
||||||
|
|||||||
@@ -33,14 +33,30 @@ message logs как заметки, чтобы администратор мог
|
|||||||
- `REMNAWAVE_TOKEN` -> `PANEL_API_KEY`;
|
- `REMNAWAVE_TOKEN` -> `PANEL_API_KEY`;
|
||||||
- `REMNAWAVE_WEBHOOK_SECRET` -> `PANEL_WEBHOOK_SECRET`;
|
- `REMNAWAVE_WEBHOOK_SECRET` -> `PANEL_WEBHOOK_SECRET`;
|
||||||
- `BOT_SUPPORT_USERNAME` -> `SUPPORT_LINK`;
|
- `BOT_SUPPORT_USERNAME` -> `SUPPORT_LINK`;
|
||||||
- `APP_DEFAULT_LOCALE` -> `DEFAULT_LANGUAGE`;
|
- `APP_DEFAULT_LOCALE` -> `DEFAULT_LANGUAGE`.
|
||||||
- `BOT_MINI_APP` -> `SUBSCRIPTION_MINI_APP_URL`, если там уже HTTPS URL.
|
|
||||||
|
`BOT_MINI_APP` из Remnashop не переносится автоматически. В Remnashop эта
|
||||||
|
переменная управляет кнопкой подключения к subscription page или внешнему Mini
|
||||||
|
App, а не веб-кабинетом Remnashop. В Minishop `SUBSCRIPTION_MINI_APP_URL`
|
||||||
|
должен указывать на текущий frontend/Mini App этого стека; wizard настраивает
|
||||||
|
его из `WEBHOOK_HOST`/`MINIAPP_HOST` или `MINIAPP_PUBLIC_URL`.
|
||||||
|
|
||||||
|
Значения-заглушки вроде `change_me` importer пропускает, чтобы случайно не
|
||||||
|
записать шаблонные секреты в рабочую конфигурацию.
|
||||||
|
|
||||||
Платежные провайдеры берутся из таблицы Remnashop `payment_gateways`.
|
Платежные провайдеры берутся из таблицы Remnashop `payment_gateways`.
|
||||||
Поддерживаются и автоматически маппятся: Telegram Stars, YooKassa, WATA,
|
Поддерживаются и автоматически маппятся: Telegram Stars, YooKassa, WATA,
|
||||||
CryptoPay, Heleket, FreeKassa и Platega. Для них importer переносит флаги
|
CryptoPay, Heleket, FreeKassa и Platega. Для них importer переносит флаги
|
||||||
включения, API-ключи/merchant IDs и доступные provider-specific параметры в
|
включения, API-ключи/merchant IDs и прямые технические параметры, без которых
|
||||||
раздел настроек админки.
|
провайдер не сможет работать: YooKassa receipt email/VAT, FreeKassa second
|
||||||
|
secret/payment method/server IP и Platega payment method.
|
||||||
|
|
||||||
|
Provider currency и supported-currency ограничения не переносятся автоматически:
|
||||||
|
в Minishop валюта платежа управляется тарифами и `DEFAULT_CURRENCY_SYMBOL`.
|
||||||
|
Если старый gateway Remnashop был настроен на нестандартную валюту, importer
|
||||||
|
оставит предупреждение в JSON-сводке; проверьте `CRYPTOPAY_ASSET`,
|
||||||
|
`HELEKET_CURRENCY`, `HELEKET_SUPPORTED_CURRENCIES` или
|
||||||
|
`PLATEGA_SUPPORTED_CURRENCIES` вручную.
|
||||||
|
|
||||||
Провайдеры YooMoney, Cryptomus, MulenPay, PayMaster, RoboKassa и UrlPay сейчас
|
Провайдеры YooMoney, Cryptomus, MulenPay, PayMaster, RoboKassa и UrlPay сейчас
|
||||||
не имеют прямого аналога в Minishop. Если они были в Remnashop, importer
|
не имеют прямого аналога в Minishop. Если они были в Remnashop, importer
|
||||||
@@ -72,7 +88,7 @@ Remnashop может хранить секреты в формате `enc_...`.
|
|||||||
`raw.githubusercontent.com`, без клонирования репозитория.
|
`raw.githubusercontent.com`, без клонирования репозитория.
|
||||||
2. Вы указываете source PostgreSQL DSN Remnashop и schema, обычно `public`.
|
2. Вы указываете source PostgreSQL DSN Remnashop и schema, обычно `public`.
|
||||||
3. Опционально указываете путь к старому Remnashop `.env` для `APP_CRYPT_KEY`,
|
3. Опционально указываете путь к старому Remnashop `.env` для `APP_CRYPT_KEY`,
|
||||||
Remnawave API settings и переносимых payment/provider settings.
|
Remnawave API settings и переносимых settings.
|
||||||
4. Вы выбираете целевую БД: текущую compose-БД или ручной target DSN.
|
4. Вы выбираете целевую БД: текущую compose-БД или ручной target DSN.
|
||||||
5. При необходимости указываете JSON map тарифов Remnashop в локальные
|
5. При необходимости указываете JSON map тарифов Remnashop в локальные
|
||||||
`tariff_key`, например `{"basic": "standard_month"}`.
|
`tariff_key`, например `{"basic": "standard_month"}`.
|
||||||
|
|||||||
@@ -73,10 +73,24 @@ def test_remnashop_env_parser_and_overrides_map_safe_values():
|
|||||||
"PANEL_WEBHOOK_SECRET": "panel secret",
|
"PANEL_WEBHOOK_SECRET": "panel secret",
|
||||||
"SUPPORT_LINK": "https://t.me/support_bot",
|
"SUPPORT_LINK": "https://t.me/support_bot",
|
||||||
"DEFAULT_LANGUAGE": "en",
|
"DEFAULT_LANGUAGE": "en",
|
||||||
"SUBSCRIPTION_MINI_APP_URL": "https://app.example.com/",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_remnashop_env_overrides_skip_placeholders_and_mini_app():
|
||||||
|
overrides = remnashop_env_overrides(
|
||||||
|
{
|
||||||
|
"REMNAWAVE_HOST": "change_me",
|
||||||
|
"REMNAWAVE_TOKEN": "change_me",
|
||||||
|
"REMNAWAVE_WEBHOOK_SECRET": "change_me",
|
||||||
|
"BOT_SUPPORT_USERNAME": "change_me",
|
||||||
|
"APP_DEFAULT_LOCALE": "change_me",
|
||||||
|
"BOT_MINI_APP": "https://old-mini-app.example.com/",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert overrides == {}
|
||||||
|
|
||||||
|
|
||||||
def test_remnashop_yookassa_gateway_maps_to_current_provider_settings():
|
def test_remnashop_yookassa_gateway_maps_to_current_provider_settings():
|
||||||
result = remnashop_payment_gateway_overrides(
|
result = remnashop_payment_gateway_overrides(
|
||||||
{
|
{
|
||||||
@@ -138,6 +152,38 @@ def test_remnashop_free_kassa_and_platega_gateways_map_available_settings():
|
|||||||
assert platega["provider_ids"] == ["platega_sbp"]
|
assert platega["provider_ids"] == ["platega_sbp"]
|
||||||
assert platega["overrides"]["PLATEGA_SBP_ENABLED"] is True
|
assert platega["overrides"]["PLATEGA_SBP_ENABLED"] is True
|
||||||
assert platega["overrides"]["PLATEGA_SBP_METHOD"] == 2
|
assert platega["overrides"]["PLATEGA_SBP_METHOD"] == 2
|
||||||
|
assert "PLATEGA_SUPPORTED_CURRENCIES" not in platega["overrides"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_remnashop_crypto_provider_currency_is_not_imported_as_setting_override():
|
||||||
|
cryptopay = remnashop_payment_gateway_overrides(
|
||||||
|
{
|
||||||
|
"type": "CRYPTOPAY",
|
||||||
|
"currency": "USD",
|
||||||
|
"is_active": True,
|
||||||
|
"settings": {"api_key": "crypto-token"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert cryptopay["overrides"] == {
|
||||||
|
"CRYPTOPAY_ENABLED": True,
|
||||||
|
"CRYPTOPAY_TOKEN": "crypto-token",
|
||||||
|
}
|
||||||
|
assert any("source currency was USD" in warning for warning in cryptopay["warnings"])
|
||||||
|
|
||||||
|
heleket = remnashop_payment_gateway_overrides(
|
||||||
|
{
|
||||||
|
"type": "HELEKET",
|
||||||
|
"currency": "USD",
|
||||||
|
"is_active": True,
|
||||||
|
"settings": {"merchant_id": "merchant", "api_key": "secret"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert heleket["overrides"] == {
|
||||||
|
"HELEKET_ENABLED": True,
|
||||||
|
"HELEKET_MERCHANT_ID": "merchant",
|
||||||
|
"HELEKET_API_KEY": "secret",
|
||||||
|
}
|
||||||
|
assert "HELEKET_CURRENCY" not in heleket["overrides"]
|
||||||
|
|
||||||
|
|
||||||
def test_remnashop_unsupported_gateway_is_reported_without_overrides():
|
def test_remnashop_unsupported_gateway_is_reported_without_overrides():
|
||||||
|
|||||||
Reference in New Issue
Block a user