ホームページ  >  記事  >  PHPフレームワーク  >  Laravelをベースとした会員配信システムの開発

Laravelをベースとした会員配信システムの開発

Guanhui
Guanhui転載
2020-05-28 10:16:323582ブラウズ

Laravelをベースとした会員配信システムの開発

最近、Sitesauce の既存の基盤に新しいメンバーシップ システムが追加されました。具体的な実装の詳細についてこの記事を書きました。

注: どの段階にいても記事を読めるように、このプログラムを最初から作成します。もちろん、すでに Laravel に精通している場合は、Rewardful のようなプラットフォームでこれを行うことができ、時間を大幅に節約できます。

概念を明確にするために、次のテキストでは、招待者を上司に置き換え、招待者を部下に置き換えます。

まず、ニーズを明確にする必要があります。第一に、ユーザーはプログラムを通じてリンクの招待を共有することができ、友達登録の場合、招待された人はリンクを通じて登録して招待関係を結び付けることができ、第二に、部下が消費すると、上司は対応するコミッションを得ることができます。

次に、登録を実装する方法を決定する必要があります。当初はFathomの方法を利用する予定でしたが、ユーザーが特定のページに誘導される限り、そのユーザーは特別な紹介ページとしてマークされ、ユーザーが登録を完了した後、関係がバインドされます。しかし最終的には、推奨ページを構築するリンクにパラメータ ?via=miguel を追加するという、Rewardful のアプローチが採用されました。

さて、登録ページを作成しましょう。登録ページでは、プログラムはリンク パラメーター経由で上位と一致します。コードは単純です。via が存在する場合は、それを Cookie に 30 日間保存します。これを必要とする複数の異なるサブドメインがあるため、すべてのサブドメインが Cookie を使用できるように、メイン ドメインの下に追加します。具体的なコードは次のとおりです。

import Cookies from 'js-cookie'
const via = new URL(location.href).searchParams.get('via')
if (via) {
    Cookies.set('sitesauce_affiliate', via, {
        expires: 30,
        domain: '.sitesauce.app',
        secure: true,
        sameSite: 'lax'
    })
}

これの利点は、メンバーがこの共有を通じて登録せず、その後自分で登録した場合に、そのメンバーがその上司の推薦によって来たことが明確にわかることです。 。さらに一歩進めて、新規会員登録時にキャッチコピーや優良情報を表示し、会員(友人)からの紹介リンクであることがユーザーに分かりやすくして、登録成功率を高めたいと考えましたので追記しました。プロンプトのポップアップ。結果は次のとおりです。

上記の効果を達成したい場合、上位のタグだけでなく、上位の詳細情報も必要となるため、一致する API が必要になります。 viaを通じて上位レベルの詳細情報を提供します。

import axios from 'axios'
import Cookies from 'js-cookie'
const via = new URL(location.href).searchParams.get('via')
if (via) {
    axios.post(`https://app.sitesauce.app/api/affiliate/${encodeURIcomponent(this.via)}`).then(response => {
        Cookies.set('sitesauce_affiliate', response.data, { expires: 30, domain: '.sitesauce.app', secure: true, sameSite: 'lax' })
    }).catch(error => {
        if (!error.response || error.response.status !== 404) return console.log('Something went wrong')
        console.log('Affiliate does not exist. Register for our referral program here: https://app.sitesauce.app/affiliate')
    })
}

URL に encodeURIComponent が表示されます。その役割は、パス トラバーサルの脆弱性から保護することです。 /api/referral/:via にリクエストを送信するときに、誰かがリンク パラメータを悪意を持って変更した場合 (?via=../../logout)、ユーザーはクリック後にログアウトする可能性があります。影響はありませんが、予期せぬ影響をもたらす他の操作が行われることは避けられません。

Sitesauce では Alpine を使用している箇所があるため、これをベースにポップアップ ウィンドウを拡張性の高い Alpine コンポーネントに変更しました。私の変換が適切に機能しなかったときに貴重なアドバイスをくれた Ryan に感謝します。

<div x-data="{ ...component() } x-cloak x-init="init()">
    <template x-if="affiliate">
        <div>
            <img :src="affiliate.avatar" class="h-8 w-8 rounded-full mr-2">
            <p>Your friend <span x-text="affiliate.name"></span> has invited you to try Sitesauce</p>
            <button>Start your trial</button>
        </div>
    </template>
</div>
<script>
import axios from &#39;axios&#39;
import Cookies from &#39;js-cookie&#39;
// 使用模板标签 $nextTick ,进行代码转换,这里特别感谢下我的朋友 Ryan 
window.component = () => ({
    affiliate: null,
    via: new URL(location.href).searchParams.get(&#39;via&#39;)
    init() {
        if (! this.via) return this.$nextTick(() => this.affiliate = Cookies.getJSON(&#39;sitesauce.affiliate&#39;))
        axios.post(`https://app.sitesauce.app/api/affiliate/${encodeURIComponent(this.via)}`).then(response => {
            this.$nextTick(() => this.affiliate = response.data)
            Cookies.set(&#39;sitesauce.affiliate&#39;, response.data, {
                expires: 30, domain: &#39;.sitesauce.app&#39;, secure: true, sameSite: &#39;lax&#39;
            })
        }).catch(error => {
            if (!error.response || error.response.status !== 404) return console.log(&#39;Something went wrong&#39;)
            console.log(&#39;Affiliate does not exist. Register for our referral program here: https://app.sitesauce.app/affiliate&#39;)
        })
    }
})
</script>

次に、有効なデータを取得できるように API を改善します。さらに、既存のデータベースにいくつかのフィールドを追加する必要があります。これについては後で説明します。以下は移行ファイルです:

class AddAffiliateColumnsToUsersTable extends Migration
{
    /**
     * 迁移执行
     *
     * @return void
     */
    public function up()
    {
        Schema::table(&#39;users&#39;, function (Blueprint $table) {
            $table->string(&#39;affiliate_tag&#39;)->nullable();
            $table->string(&#39;referred_by&#39;)->nullable();
            $table->string(&#39;paypal_email&#39;)->nullable();
            $table->timestamp(&#39;cashed_out_at&#39;)->nullable();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table(&#39;users&#39;, function (Blueprint $table) {
            $table->dropColumn(&#39;affiliate_tag&#39;, &#39;referred_by&#39;, &#39;paypal_email&#39;, &#39;cashed_out_at&#39;);
        });
    }
}

ルーティング カスタム キー名関数 (Laravel 7.X で使用可能) を介してパラメーター バインディングを実装し、API ルーティングを構築します。

Route::post(&#39;api/affiliate/{user:affiliate_tag}&#39;, function (User $user) {
    return $user->only(&#39;id&#39;, &#39;name&#39;, &#39;avatar&#39;, &#39;affiliate_tag&#39;);
})->middleware(&#39;throttle:30,1&#39;);

登録操作を開始する前に、まず Cookie を読み取り、暗号化されていないため、EncryptCookies の除外フィールドに追加して除外する必要があります。

// 中间件:app/Http/Middleware/EncryptCookies.php
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
    /**
    * The names of the cookies that should not be encrypted
    *
    * @var array
    */
    protected $except = [
        &#39;sitesauce_referral&#39;,
    ];
}

次に、RegisterController の認証されたメソッドを通じて登録に対応するロジックを実行します。この間、上記のメソッドを通じて Cooke を取得し、Cooke を通じて対応する上位を見つけ、最終的に下位と上位を関連付けます。 。

/**
 * 上级用户已经注册
 *
 * @param \Illuminate\Http\Request $request
 * @param \App\User $user
 */
protected function registered(Request $request, User $user)
{
    if (! $request->hasCookie(&#39;sitesauce.referral&#39;)) return;
    $referral = json_decode($request->cookie(&#39;sitesauce_referral&#39;), true)[&#39;affiliate_tag&#39;];
    if (! User::where(&#39;affiliate_tag&#39;, $referral)->exists()) return;
    $user->update([
        &#39;referred_by&#39; => $referral,
    ]);
}

また、下位ユーザーのリストを取得するメソッドも必要です。これは、ORM の hasMany メソッドを使用して簡単に実現できます。

class User extends Model
{
    public function referred()
    {
        return $this->hasMany(self::class, &#39;referred_by&#39;, &#39;affiliate_tag&#39;);
    }
}

さて、登録ページを作成しましょう。ユーザーが登録するとき、設定タブを選択し、その後の出金操作のために PayPal メールアドレスを提供するよう要求できます。以下は効果のプレビューです:

メンバー登録後、ラベル変更のために電子メール アドレスと関連する入り口の変更も提供する必要があります。ラベルを変更した後、その下位ユーザーのラベルをカスケード更新して、2 つのラベルを確実に統一する必要があります。これにはデータベース トランザクション操作が含まれます。操作のアトミック性を確保するには、トランザクション内で上記 2 つの操作を完了する必要があります。コードは次のとおりです:

public function update(Request $request)
    {
        $request->validate([
            &#39;affiliate_tag&#39; => [&#39;required&#39;, &#39;string&#39;, &#39;min:3&#39;, &#39;max:255&#39;, Rule::unique(&#39;users&#39;)->ignoreModel($request->user())],
            &#39;paypal_email&#39; => [&#39;required&#39;, &#39;string&#39;, &#39;max:255&#39;, &#39;email&#39;],
        ]);
        DB::transaction(function () use ($request) {
            if ($request->input(&#39;affiliate_tag&#39;) != $request->user()->affiliate_tag) {
                User::where(&#39;referred_by&#39;, $request->user()->affiliate_tag)
                    ->update([&#39;referred_by&#39; => $request->input(&#39;affiliate_tag&#39;)]);
            }
            $request->user()->update([
                &#39;affiliate_tag&#39; => $request->input(&#39;affiliate_tag&#39;),
                &#39;paypal_email&#39; => $request->input(&#39;paypal_email&#39;),
            ]);
        });
        return redirect()->route(&#39;affiliate&#39;);
    }

次に、メンバーの収入の計算方法を決定する必要がありますが、下位ユーザーの全消費額に対する割合を手数料として上位ユーザーに還元することができます。計算では、最終決済日以降のデータのみを計算します。計算をシンプルかつ簡単にするために、拡張機能 Mattias のパーセント パッケージを使用します。

use Mattiasgeniar\Percentage\Percentage;
const COMMISSION_PERCENTAGE = 20;
public function getReferralBalance() : int
{
    return Percentage::of(static::COMISSION_PERCENTAGE,
        $this->referred
            ->map(fn (User $user) => $user->invoices(false, [&#39;created&#39; => [&#39;gte&#39; => optional($this->cashed_out_at)->timestamp]]))
            ->flatten()
            ->map(fn (\Stripe\Invoice $invoice) => $invoice->subtotal)
            ->sum()
    );
}

最後に、月次決済の形で上長にコミッションを分配し、Cashed_out_atフィールドをコミッション分配時刻に更新します。結果は次のとおりです。

現時点では、上記のドキュメントがお役に立てば幸いです。毎日あなたの幸せを祈っています。

推奨チュートリアル: 「Laravel チュートリアル

以上がLaravelをベースとした会員配信システムの開発の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlearnku.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。