使用Stripe一次性充值100%折扣[Laravel]

时间:2019-02-07 14:47:48

标签: php laravel stripe-payments

在我的Laravel应用程序中,我有一个页面,用户必须支付150英镑的会员费。为了处理这笔付款,我选择了Stripe。

我将所有费用以及用户的ID都存储在付款表中。

付款表

Schema::create('payments', function (Blueprint $table) {
    $table->increments('id');
    $table->uuid('user_id');
    $table->string('transaction_id');
    $table->string('description');
    $table->string('amount');
    $table->string('currency');
    $table->datetime('date_recorded');
    $table->string('card_brand');
    $table->string('card_last_4', 4);
    $table->string('status');
    $table->timestamps();
});

我还实现了自己的凭证系统,因为我不使用订阅。

优惠券表

Schema::create('vouchers', function (Blueprint $table) {
    $table->increments('id');
    $table->string('code');
    $table->integer('discount_percent');
    $table->dateTime('expires_on');
    $table->timestamps();
});

付款控制器

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Carbon\Carbon;
use App\User;
use App\Payment;
use App\Voucher;
use App\Mail\User\PaymentReceipt;
use App\Mail\Admin\UserMembershipPaid;
use Log;
use Mail;
use Validator;
use Stripe;
use Stripe\Error\Card;

class PaymentController extends Controller
{
    /**
     * Set an initial amount to be used by the controller
     *
     * @var float
     */
    private $amount = 150.00;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
        $this->middleware('verified');
        $this->middleware('is_investor');
        $this->middleware('is_passive_member');
    }

    /**
     * Display a form allowing a user to make a payment
     *
     * @return void
     */
    public function showPaymentForm()
    {
        return view('user.payment');
    }

    /**
     * Handle an entered voucher code by the user
     * Either calculate a discount or skip the payment form 
     *
     * @param [type] $request
     * @return void
     */
    public function processVoucher(Request $request)
    {
        $rules = [
            'code' => 'required|exists:vouchers',
        ];

        $messages = [
            'code.required' => 'You submitted a blank field',
            'code.exists' => 'This voucher code is not valid'
        ];

        Validator::make($request->all(), $rules, $messages)->validate();

        $entered_voucher_code = $request->get('code');

        $voucher = Voucher::where('code', $entered_voucher_code)->where('expires_on', '>', Carbon::now())->first();

        // If the voucher exists
        if ($voucher) {
            $discount_percent = $voucher->discount_percent;
            $new_amount = $this->amount - ($discount_percent / 100 * $this->amount);

            // As Stripe won't handle charges of 0, we need some extra logic
            if ($new_amount <= 0.05) {
                $this->upgradeAccount(auth()->user());

                Log::info(auth()->user()->log_reference . " used voucher code {$voucher->code} to get a 100% discount on their Active membership");

                return redirect()->route('user.dashboard')->withSuccess("Your membership has been upgraded free of charge.");
            }
            // Apply the discount to this session 
            else {
                Log::info(auth()->user()->log_reference . " used voucher code {$voucher->code} to get a {$voucher->discount_percent}% discount on their Active membership");

                // Store some data in the session and redirect
                session(['voucher_discount' => $voucher->discount_percent]);
                session(['new_price' => $this->amount - ($voucher->discount_percent / 100) * $this->amount]);

                return redirect()->back()->withSuccess([
                    'voucher' => [
                        'message' => 'Voucher code ' . $voucher->code . ' has been applied. Please fill in the payment form',
                        'new_price' => $new_amount
                    ]
                ]);
            }
        }
        // Voucher has expired
        else {
            return redirect()->back()->withError('This voucher code has expired.');
        }
    }

    /**
     * Handle a Stripe payment attempt from the Stripe Elements form
     * Takes into account voucher codes if they are less than 100%
     *
     * @param Request $request
     * @return void
     */
    public function handleStripePayment(Request $request)
    {
        // Retreive the currently authenticated user
        $user = auth()->user();

        // Get the Stripe token from the request
        $token = $request->get('stripeToken');

        // Set the currency for your country
        $currency = 'GBP';

        // Set an initial amount for Stripe to use with the charge
        $amount = $this->amount;

        // A description for this payment
        $description = "Newable Private Investing Portal - Active Membership fee";

        // Initialize Stripe with given public key
        $stripe = Stripe::make(config('services.stripe.secret'));

        // Attempt a charge via Stripe
        try {
            Log::info("{$user->log_reference} attempted to upgrade their membership to Active");

            // Check that token was sent across, if it wasn't, stop
            if (empty($token)) {
                return redirect()->back()->withErrors([
                    'error' => "Token error, do you have JavaScript disabled?"
                ]);
            }

            // Check whether a discount should be applied to this charge
            if (session()->has('voucher_discount')) {
                $discount_percentage = session()->pull('voucher_discount');

                $discount = ($discount_percentage / 100) * $amount;

                $amount = $amount - $discount;

                session()->forget('new_price');
            }

            // Create a charge with an idempotent id to prevent duplicate charges
            $charge = $stripe->idempotent(session()->getId())->charges()->create([
                'amount' => $amount,
                'currency' => $currency,
                'card' => $token,
                'description' => $description,
                'statement_descriptor' => 'Newable Ventures',
                'receipt_email' => $user->email
            ]);

            //If the payment is successful, store the payment, send some emails and upgrade this user
            if ($charge['status'] == 'succeeded') {
                $this->storePayment($charge);

                Mail::send(new PaymentReceipt($user));
                Mail::send(new UserMembershipPaid($user));

                $this->upgradeAccount($user);

                return redirect()->route('user.dashboard')->withSuccess("Your payment was successful, you will soon recieve an email receipt.");
            // If the payment was unsuccessful
            } else {
                $this->storePayment($charge);

                Log::error("Stripe charge failed for {$user->log_reference}");

                return redirect()->back()->withErrors([
                    'error' => "Unfortunately, your payment was unsuccessful."
                ]);
            }
        } catch (Exception $e) {
            Log::error("Error attempting Stripe Charge for {$user->log_reference} - Exception - error details {$e->getMessage()}");

            return redirect()->back()->withErrors([
                'error' => $e->getMessage()
            ]);
        } catch (\Cartalyst\Stripe\Exception\MissingParameterException $e) {
            Log::error("Error attempting Stripe Charge for {$user->log_reference} - MissingParameterException - error details {$e->getMessage()}");

            return redirect()->back()->withErrors([
                'error' => $e->getMessage()
            ]);
        } catch (\Cartalyst\Stripe\Exception\CardErrorException $e) {
            Log::error("Error attempting Stripe Charge for {$user->log_reference} - CardErrorException - error details {$e->getMessage()}");

            return redirect()->back()->withErrors([
                'error' => $e->getMessage()
            ]);
        } catch (\Cartalyst\Stripe\Exception\ApiLimitExceededException $e) {
            Log::error("Error attempting Stripe Charge for {$user->log_reference} - ApiLimitExceededException - error details {$e->getMessage()}");

            return redirect()->back()->withErrors([
                'error' => $e->getMessage()
            ]);
        } catch (\Cartalyst\Stripe\Exception\BadRequestException $e) {
            Log::error("Error attempting Stripe Charge for {$user->log_reference} - BadRequestException -  error details {$e->getMessage()}");

            return redirect()->back()->withErrors([
                'error' => $e->getMessage()
            ]);
        } catch (\Cartalyst\Stripe\Exception\ServerErrorException $e) {
            Log::error("Error attempting Stripe Charge for {$user->log_reference} - ServerErrorException - error details: {$e->getMessage()}");

            return redirect()->back()->withErrors([
                'error' => $e->getMessage()
            ]);
        } catch (\Cartalyst\Stripe\Exception\UnauthorizedException $e) {
            Log::error("Error attempting Stripe Charge for {$user->log_reference} - UnauthorizedException - error details: {$e->getMessage()}");

            return redirect()->back()->withErrors([
                'error' => $e->getMessage()
            ]);
        }
    }

    /**
     * Store a Stripe chargee in our database so we can reference it later if necessary
     * Charges stored against users for cross referencing and easy refunds
     *
     * @return void
     */
    private function storePayment(array $charge)
    {
        $payment = new Payment();

        $payment->transaction_id = $charge['id'];
        $payment->description = $charge['description'];
        $payment->amount = $charge['amount'];
        $payment->currency = $charge['currency'];
        $payment->date_recorded = Carbon::createFromTimestamp($charge['created']);
        $payment->card_brand = $charge['source']['brand'];
        $payment->card_last_4 = $charge['source']['last4'];
        $payment->status = $charge['status'];

        auth()->user()->payments()->save($payment);

        if ($payment->status === "succeeded") {
            Log::info("Successful Stripe Charge recorded for {$user->log_reference} with Stripe reference {$payment->transaction_id} using card ending {$payment->card_last_4}");
        } else {
            Log::info("Failed Stripe Charge recorded for {$user->log_reference} with Stripe reference {$payment->transaction_id} using card ending {$payment->card_last_4}");
        }
    }

    /**
     * Handle a user account upgrade from whatever to Active
     *
     * @param User $user
     * @return void
     */
    private function upgradeAccount(User $user)
    {
        $current_membership_type = $user->member_type;

        $user->member_type = "Active";

        $user->save();

        Log::info("{$user->log_reference} has been upgraded from a {$current_membership_type} member to an Active Member.");
    }
}

processVoucher()接受用户输入的字符串,检查其是否存在于vouchers表中,然后将折扣百分比应用于费用150.00

然后它将新值添加到会话中,然后在Stripe Charge中使用它。

问题

问题在于,Stripe的最低可计费金额为0.05,因此,为了避免发生此问题,我刚刚调用了一种升级帐户的方法。

从理论上讲,我应该将免费升级存储在charges表中,但最终会有多个空值。

这是一个可怕的解决方案吗?

User模型中,我还有以下方法:

/**
 * Relationship to payments
 */
public function payments()
{
    return $this->hasMany(Payment::class, 'user_id', 'id');
}

/**
 * Relationship to payments to get most recent payment
 *
 * @return void
 */
public function latest_payment()
{
    return $this->hasOne(Payment::class, 'user_id', 'id')->latest();
} 

使用这些是因为我可以计算用户上一次付款的时间,因为我需要每年结算而不使用订阅,因为用户还可以使用100%的优惠券进行升级。

我做了以下控制台命令:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

use Carbon\Carbon;
use App\User;
use App\Payment;
use Log;

class ExpireMembership extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'membership:expire';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Expire user memberships after 1 year of being Active.';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        //Retrieve all users who are an active member with their list of payments
        $activeUsers = User::where('member_type', 'Active')->get();

        //Get current date
        $current_date = Carbon::now();

        foreach($activeUsers as $user){
            $this->info("Checking user {$user->log_reference}");

            // If a user has at least one payment recorded
            if($user->payments()->exists()){
                //Get membership end date (latest payment + 1 year added)
                $membership_end_date = $user->payments
                    ->where('description', 'Newable Private Investing Portal - Active Membership fee')
                    ->sortByDesc('created_at')
                    ->first()->created_at->addYear();
            }
            // If the user has no payments but is an active member just check if they're older than a year
            else{
                $membership_end_date = $user->created_at->addYear();
            }
            //If the membership has gone over 1 year, expire the membership.
            if ($current_date->lessThanOrEqualTo($membership_end_date)) {
                $user->member_type = "Passive";
                $user->save();

                $this->info($user->log_reference . "membership has expired and membership status has been set to Passive.");

                Log::info($user->log_reference . "membership has expired and membership status has been set to Passive.");

            }
        }

        $this->info("Finished checking user memberships.");

    }
}

使用代金券的用户没有付款,因此弄清楚何时自动开具账单是很棘手的。

0 个答案:

没有答案