fix: update production app configuration
This commit is contained in:
+2
-12
@@ -91,12 +91,6 @@ ERYAO_LLM__PROVIDER_KEYS__DEEPSEEK=
|
||||
ERYAO_POINTS_POLICY__REGISTER_BONUS_POINTS=60
|
||||
ERYAO_POINTS_POLICY__REGISTER_BONUS_HMAC_KEY=replace-with-strong-random-key
|
||||
|
||||
############
|
||||
# 敏感词配置
|
||||
############
|
||||
ERYAO_SENSITIVE_WORD__USE_ALIYUN=true
|
||||
ERYAO_SENSITIVE_WORD__FALLBACK_TO_LOCAL=true
|
||||
|
||||
############
|
||||
# CORS 配置
|
||||
############
|
||||
@@ -112,13 +106,9 @@ ERYAO_TEST__CODE=123456
|
||||
# Apple IAP 配置
|
||||
############
|
||||
ERYAO_APPLE_IAP__BUNDLE_ID=com.meeyao.qianwen
|
||||
# Apple IAP 环境识别。auto 表示以后端验签后的 Apple transaction environment 为准。
|
||||
ERYAO_APPLE_IAP__ENVIRONMENT=auto
|
||||
# Server API 密钥(可选,用于主动查询交易状态)
|
||||
ERYAO_APPLE_IAP__SERVER_API_KEY_ID=
|
||||
ERYAO_APPLE_IAP__SERVER_API_PRIVATE_KEY=
|
||||
ERYAO_APPLE_IAP__SERVER_API_ISSUER_ID=
|
||||
# 沙盒测试账号(仅用于手动测试,不用于后端验证)
|
||||
ERYAO_APPLE_IAP__SANDBOX_TESTER_EMAIL=
|
||||
ERYAO_APPLE_IAP__SANDBOX_TESTER_PASSWORD=
|
||||
# Server Notifications V2 URL(在 App Store Connect 中配置)
|
||||
# 格式: https://<your-domain>/api/v1/payments/apple/notifications
|
||||
ERYAO_APPLE_IAP__SERVER_NOTIFICATIONS_URL=
|
||||
|
||||
+9
-6
@@ -2,9 +2,15 @@
|
||||
|
||||
Flutter client for `觅爻签问`.
|
||||
|
||||
## Debug startup with backend injection
|
||||
## Backend URL
|
||||
|
||||
This app supports injecting backend URL at startup (same pattern as social-app):
|
||||
Default backend URL:
|
||||
|
||||
```text
|
||||
https://api.meeyao.com
|
||||
```
|
||||
|
||||
This app also supports injecting backend URL at startup (same pattern as social-app):
|
||||
|
||||
- Dart read path: `lib/core/config/env.dart`
|
||||
- Injection key: `BACKEND_URL`
|
||||
@@ -21,7 +27,4 @@ flutter run --dart-define=BACKEND_URL=http://192.168.1.100:5775
|
||||
./tool/run-dev.sh --backend-url http://192.168.1.100:5775
|
||||
```
|
||||
|
||||
If `BACKEND_URL` is not provided, fallback is:
|
||||
|
||||
- Android emulator: `http://10.0.2.2:5775`
|
||||
- Others: `http://localhost:5775`
|
||||
If `BACKEND_URL` is not provided, the app uses the production backend URL above.
|
||||
|
||||
@@ -12,7 +12,7 @@ MeeYao Divination is designed based on traditional oriental culture. Our core go
|
||||
|
||||
**Developer:** Ann Lee
|
||||
|
||||
**Contact Email:** ann@xumee.com
|
||||
**Contact Email:** ann@xunmee.com
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ In accordance with CCPA/CPRA and U.S. local privacy laws, you enjoy the followin
|
||||
|
||||
You can submit data requests through the only dedicated contact method:
|
||||
|
||||
- **Contact Email**: ann@xumee.com
|
||||
- **Contact Email**: ann@xunmee.com
|
||||
|
||||
I will respond to your legitimate request within 45 days, and properly verify your identity to ensure data security before processing.
|
||||
|
||||
@@ -152,7 +152,7 @@ This Privacy Policy may be updated irregularly to adapt to platform rules and le
|
||||
|
||||
If you have any questions, suggestions or privacy-related complaints about this Privacy Policy, please contact me:
|
||||
|
||||
**Developer Email**: ann@xumee.com
|
||||
**Developer Email**: ann@xunmee.com
|
||||
|
||||
If you are a California resident and dissatisfied with the processing result, you can consult the local privacy regulatory authority.
|
||||
|
||||
|
||||
@@ -118,4 +118,4 @@ I reserve the right to revise and update these Terms of Service at any time. Mat
|
||||
If you have questions, feedback or legal inquiries about these Terms, please contact:
|
||||
|
||||
- **Developer**: Individual Independent Developer
|
||||
- **Contact Email**: ann@xumee.com
|
||||
- **Contact Email**: ann@xunmee.com
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
**开发者**:Ann Lee
|
||||
|
||||
**联系邮箱**:ann@xumee.com
|
||||
**联系邮箱**:ann@xunmee.com
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
|
||||
您可以通过唯一指定联系方式提交数据请求:
|
||||
|
||||
- **联系邮箱**:ann@xumee.com
|
||||
- **联系邮箱**:ann@xunmee.com
|
||||
|
||||
我将在 45 天内回复您的合法请求,并在处理前妥善验证您的身份以确保数据安全。
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
|
||||
如果您对本隐私政策有任何疑问、建议或隐私相关投诉,请联系我:
|
||||
|
||||
**开发者邮箱**:ann@xumee.com
|
||||
**开发者邮箱**:ann@xunmee.com
|
||||
|
||||
如果您是加州居民且对处理结果不满意,可咨询当地隐私监管机构。
|
||||
|
||||
|
||||
@@ -118,4 +118,4 @@
|
||||
如果您对本条款有疑问、反馈或法律咨询,请联系:
|
||||
|
||||
- **开发者**:独立个人开发者
|
||||
- **联系邮箱**:ann@xumee.com
|
||||
- **联系邮箱**:ann@xunmee.com
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
**開發者**:Ann Lee
|
||||
|
||||
**聯繫郵箱**:ann@xumee.com
|
||||
**聯繫郵箱**:ann@xunmee.com
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
|
||||
您可以通過唯一指定聯繫方式提交數據請求:
|
||||
|
||||
- **聯繫郵箱**:ann@xumee.com
|
||||
- **聯繫郵箱**:ann@xunmee.com
|
||||
|
||||
我將在 45 天內回覆您的合法請求,並在處理前妥善驗證您的身份以確保數據安全。
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
|
||||
如果您對本隱私政策有任何疑問、建議或隱私相關投訴,請聯繫我:
|
||||
|
||||
**開發者郵箱**:ann@xumee.com
|
||||
**開發者郵箱**:ann@xunmee.com
|
||||
|
||||
如果您是加州居民且對處理結果不滿意,可諮詢當地隱私監管機構。
|
||||
|
||||
|
||||
@@ -118,4 +118,4 @@
|
||||
如果您對本條款有疑問、反饋或法律諮詢,請聯繫:
|
||||
|
||||
- **開發者**:獨立個人開發者
|
||||
- **聯繫郵箱**:ann@xumee.com
|
||||
- **聯繫郵箱**:ann@xunmee.com
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import 'dart:io';
|
||||
|
||||
class Env {
|
||||
static const _productionBackendUrl = 'https://api.meeyao.com';
|
||||
|
||||
static String get backendUrl {
|
||||
final injected = const String.fromEnvironment('BACKEND_URL');
|
||||
if (injected.isNotEmpty && injected != 'false') {
|
||||
return injected;
|
||||
}
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
return 'http://10.0.2.2:5775';
|
||||
}
|
||||
if (Platform.isIOS) {
|
||||
return 'http://192.168.1.63:5775';
|
||||
}
|
||||
return 'http://localhost:5775';
|
||||
return _productionBackendUrl;
|
||||
}
|
||||
|
||||
static Future<void> init() async {}
|
||||
|
||||
@@ -32,6 +32,22 @@ _MAX_CONTEXT_ATTACHMENTS = 3
|
||||
_RUNTIME_AGENT_OUTPUT_ADAPTER = TypeAdapter(RuntimeAgentOutput)
|
||||
|
||||
|
||||
def create_context_messages_cache() -> Any:
|
||||
from core.agentscope.caches.context_messages_cache import (
|
||||
create_context_messages_cache as factory,
|
||||
)
|
||||
|
||||
return factory()
|
||||
|
||||
|
||||
def create_attachment_content_cache() -> Any:
|
||||
from core.agentscope.caches.attachment_content_cache import (
|
||||
create_attachment_content_cache as factory,
|
||||
)
|
||||
|
||||
return factory()
|
||||
|
||||
|
||||
def _serialize_tool_agent_output(
|
||||
*,
|
||||
metadata: AgentChatMessageMetadata | None,
|
||||
@@ -203,12 +219,6 @@ async def _build_recent_context_messages(
|
||||
context_config: "MessageContextConfig",
|
||||
) -> list[Msg]:
|
||||
from agentscope.message import Msg
|
||||
from core.agentscope.caches.attachment_content_cache import (
|
||||
create_attachment_content_cache,
|
||||
)
|
||||
from core.agentscope.caches.context_messages_cache import (
|
||||
create_context_messages_cache,
|
||||
)
|
||||
from core.agentscope.services.context_service import AgentContextService
|
||||
from services.base.supabase import supabase_service
|
||||
from v1.agent.repository import AgentRepository
|
||||
@@ -348,12 +358,10 @@ async def _build_recent_context_messages(
|
||||
|
||||
|
||||
async def run_agentscope_task(command: dict[str, Any]) -> dict[str, object]:
|
||||
from core.agentscope.events import (
|
||||
AgentScopeAgUiCodec,
|
||||
AgentScopeEventPipeline,
|
||||
RedisStreamBus,
|
||||
SqlAlchemyEventStore,
|
||||
)
|
||||
from core.agentscope.events.agui_codec import AgentScopeAgUiCodec
|
||||
from core.agentscope.events.pipeline import AgentScopeEventPipeline
|
||||
from core.agentscope.events.redis_bus import RedisStreamBus
|
||||
from core.agentscope.events.store import SqlAlchemyEventStore
|
||||
from core.config.settings import config
|
||||
from core.db.session import AsyncSessionLocal
|
||||
from services.base.redis import get_or_init_redis_client
|
||||
|
||||
@@ -183,11 +183,6 @@ class DatabaseSettings(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class SensitiveWordSettings(BaseModel):
|
||||
use_aliyun: bool = True
|
||||
fallback_to_local: bool = True
|
||||
|
||||
|
||||
class TestSettings(BaseModel):
|
||||
email: str = ""
|
||||
code: str = ""
|
||||
@@ -232,12 +227,10 @@ class AppleIapSettings(BaseModel):
|
||||
bundle_id: str = Field(default="com.meeyao.qianwen", min_length=1)
|
||||
root_cert_url: str = "https://www.apple.com/certificateauthority/AppleIncRootCertificate.cer"
|
||||
jws_x5c_cert_url: str = "https://api.storekit.itunes.apple.com/v1/verificationKeys"
|
||||
environment: Literal["auto", "sandbox", "production"] = "auto"
|
||||
server_api_issuer_id: str | None = None
|
||||
server_api_key_id: str | None = None
|
||||
server_api_private_key: SecretStr | None = None
|
||||
sandbox_tester_email: str | None = None
|
||||
sandbox_tester_password: SecretStr | None = None
|
||||
server_notifications_url: str | None = None
|
||||
|
||||
|
||||
def _resolve_env_files() -> list[str]:
|
||||
@@ -283,7 +276,6 @@ class Settings(BaseSettings):
|
||||
storage: StorageSettings = StorageSettings()
|
||||
llm: LlmSettings = LlmSettings()
|
||||
database: DatabaseSettings = DatabaseSettings()
|
||||
sensitive_word: SensitiveWordSettings = Field(default_factory=SensitiveWordSettings)
|
||||
test: TestSettings = Field(default_factory=TestSettings)
|
||||
taskiq: TaskiqSettings = Field(default_factory=TaskiqSettings)
|
||||
agent_runtime: AgentRuntimeSettings = Field(default_factory=AgentRuntimeSettings)
|
||||
|
||||
@@ -9,7 +9,7 @@ from fastapi import Depends
|
||||
from redis.asyncio import Redis
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from core.agentscope.events import RedisStreamBus
|
||||
from core.agentscope.events.redis_bus import RedisStreamBus
|
||||
from core.agentscope.tools.tool_result_storage import (
|
||||
create_tool_result_storage,
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import Annotated
|
||||
|
||||
from ag_ui.core import RunAgentInput
|
||||
from core.http.errors import ApiProblemError, problem_payload
|
||||
from core.agentscope.events import to_sse_event
|
||||
from core.agentscope.events.sse import to_sse_event
|
||||
from core.agentscope.schemas.agui_input import (
|
||||
parse_run_input,
|
||||
validate_run_request_messages_contract,
|
||||
|
||||
@@ -47,7 +47,6 @@ class AppleJwsVerifier:
|
||||
*,
|
||||
expected_bundle_id: str,
|
||||
expected_product_id: str,
|
||||
expected_environment: str,
|
||||
) -> VerifiedTransaction | VerificationError:
|
||||
try:
|
||||
unverified_header = jwt.get_unverified_header(signed_transaction_info)
|
||||
@@ -148,17 +147,6 @@ class AppleJwsVerifier:
|
||||
detail=f"Invalid environment: {environment}",
|
||||
)
|
||||
|
||||
if environment != expected_environment:
|
||||
logger.error(
|
||||
"Environment mismatch: expected=%s got=%s",
|
||||
expected_environment,
|
||||
environment,
|
||||
)
|
||||
return VerificationError(
|
||||
code="PAYMENT_ENVIRONMENT_MISMATCH",
|
||||
detail=f"Environment mismatch: expected={expected_environment} got={environment}",
|
||||
)
|
||||
|
||||
revocation_date_raw = payload.get("revocationDate")
|
||||
revocation_date: int | None = (
|
||||
int(revocation_date_raw) if revocation_date_raw is not None else None
|
||||
|
||||
@@ -114,12 +114,10 @@ class PaymentService:
|
||||
)
|
||||
|
||||
expected_bundle_id = config.apple_iap.bundle_id
|
||||
expected_environment = "Sandbox" if config.runtime.environment != "prod" else "Production"
|
||||
result = self._verifier.verify_signed_transaction(
|
||||
request.signed_transaction_info,
|
||||
expected_bundle_id=expected_bundle_id,
|
||||
expected_product_id=product_mapping.app_store_product_id,
|
||||
expected_environment=expected_environment,
|
||||
)
|
||||
|
||||
if isinstance(result, VerificationError):
|
||||
|
||||
@@ -82,10 +82,8 @@ class _FakeVerifier:
|
||||
*,
|
||||
expected_bundle_id: str,
|
||||
expected_product_id: str,
|
||||
expected_environment: str,
|
||||
) -> VerifiedTransaction | VerificationError:
|
||||
del signed_transaction_info, expected_bundle_id, expected_product_id
|
||||
del expected_environment
|
||||
return self._result
|
||||
|
||||
|
||||
@@ -290,6 +288,32 @@ class TestPaymentServiceSuccessfulGrant:
|
||||
assert len(points_repo.appended_ledger) == 1
|
||||
assert len(payment_repo.inserted_transactions) == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_grants_production_transaction_from_verified_payload(self) -> None:
|
||||
payment_repo = _FakePaymentRepository()
|
||||
service = PaymentService(
|
||||
payment_repo=payment_repo,
|
||||
points_repo=_FakePointsRepository(),
|
||||
verifier=_FakeVerifier(
|
||||
result=_make_verified_transaction(environment="Production")
|
||||
),
|
||||
)
|
||||
request = VerifyTransactionRequest(
|
||||
productCode="starter_pack",
|
||||
appStoreProductId="com.meeyao.qianwen.starter_pack",
|
||||
transactionId="2000000123456789",
|
||||
signedTransactionInfo="fake_jws",
|
||||
)
|
||||
|
||||
result = await service.verify_and_grant(
|
||||
user_id=uuid4(),
|
||||
user_email="test@example.com",
|
||||
request=request,
|
||||
)
|
||||
|
||||
assert result.status == "granted"
|
||||
assert payment_repo.inserted_transactions[0].environment == "Production"
|
||||
|
||||
|
||||
class TestPaymentServiceStarterPackIneligible:
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -108,7 +108,6 @@ This document is the source of truth for backend RFC7807 `code` values consumed
|
||||
|---|---:|---|---|
|
||||
| `PAYMENT_PRODUCT_NOT_FOUND` | 404 | `productCode` does not exist or is not enabled | Refresh packages and show product-unavailable message |
|
||||
| `PAYMENT_PRODUCT_MISMATCH` | 422 | Client product ID does not match backend/Apple verification result | Block grant and prompt retry |
|
||||
| `PAYMENT_ENVIRONMENT_MISMATCH` | 422 | Transaction environment (Sandbox/Production) does not match server environment | Show purchase-verification-failed message |
|
||||
| `PAYMENT_TRANSACTION_INVALID` | 422 | Apple signed transaction invalid, signature verification failed, or payload malformed | Show purchase-verification-failed message |
|
||||
| `PAYMENT_TRANSACTION_REVOKED` | 409 | Transaction has been revoked or refunded, grant not allowed | Show purchase-unavailable message |
|
||||
| `PAYMENT_TRANSACTION_CONFLICT` | 409 | Transaction already processed by another user or in conflicting state | Prompt to contact support or refresh balance |
|
||||
|
||||
@@ -14,6 +14,7 @@ Protocol verification status:
|
||||
|
||||
- Current strategy: additive evolution (`backward-compatible`).
|
||||
- Breaking change requires explicit migration + rollback notes (`requires-migration`).
|
||||
- Apple IAP environment is auto-detected from verified Apple transaction payloads; the verify API is not split by Sandbox/Production.
|
||||
|
||||
## Route overview
|
||||
|
||||
@@ -70,7 +71,6 @@ Verify and grant credits for an Apple IAP transaction.
|
||||
|---|---:|---|
|
||||
| `PAYMENT_PRODUCT_NOT_FOUND` | 404 | `productCode` does not exist or is not enabled |
|
||||
| `PAYMENT_PRODUCT_MISMATCH` | 422 | Client product ID does not match backend/Apple verification result |
|
||||
| `PAYMENT_ENVIRONMENT_MISMATCH` | 422 | Transaction environment (Sandbox/Production) does not match server environment |
|
||||
| `PAYMENT_TRANSACTION_INVALID` | 422 | Apple signed transaction invalid, signature verification failed, or payload malformed |
|
||||
| `PAYMENT_TRANSACTION_REVOKED` | 409 | Transaction has been revoked or refunded, grant not allowed |
|
||||
| `PAYMENT_TRANSACTION_CONFLICT` | 409 | Transaction already processed by another user or in conflicting state |
|
||||
@@ -119,6 +119,14 @@ product_mappings:
|
||||
- Backend tracks purchase via `register_bonus_claims.has_purchased_starter_pack`
|
||||
- If already purchased, returns `PAYMENT_STARTER_PACK_INELIGIBLE` (409)
|
||||
|
||||
## Environment handling
|
||||
|
||||
- `signedTransactionInfo` is verified server-side and its Apple-provided `environment` field is the source of truth.
|
||||
- Valid environments are `Sandbox` and `Production`.
|
||||
- TestFlight and App Review Sandbox transactions are accepted when Apple signs them as `Sandbox`.
|
||||
- App Store Production transactions are accepted when Apple signs them as `Production`.
|
||||
- Backend configuration defaults to `apple_iap.environment=auto`; it must not force all verifications into one Apple environment.
|
||||
|
||||
## Ledger integration
|
||||
|
||||
- Successful purchases create a ledger entry with:
|
||||
|
||||
Reference in New Issue
Block a user