feat: 实现 iOS Apple Pay 内购支付功能
前端: - 集成 in_app_purchase 插件,实现 IAP 支付流程 - 添加支付模块 (payments/) 处理产品获取、购买、验证 - 积分中心页面集成 Apple Pay 购买入口 - 设置页面重构: 关于/隐私/协议直接展示,删除 legal_center 子页面 - 修复欢迎引导页滚动检测阈值问题 - 修复解卦结果页 iOS 侧滑返回手势被阻止的问题 - 邀请码绑定按钮临时禁用(待后端实现) 后端: - 新增 apple_iap_transactions 表记录交易 - 实现 Apple 服务器端验证 (App Store Server API) - 支付成功后自动发放积分 - 支持 Sandbox/Production 环境切换 - 添加退款处理和交易状态机 协议: - 更新积分流水协议,支持 purchase/refund 类型 - 新增 PAYMENT_* 错误码
This commit is contained in:
@@ -44,6 +44,7 @@ async def get_available_packages(
|
||||
packages=[
|
||||
PackageInfo(
|
||||
productCode=pkg.product_code,
|
||||
appStoreProductId=pkg.app_store_product_id,
|
||||
type=pkg.type.value,
|
||||
price=pkg.price,
|
||||
credits=pkg.credits,
|
||||
|
||||
@@ -19,6 +19,9 @@ class PackageInfo(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True, serialize_by_alias=True)
|
||||
|
||||
product_code: str = Field(alias="productCode", min_length=1, max_length=128)
|
||||
app_store_product_id: str = Field(
|
||||
alias="appStoreProductId", min_length=1, max_length=256
|
||||
)
|
||||
type: Literal["starter", "regular"]
|
||||
price: float = Field(ge=0)
|
||||
credits: int = Field(ge=1)
|
||||
|
||||
@@ -23,6 +23,7 @@ from schemas.domain.points import (
|
||||
from schemas.enums import PointsBizType, PointsChangeType, PointsOperatorType
|
||||
from schemas.domain.points import ApplyPointsChangeCommand
|
||||
from schemas.shared.user import parse_profile_settings
|
||||
from v1.payments.service import _load_product_mappings
|
||||
from v1.points.repository import PointsRepository
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -67,6 +68,7 @@ class RegisterBonusResult:
|
||||
@dataclass(frozen=True)
|
||||
class PackageInfoResult:
|
||||
product_code: str
|
||||
app_store_product_id: str
|
||||
type: PackageType
|
||||
price: float
|
||||
credits: int
|
||||
@@ -461,6 +463,8 @@ class PointsService:
|
||||
email_hash=email_hash
|
||||
)
|
||||
|
||||
product_mappings = _load_product_mappings()
|
||||
|
||||
packages: list[PackageInfoResult] = []
|
||||
for pkg in pkg_config.packages:
|
||||
if not pkg.enabled:
|
||||
@@ -468,9 +472,13 @@ class PointsService:
|
||||
if pkg.type == PackageType.STARTER and has_starter:
|
||||
continue
|
||||
|
||||
mapping = product_mappings.get(pkg.product_code)
|
||||
app_store_product_id = mapping.app_store_product_id if mapping else ""
|
||||
|
||||
packages.append(
|
||||
PackageInfoResult(
|
||||
product_code=pkg.product_code,
|
||||
app_store_product_id=app_store_product_id,
|
||||
type=pkg.type,
|
||||
price=pkg.price,
|
||||
credits=pkg.credits,
|
||||
|
||||
Reference in New Issue
Block a user