Maison  >  Article  >  cadre php  >  Développer un système de distribution des membres basé sur Laravel

Développer un système de distribution des membres basé sur Laravel

Guanhui
Guanhuiavant
2020-05-28 10:16:323582parcourir

Développer un système de distribution des membres basé sur Laravel

Récemment, un nouveau système d'adhésion a été ajouté à la base existante de Sitesauce, et j'ai écrit cet article sur les détails spécifiques de mise en œuvre.

Remarque : je vais créer ce programme à partir de zéro afin que, quelle que soit l'étape à laquelle vous vous trouvez, vous puissiez lire l'article. Bien sûr, si vous connaissez déjà très bien Laravel, vous pouvez le faire sur une plateforme comme Rewardful, ce qui vous fera gagner beaucoup de temps.

Afin de clarifier le concept, dans le texte suivant, l'invitant est remplacé par supérieur, et l'invité est remplacé par subordonné

Tout d'abord, nous devons clarifier nos besoins : premièrement, les utilisateurs peuvent partager des invitations par lien via le programme. Pour l'enregistrement d'amis, les invités peuvent s'inscrire via le lien pour lier la relation d'invitation. Deuxièmement, lorsque le subordonné consomme, le supérieur peut recevoir la commission correspondante.

Nous devons maintenant déterminer comment mettre en œuvre l'enregistrement. J'avais initialement prévu d'utiliser la méthode de Fathom. Tant que l'utilisateur est dirigé vers une page spécifique, l'utilisateur sera marqué comme une page de référence spéciale. Une fois l'enregistrement terminé, la relation sera liée. Mais finalement, l’approche de Rewardful a été adoptée, en ajoutant le paramètre ?via=miguel au lien pour construire la page de recommandation.

D'accord, créons maintenant notre page d'inscription. Sur la page d'inscription, le programme correspondra au supérieur via le paramètre de lien via. Le code est simple, si via existe alors stockez-le dans un cookie pendant 30 jours, puisque nous avons plusieurs sous-domaines différents qui en ont besoin, nous l'ajoutons sous le domaine principal afin que tous les sous-domaines puissent utiliser le cookie. Le code spécifique est le suivant :

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'
    })
}

L'avantage de ceci est que lorsqu'un membre ne s'inscrit pas via ce partage, mais s'inscrit par lui-même par la suite, vous pouvez clairement savoir que le membre est venu grâce à la recommandation de ce supérieur . Je souhaite aller plus loin et afficher des slogans et des informations supérieures lorsqu'un nouveau membre s'inscrit, afin que l'utilisateur sache clairement qu'il s'agit d'un lien de parrainage d'un membre (ami), augmentant ainsi le taux de réussite de l'inscription, j'ai donc ajouté une fenêtre contextuelle d'invite. L'effet est le suivant :

Si nous voulons obtenir l'effet ci-dessus, nous avons maintenant besoin non seulement de la balise de niveau supérieur, mais également des informations détaillées de niveau supérieur, nous avons donc besoin d'une API qui correspondra et fournir des informations détaillées de niveau supérieur via 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')
    })
}

Dans l'URL que vous pouvez voir encodeURIComponent, son rôle est de nous protéger de la vulnérabilité Path Traversal. Lorsque nous envoyons une requête à /api/referral/:via, si quelqu'un modifie de manière malveillante les paramètres du lien, comme ceci : ?via=../../logout, l'utilisateur peut se déconnecter après avoir cliqué. aucun impact. Mais il est inévitable qu’il y ait d’autres opérations qui apporteront des effets inattendus.

Étant donné que Sitesauce utilise Alpine à certains endroits, nous avons modifié la fenêtre contextuelle en un composant Alpine basé sur celui-ci, qui a une meilleure évolutivité. Je tiens à remercier Ryan de m'avoir donné de précieux conseils lorsque ma conversion ne fonctionnait pas correctement.

<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>

Maintenant, améliorez l'API afin qu'elle puisse obtenir des données valides. De plus, nous devons également ajouter plusieurs champs à notre base de données existante, que nous expliquerons plus tard. Voici le fichier de migration :

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;);
        });
    }
}

Implémentez la liaison des paramètres via la fonction de nom de clé personnalisée de routage (peut être utilisée sur Laravel 7.X) pour créer notre routage 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;);

Avant de commencer l'opération d'enregistrement, lisez d'abord notre Cookie Comme il n'est pas crypté, nous devons l'ajouter au champ except de EncryptCookies pour l'exclure.

// 中间件: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;,
    ];
}

Exécutons maintenant la logique d'enregistrement correspondante via la méthode authentifiée de RegisterController. Pendant cette période, nous obtenons le Cooke via la méthode ci-dessus, et trouvons le supérieur correspondant via le Cooke, et enfin associons le subordonné au supérieur. .

/**
 * 上级用户已经注册
 *
 * @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,
    ]);
}

Nous avons également besoin d'une méthode pour obtenir la liste de mes utilisateurs subordonnés, ce qui est facilement réalisé grâce à la méthode hasMany d'ORM.

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

Maintenant, construisons notre page d'inscription. Lorsque les utilisateurs s'inscrivent, ils peuvent sélectionner l'onglet préférences et leur demander de fournir leur e-mail PayPal pour les opérations de retrait ultérieures. Voici un aperçu de l'effet :

Après l'inscription du membre, nous devons également fournir le changement d'adresse e-mail et l'entrée correspondante pour le changement d'étiquette. Après avoir modifié son étiquette, nous devons mettre à jour en cascade les étiquettes de ses utilisateurs subordonnés pour garantir l'unité des deux. Cela implique des opérations de transaction de base de données. Pour garantir l'atomicité de l'opération, nous devons effectuer les deux opérations ci-dessus dans la transaction. Le code est le suivant :

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;);
    }

Ensuite, nous devons déterminer la méthode de calcul des revenus des membres. Le pourcentage de tous les montants de consommation des utilisateurs de niveau inférieur peut être restitué au supérieur sous forme de commission. calcul, nous calculons uniquement depuis la dernière date de règlement. Nous utilisons une extension du package de pourcentages de Mattias pour rendre les calculs simples et directs.

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()
    );
}

Enfin, nous distribuons la commission au supérieur sous forme de règlement mensuel, et mettons à jour le champ cashed_out_at avec l'heure de distribution de la commission. L'effet est le suivant :

À ce stade, j'espère que les documents ci-dessus vous seront utiles. Je te souhaite du bonheur chaque jour.

Tutoriel recommandé : "Tutoriel Laravel"

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer