首頁 > php框架 > Laravel > 主體

基於 Laravel 開發會員分銷系統

Guanhui
發布: 2020-05-28 10:16:32
轉載
3611 人瀏覽過

基於 Laravel 開發會員分銷系統

最近,在 Sitesauce 現有基礎上新增會員系統,就具體實作細節寫了這篇文章。

筆記:我將從零開始建立該程序,這樣無論你處於什麼階段都可以讀懂該文章。當然如果你已經非常熟悉 Laravel ,你可以在此 Rewardful 之類的平台上完成該工作,這會節省不少時間。

為了概念明確,下文中邀請者用上級替代,被邀請者使用下級替代

首先,我們要明確我們的需求:首先使用者可以透過程式分享連結邀請好友註冊,被邀請者可以透過連結註冊,從而綁定邀請關係,其次在下級消費的時候,上級都可以獲得相應的佣金。

現在我們要確定如何實現註冊。我原本打算使用 Fathom 的方法,只要將使用者引導到特定頁面,該使用者將會被標記為 special referral page ,待使用者完成註冊,並將關係綁定。但最終採用的是 Rewardful 的做法,透過向連結添加參數 ?via=miguel 來實現推薦頁面的建置。

好了,現在讓我們建立我們的註冊頁面,在註冊頁面程式會透過連結參數 via 來匹配上級。代碼很簡單,如果 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,該 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,他的角色是保護我們不在受 Path Traversal vulnerability 的影響。在我們發送請求到/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 的 except 欄位中,將其排除。

// 中间件: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 的 authenticated 方法執行註冊對應的邏輯,其間,我們透過上面的方式取得 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 電子郵件 以以便之後的提現操作。以下是效果預覽:

會員註冊後,我們還需要提供電子郵件信箱的變更,以及標籤變更的相關入口。在其變更標籤之後,我們需要級聯更新其下級用戶的標籤,以確保兩者的統一。這牽涉到了資料庫的事務操作,為確保操作的原子性,我們需要在事務中完成以上兩個操作。代碼如下:

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’ percentages package 讓計算簡單明了。

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中文網其他相關文章!

相關標籤:
來源:learnku.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!