PHPackages                             lemoba/mobile-monetization - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. lemoba/mobile-monetization

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

lemoba/mobile-monetization
==========================

Laravel package for Apple/Google login verification, mobile IAP validation, Unity LevelPlay rewarded ad callbacks, and Firebase Cloud Messaging.

v1.0.3(3w ago)1117↓35.7%MITPHPPHP &gt;=8.1CI passing

Since Apr 30Pushed 1w agoCompare

[ Source](https://github.com/lemoba/mobile-monetization)[ Packagist](https://packagist.org/packages/lemoba/mobile-monetization)[ RSS](/packages/lemoba-mobile-monetization/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (5)Dependencies (5)Versions (5)Used By (0)

Mobile Monetization for Laravel
===============================

[](#mobile-monetization-for-laravel)

Laravel 扩展包，用于移动端短剧/内容应用常见的后端验证能力：

- iOS Sign in with Apple 登录 token 验证
- Android Google 登录 ID token 验证
- iOS App Store / Android Google Play 内购验证
- Unity LevelPlay 激励广告 S2S 回调验签
- Firebase Cloud Messaging iOS / Android 消息推送

本包只做「可信验证、签名校验、接口封装、结果归一化、推送发送」，不创建数据表，不写数据库，不给用户加金币，不开通 VIP，不解锁视频。订单幂等、金币流水、会员权益、短剧解锁、推送 token 保存等业务逻辑全部由调用方完成。

安装
--

[](#安装)

通过 Composer 安装：

```
composer require lemoba/mobile-monetization
```

发布配置：

```
php artisan vendor:publish --tag=mobile-monetization-config
```

环境变量
----

[](#环境变量)

```
MOBILE_MONETIZATION_CACHE_STORE=redis
MOBILE_MONETIZATION_CACHE_PREFIX=mobile_monetization
MOBILE_MONETIZATION_JWKS_TTL=3600
MOBILE_MONETIZATION_OAUTH_TOKEN_TTL=3300

APPLE_BUNDLE_ID=com.example.app
APPLE_TEAM_ID=YOUR_APPLE_TEAM_ID
APPLE_ISSUER_ID=YOUR_APP_STORE_CONNECT_ISSUER_ID
APPLE_CLIENT_ID=com.example.app
APPLE_KEY_ID=ABC123DEFG
APPLE_PRIVATE_KEY_PATH=/secure/AuthKey_ABC123DEFG.p8
APPLE_PROMOTIONAL_OFFER_KEY_ID=PROMO12345
APPLE_PROMOTIONAL_OFFER_PRIVATE_KEY_PATH=/secure/SubscriptionKey_PROMO12345.p8
APPLE_IAP_ENVIRONMENT=production

GOOGLE_ANDROID_CLIENT_IDS=android-oauth-client-id.apps.googleusercontent.com
GOOGLE_PLAY_PACKAGE_NAME=com.example.app
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH=/secure/google-play-service-account.json

MOBILE_COIN_PRODUCT_IDS=coins_60,coins_300,coins_980
MOBILE_VIP_WEEK_PRODUCT_ID=vip_week
MOBILE_VIP_MONTH_PRODUCT_ID=vip_month
MOBILE_VIP_YEAR_PRODUCT_ID=vip_year

LEVELPLAY_SECRET=your_levelplay_secret

FCM_ANDROID_PROJECT_ID=android-firebase-project-id
FCM_ANDROID_SERVICE_ACCOUNT_JSON_PATH=/secure/firebase-android-service-account.json
FCM_IOS_PROJECT_ID=ios-firebase-project-id
FCM_IOS_SERVICE_ACCOUNT_JSON_PATH=/secure/firebase-ios-service-account.json
FCM_DEFAULT_PLATFORM=android
FCM_TIMEOUT=15
```

配置文件会按职责发布到主项目：

```
config/mobile-monetization.php  # Redis cache store、cache key 前缀、TTL
config/mobile-auth.php          # Apple / Google 登录
config/mobile-payments.php      # App Store / Google Play 支付
config/mobile-ads.php           # LevelPlay 广告
config/mobile-push.php          # FCM 推送，Android/iOS 两套 Firebase 文件

```

路由
--

[](#路由)

本包不注册任何默认路由，由调用方在主项目中自行定义路由和控制器。示例：

```
use Illuminate\Support\Facades\Route;
use Lemoba\MobileMonetization\Facades\MobileMonetization;

Route::post('/auth/apple', function () {
    $data = request()->validate([
        'identity_token' => ['required', 'string'],
        'nonce' => ['nullable', 'string'],
    ]);

    return MobileMonetization::verifyAppleIdentityToken(
        $data['identity_token'],
        $data['nonce'] ?? null
    );
});
```

缓存
--

[](#缓存)

JWKS、公钥集合、Google Play OAuth token、App Store Server API bearer token、FCM OAuth token 都会走 Laravel Cache，并默认使用 Redis：

```
// config/mobile-monetization.php
'cache' => [
    'store' => env('MOBILE_MONETIZATION_CACHE_STORE', 'redis'),
    'key_prefix' => env('MOBILE_MONETIZATION_CACHE_PREFIX', 'mobile_monetization'),
    'jwks_ttl' => 3600,
    'oauth_token_ttl' => 3300,
],
```

调用方可以改 `store` 使用任意 Laravel cache store，但生产环境建议 Redis。

登录验证
----

[](#登录验证)

Apple：

```
use Lemoba\MobileMonetization\Facades\MobileMonetization;

$identity = MobileMonetization::verifyAppleIdentityToken($identityToken, $nonce);
```

Google：

```
use Lemoba\MobileMonetization\Facades\MobileMonetization;

$identity = MobileMonetization::verifyGoogleIdToken($idToken, $nonce);
```

返回字段包含：

```
[
    'provider' => 'apple',
    'provider_user_id' => '...',
    'email' => '...',
    'email_verified' => true,
    'claims' => [],
]
```

调用方应该用 `provider + provider_user_id` 去绑定或创建自己的用户。

`provider_user_id` 来自 Apple / Google ID token 的 `sub` 字段。本包会强制校验 `sub`，如果 token 中没有 `sub` 会直接抛出异常，不会返回空的 `provider_user_id`。

支付验证
----

[](#支付验证)

iOS App Store：

```
use Lemoba\MobileMonetization\Facades\MobileMonetization;

$purchase = MobileMonetization::verifyAppleTransactionId($transactionId);

// 或者客户端已拿到 signedTransactionInfo:
$purchase = MobileMonetization::verifyAppleSignedTransaction($signedTransactionInfo);
```

iOS App Store 订阅优惠签名：

```
use Lemoba\MobileMonetization\Facades\MobileMonetization;

// StoreKit 1 / StoreKit 2 Product.SubscriptionOffer.Signature 使用。
$offer = MobileMonetization::applePromotionalOfferSignature(
    productIdentifier: 'vip_month',
    subscriptionOfferId: 'intro_month_50',
    appAccountToken: (string) $user->id, // 如果客户端传 UUID，这里传同一个 UUID。
);

// 返回:
// [
//     'keyIdentifier' => 'PROMO12345',
//     'nonce' => '47f8a4b5-5957-4d56-bf0f-c7416f33c701',
//     'timestamp' => 1714567890123,
//     'signature' => 'base64-encoded-signature',
// ]
```

`applePromotionalOfferSignature()` 参数：

```
MobileMonetization::applePromotionalOfferSignature(
    string $productIdentifier,
    string $subscriptionOfferId,
    string $appAccountToken = '',
    ?string $nonce = null,
    ?int $timestamp = null,
);
```

- `productIdentifier`：App Store Connect 中的订阅商品 ID，例如 `vip_month`。
- `subscriptionOfferId`：App Store Connect 中配置的 promotional offer identifier，例如 `intro_month_50`。
- `appAccountToken`：与客户端购买时传入的 app account token 保持一致；如果客户端不传，可以留空。
- `nonce`：可选，不传时服务端自动生成 UUID。
- `timestamp`：可选，毫秒时间戳，不传时服务端自动生成。

客户端按 Apple StoreKit API 使用返回字段即可：

```
[
    'identifier' => 'intro_month_50',
    'keyIdentifier' => $offer['keyIdentifier'],
    'nonce' => $offer['nonce'],
    'signature' => $offer['signature'],
    'timestamp' => $offer['timestamp'],
]
```

如配置了 `APPLE_PROMOTIONAL_OFFER_KEY_ID` / `APPLE_PROMOTIONAL_OFFER_PRIVATE_KEY_PATH`，会优先使用订阅优惠专用密钥；否则回退到 `APPLE_KEY_ID` / `APPLE_PRIVATE_KEY_PATH`。

新版 StoreKit 2 promotional offer compact JWS：

```
use Lemoba\MobileMonetization\Facades\MobileMonetization;

$compactJws = MobileMonetization::applePromotionalOfferJws(
    productId: 'vip_month',
    offerIdentifier: 'intro_month_50',
    transactionId: $originalTransactionId, // 可选。
);
```

`applePromotionalOfferJws()` 参数：

```
MobileMonetization::applePromotionalOfferJws(
    string $productId,
    string $offerIdentifier,
    ?string $transactionId = null,
    ?string $nonce = null,
);
```

- `productId`：App Store Connect 中的订阅商品 ID。
- `offerIdentifier`：App Store Connect 中配置的 promotional offer identifier。
- `transactionId`：可选，通常传原始订阅交易 ID。
- `nonce`：可选，不传时服务端自动生成 UUID。

Android Google Play 一次性消耗商品：

```
use Lemoba\MobileMonetization\Facades\MobileMonetization;

$purchase = MobileMonetization::verifyGoogleProduct($productId, $purchaseToken);

if ($purchase->valid) {
    // 调用方先按 transaction_id 做唯一幂等，再发金币。
    MobileMonetization::acknowledgeGoogleProduct($productId, $purchaseToken);
    MobileMonetization::consumeGoogleProduct($productId, $purchaseToken);
}
```

Android Google Play VIP 周/月/年订阅：

```
$purchase = MobileMonetization::verifyGoogleSubscription($productId, $purchaseToken);

if ($purchase->active()) {
    // 调用方按 original_transaction_id 或 transaction_id 更新自己的会员到期时间。
}

// Google Play 订阅优惠没有类似 Apple 的服务端签名。
// 客户端应使用 Play Billing ProductDetails.SubscriptionOfferDetails 返回的 offerToken 发起购买；
// 服务端在购买后校验 purchaseToken，并检查 Google 返回的 basePlanId / offerId。
$offer = MobileMonetization::verifyGoogleSubscriptionOffer(
    subscriptionId: $productId,
    purchaseToken: $purchaseToken,
    expectedBasePlanId: 'monthly',
    expectedOfferId: 'intro_month_50',
);

$offer['purchase']->active();
$offer['base_plan_id']; // monthly
$offer['offer_id'];     // intro_month_50
$offer['offer_tags'];   // Google Play Console 配置的标签
```

统一返回对象：

```
$purchase->toArray();
```

关键字段：

```
[
    'platform' => 'ios|android',
    'product_id' => 'coins_60',
    'transaction_id' => '...',
    'original_transaction_id' => '...',
    'type' => 'consumable|subscription',
    'valid' => true,
    'active' => true,
    'consumable' => true,
    'purchased_at_ms' => 1710000000000,
    'expires_at_ms' => null,
    'raw' => [],
]
```

建议调用方业务处理：

```
if ($purchase->valid && $purchase->consumable) {
    // 1. 用 transaction_id 建唯一索引或幂等锁。
    // 2. 根据 product_id 查自己的金币配置。
    // 3. 写订单、写金币流水、增加余额。
}

if ($purchase->active() && $purchase->type === 'subscription') {
    // 1. 用 original_transaction_id 关联订阅。
    // 2. 用 expires_at_ms 更新 VIP 到期时间。
}
```

LevelPlay 激励广告
--------------

[](#levelplay-激励广告)

在 LevelPlay 后台配置 S2S Rewarded Video Callback URL：

```
https://your-domain.com/your-levelplay-callback

```

本包不提供默认 HTTP 控制器，也不注册默认路由。调用方需要在主项目中自行创建回调入口，调用本包完成验签，并保存 `event_id` 做唯一幂等。

业务控制器示例：

```
use Illuminate\Http\Request;
use Lemoba\MobileMonetization\Facades\MobileMonetization;

public function reward(Request $request)
{
    $reward = MobileMonetization::verifyLevelPlayRewardCallback($request);

    // 调用方业务逻辑：
    // 1. 用 event_id 做唯一幂等，event_id 对应 LevelPlay eventId。
    // 2. 用 user_id/app_user_id 或 dynamic_user_id 映射自己的用户。
    // 3. 用 order_id 关联前端传入的自定义订单号（如果配置了 customParameters）。
    // 4. 根据 reward_amount 或自己的广告奖励配置给金币。
    // 5. 写金币流水、余额变化、任务记录等。

    return response(MobileMonetization::levelPlayOkResponse($reward['event_id']), 200)
        ->header('Content-Type', 'text/plain');
}
```

本地测试如果不方便生成 LevelPlay 签名，可以传入第二个参数 `true` 开启 dev 模式，跳过 `LEVELPLAY_SECRET` 和 `signature` 校验：

```
$reward = MobileMonetization::verifyLevelPlayRewardCallback($request, dev: true);
```

`verifyRewardCallback()` 返回：

```
[
    'event_id' => '...',
    'user_id' => '...',
    'app_user_id' => '...',
    'dynamic_user_id' => '...',
    'reward_item' => 'coins',
    'reward_amount' => 10,
    'rewards' => '10',
    'country' => 'SG',
    'publisher_sub_id' => '0',
    'custom_parameters' => [
        'order_id' => 'ORD-20260429-001',
    ],
    'order_id' => 'ORD-20260429-001',
    'ad_unit' => '...',
    'placement' => '...',
    'network' => '...',
    'timestamp' => 1710000000,
    'raw' => [],
]
```

Firebase Cloud Messaging 推送
---------------------------

[](#firebase-cloud-messaging-推送)

由于 Android 和 iOS 不在同一个 Firebase 后台，本包在 `config/mobile-push.php` 中分别配置两套 service account：

```
'fcm' => [
    'android' => [
        'project_id' => env('FCM_ANDROID_PROJECT_ID'),
        'service_account_json_path' => env('FCM_ANDROID_SERVICE_ACCOUNT_JSON_PATH'),
    ],
    'ios' => [
        'project_id' => env('FCM_IOS_PROJECT_ID'),
        'service_account_json_path' => env('FCM_IOS_SERVICE_ACCOUNT_JSON_PATH'),
    ],
],
```

发送到单个设备 token：

```
use Lemoba\MobileMonetization\Facades\MobileMonetization;

$message = MobileMonetization::sendFcmToToken(
    platform: 'ios',
    token: $deviceToken,
    title: 'VIP 到期提醒',
    body: '你的会员即将到期',
    data: [
        'type' => 'vip_expiring',
        'user_id' => (string) $userId,
    ],
    options: [
        'apns' => [
            'payload' => [
                'aps' => [
                    'sound' => 'default',
                ],
            ],
        ],
    ]
);

$message->toArray();
```

发送到 Android：

```
$message = MobileMonetization::sendFcmToToken(
    platform: 'android',
    token: $deviceToken,
    title: '金币到账',
    body: '看广告奖励已发放',
    data: [
        'type' => 'coins_granted',
        'amount' => '10',
    ],
    options: [
        'android' => [
            'priority' => 'HIGH',
        ],
    ]
);
```

发送到 topic：

```
$message = MobileMonetization::sendFcmToTopic(
    platform: 'android',
    topic: 'vip_users',
    title: '新剧上线',
    body: '会员可抢先观看'
);
```

`data` 会统一转成字符串值，符合 FCM HTTP v1 对 data payload 的要求。FCM OAuth token 会按平台和 service account 缓存在 Redis 中。

数据库说明
-----

[](#数据库说明)

本包没有迁移文件，也不会调用 `DB`、Model 或 Schema。推荐调用方自行维护这些业务表或存储：

- 用户第三方登录绑定表
- 充值订单表
- 内购交易幂等表
- 金币钱包表
- 金币流水表
- VIP 订阅表
- LevelPlay 广告事件表
- 短剧视频解锁表
- 设备推送 token 表

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance97

Actively maintained with recent releases

Popularity16

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~6 days

Total

4

Last Release

21d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/679cb48020394fc81050124ce7ac11f1341de2d8b08ac8265e824aa8029308c7?d=identicon)[lemoba](/maintainers/lemoba)

---

Top Contributors

[![lemoba](https://avatars.githubusercontent.com/u/31270124?v=4)](https://github.com/lemoba "lemoba (9 commits)")

### Embed Badge

![Health badge](/badges/lemoba-mobile-monetization/health.svg)

```
[![Health](https://phpackages.com/badges/lemoba-mobile-monetization/health.svg)](https://phpackages.com/packages/lemoba-mobile-monetization)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

76318.2M110](/packages/laravel-mcp)[illuminate/auth

The Illuminate Auth package.

9327.9M1.2k](/packages/illuminate-auth)[api-platform/laravel

API Platform support for Laravel

59156.3k10](/packages/api-platform-laravel)[spatie/laravel-export

Create a static site bundle from a Laravel app

670139.5k6](/packages/spatie-laravel-export)[fleetbase/core-api

Core Framework and Resources for Fleetbase API

1232.2k16](/packages/fleetbase-core-api)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
