代码之家  ›  专栏  ›  技术社区  ›  Jesse Orange

带条纹的一次性收费100%折扣

  •  2
  • Jesse Orange  · 技术社区  · 5 年前

    在我的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

    然后它将新值添加到会话中,我在条带费用中使用它。

    问题

    问题是条纹的最小可充量是 0.05 ,所以为了避免这个问题,我刚刚调用了一个升级帐户的方法。

    理论上,我应该把免费升级存储在 charges 但我最终会得到多个空值。

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

    User 模型I也有以下方法:

    /**
     * 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 回复  |  直到 5 年前