JIRA is an antipattern

Atlassian’s JIRA began life as a bug-tracking tool. Today, though, it has become an agile planning suite, “to plan, track, and release great software.” In many organizations it has become the primary map of software projects, the hub of all development, the infamous “source of truth.”

It is a truism that the map is not the territory. Alas, this seems especially true of JIRA. Its genesis as a bug tracker, and its resulting use of “tickets” as its fundamental, defining unit, have made its maps especially difficult to follow. JIRA1 is all too often used in a way which makes it, inadvertently, an industry-wide “antipattern,” i.e. “a common response to a recurring problem that is usually ineffective and risks being highly counterproductive.”

One thing that writing elegant software has in common with art: its crafters should remain cognizant of the overall macro vision of the project, at the same time they are working on its smallest micro details. JIRA, alas, implicitly teaches everyone to ignore the larger vision while focusing on details. There is no whole. At best there is an “Epic” — but the whole point of an Epic is to be decomposed into smaller pieces to be worked on independently. JIRA encourages the disintegration of the macro vision.

What’s more, feature-driven JIRA does not easily support the concept of project-wide infrastructure which does not map to individual features. A data model used across the project. A complex component used across multiple pages. A caching layer for a third-party interface. A background service providing real-time data used across multiple screens. Sure, you can wedge those into JIRA’s ticket paradigm … but the spiderweb of dependencies which result don’t help anyone.

Worst of all, though, is the endless implicit pressure for tickets to be marked finished, to be passed on to the next phase. Tickets, in the JIRA mindset, are taken on, focused on until complete, and then passed on, never to be seen again. They have a one-way lifecycle: specification; design; development; testing; release. Doesn’t that sound a little … um … waterfall-y? Isn’t agile development supposed to be fundamentally different from waterfall development, rather than simply replacing one big waterfall with a thousand little ones?

Here’s an analogy. Imagine a city-planning tool which makes it easy to design city maps which do include towers, residential districts, parks, malls, and roads … but which doesn’t easily support things like waterworks, sewers, subway tunnels, the electrical grid, etc., which can only be wedged in through awkward hacks, if at all.

Now imagine this tool is used as a blueprint for construction, with the implicit baked-in assumption that a) the neighborhood is the fundamental unit of city construction b) cities are built one neighborhood at a time, and neighborhoods one block at a time. What’s more, one is incentivized to proceed to the next only when the last is absolutely complete, right down to the flowers growing in the median strips.

Now imagine that the city’s developers, engineers, and construction workers are asked to estimate and report progress purely in terms of how many neighborhoods and blocks have been fully completed, and how far along each one is. Does that strike you as a particularly effective model of urban planning? Do you think you would like to live in its result? Or, in practice, do you think that the best way to grow a city might be just a little more organic?

Let’s extend that metaphor. Suppose you began to build the city more organically, so that, at a certain significant point, you have a downtown full of a mix of temporary and permanent buildings; the skyscrapers’ foundations laid (i.e. technical uncertainty resolved); much of the core infrastructure built out; a few clusters of initial structures in the central neighborhoods, and shantytowns in the outskirts; a dirt airstrip where the airport will be; and traffic going back and forth among all these places. In other words, you have built a crude but functioning city-in-the-making, its skeleton constructed, ready to be fleshed out. Well done!

But if measured by how many blocks and neighborhoods are absolutely finished, according to the urban planners’ artistic renditions, what is your progress? By that measure, your progress is zero.

So that is not how JIRA incentivizes you to work. That would look like a huge column of in-progress tickets, and zero complete ones. That would look beyond terrible. Instead JIRA incentivizes you to complete an entire block, and then the next; an entire neighborhood, and then the next; to kill off as many different tickets as possible, to mark them complete and pass them on, even if splicing them together after the fact is more difficult than building them to work together in the first place,.

(If you prefer a smaller-scale model, just transpose: city → condo building, neighborhood → floor, block → unit, etc.)

And so people take tickets, implement them as written, pass them off to whoever is next in the workflow, consider their job well done, even if working on scattered groups of them in parallel might be much more effective … and without ever considering the larger goal. “Implement the Upload button” says the ticket; so that is all that is done. The ticket does not explain that the larger goal of the Upload button is to let users back up their work. Perhaps it would actually be technically easier to automatically upload every state change, such that the user gets automatic buttonless backups plus a complete undo/redo stack. But all the ticket says is: “Implement the Upload button.” So that is all that is done.

All too often, the only time anyone worries about the vision of the project as a whole is at the very beginning, when the overworked project manager(s) initially deal(s) with the thankless task of decomposing the entire project into a forest of tickets. But the whole point of agile development is to accept that the project will always be changing over time, and — albeit to a lesser extent — for multiple people, everyone on the team, to help contribute to that change. JIRA has become a tool which actually works against this.

(And don’t even get me started on asking engineers to estimate a project that someone else has broken down, into subcomponents whose partitioning feels unnatural, by giving them about thirty seconds per feature during a planning meeting, and then basing the entire project plan on those hand-waved un-researched off-the-top-of-the-head half-blind guesses, without ever revisiting them or providing time for more thoughtful analysis. That antipattern is not JIRA’s fault … exactly. But JIRA’s structure contributes to it.)

I’m not saying JIRA has no place. It’s very good when you’re at the point where breaking things down into small pieces and finishing them sequentially does make sense. And, unsurprisingly given its history, it’s exceedingly good at issue tracking.

Let me reiterate: to write elegant software, you must keep both the macro and the micro vision in your mind simultaneously while working. JIRA is good at managing micro pieces. But you need something else for the macro. (And no, a clickable prototype isn’t enough; those are important, but they too require descriptive context.)

Allow me to propose something shocking and revolutionary: prose. Yes, that’s right; words in a row; thoughtfully written paragraphs. I’m not talking about huge requirements documents. I’m talking about maybe a ten-page overview describing the vision for the entire project in detail, and a six-page architectural document explaining the software infrastructure — where the city’s water, sewage, power, subways, and airports are located, and how they work, to extend the metaphor. When Amazon can, famously, require six-page memos in order to call meetings, this really doesn’t seem like too much to ask.

Simply ceasing to treat JIRA as the primary map and model of project completion undercuts a great deal of its implicit antipatternness. Use it for tracking iterative development and bug fixes, by all means. It’s very good at that. But it is a tool deeply ill-suited to be the map of a project’s overall vision or infrastructure, and it is never the source of truth — the source of truth is always the running code. In software, as in art, the micro work and the macro vision should always be informed by one another. Let JIRA map the micro work; but let good old-fashioned plain language describe the macro vision, and try to pay more attention to it.


1Atlassian seems to have decapitalized JIRA between versions 7.9 and 7.10, but descriptively, all-caps still seems more common.


via TechCrunch
JIRA is an antipattern

The Totally Free Streaming Service You Didn’t Know You Have


Lovers of classic films have been set adrift this month after the Filmstruck streaming service was unceremoniously murdered by corporate ghouls. But friends, I want to tell you about a free streaming service that’s in many ways just as good as Filmstruck, offers Criterion films, and has at least one feature that no one else does.

Kanopy is not new, it got its start in Australia a decade ago and has slowly expanded its services around the globe. Its business model is to partner with universities and public libraries which then offer students and members the ability to stream the service’s library of 30,000 films for free. Some 4,000 public libraries worldwide use the service, so there’s a decent chance you have access through your local system. Why haven’t you been doing this? I don’t know. Why haven’t I? I couldn’t really tell you! But it’s very good.

I think the biggest reason Kanopy isn’t a household name is that association with libraries. Public libraries are great and offer all kinds of digital options that you may not be aware of. So if you don’t have a library card that’s the first step to signing up. In New York City, where I’m located, you can sign up for a library card online. It’s been a while since my last library visit and getting set up on Kanopy meant that I had to revisit an old feeling from the Blockbuster Video days—cleaning up late fees. It did not inspire even the mildest hint of nostalgia. But there will be no late fees for your streaming, so stay with me.

Once your library card situation is in order, you just go to Kanopy’s website or download its app and sign up. You’ll have to choose your local library, enter your card number, and verify an email. It’s easy-peasy.

You’re still not doing yet, are you? You’re still thinking this sounds like a nice thing that you’ll get around to later and you’re going to go home later only to aimlessly scroll through the same watchlist on Netflix that you’ve been staring at for five years. I urge you to just take the plunge.

I subscribe to pretty much every streaming service under the sun and use PS Vue for cable. But logging into Kanopy I immediately saw selections that either don’t pop-up in my regularly scheduled, algorithmically-generated hellscape. There’s a boatload of classic films like Metropolis and Sunset Boulevard for the Filmstruck crowd. There are 50 movies from the Criterion Collection to tide you over until its new streaming service starts next spring. And there are a lot of new flicks spanning all genres.

We don’t know when Apple’s long-delayed streaming service will debut but last month it made a big deal out of its new agreement to produce flicks with the hot indie studio A24. Right this second, you can watch A24’s back catalog on Kanopy, including films like Ex Machina and current awards-competitor First Reformed for free.

Maybe you have kids. Do you have any idea what they’re watching on YouTube? I swear to you it isn’t good. Kanopy has a whole kids section with old and new classics that do not feature a CG doctor impregnating Spider-Man with a syringe.

What many would consider Kanopy’s only flaw is what I believe to be the best thing that sets it apart. You have a limit of how many films you can watch in a month and a limit of how long you have to finish a given film once you’ve started. Kanopy gets paid by its institutional partners for each film that a member streams. The university or library set their own streaming limits for members and can adjust them based on increased activity or budget changes. The Brooklyn Public Library gives me a limit of six films per month and I have three days to finish a film once I’ve started. The counter restarts on the first of every month.

Kanopy isn’t a Netflix killer, it’s a nice additional service. Of course, if you don’t subscribe to any streaming channels, it’s a great option. But I like that you need to think about what you want to watch. Instead of getting 15 minutes into something and changing your mind, maybe you’ll stick it out. If you fell asleep halfway through a film, maybe you’ll be sure to finish it the next day. In other words, it’s much closer to the old video store experience when you actually cared about your choices.


via Gizmodo
The Totally Free Streaming Service You Didn’t Know You Have

Happy Hanukkah 2018


Happy Hanukkah! The story that celebrates the victory of the Maccabees, a Jewish Priestly family from Modiin that aren’t Israeli, didn’t live in Israel and they defeated the Greek empire that wanted to force them to abandon their faith, their heritage and their G-d.

If I were going to write the story today I would cast Jewish Voices for Peace, J street, If Not Now, B’Tselem, and leftist Jews as the Hellenistic Jews of the Hanukkah story. Because they work against Israel and those that want to live life according to Torah. It’s kind of interesting to me, I recently took a class called “Wrestling With Faith” in it we talked about the Mitzvot. The 613 little holy bread crumbs G-d gave to leave a path for us to follow to find him. You want to get to know someone? You ask him or her questions about themselves, and sometimes getting that information is like pulling teeth. G-d plays no such games with us. He says these things I like, these things I don’t like. You want to make me smile? You want to show you are a child who listen to their father? These are house rules. Which kind of baffles me when people tell me they don’t matter anymore. Maybe I just haven’t found the Parasha where it says G-d changed him mind about those. But that was the point of the Hellenization. To separate the Jews from the things that G-d ask. It’s like doing everything the opposite of what your boss, significant other, spouse or coach says you should do and then expecting them to still feel the same way about you. It doesn’t usually play out that way. So Hellenization was meant to turn people from the worship of G-d to the worship of the human body, art, and pagan rituals. They wanted to convince the Jews living in Israel that they didn’t need to observe the new months, circumcision, Torah study, Shabbat observance. They wanted to drive a wedge between the people and G-d.

For more info on this history of Hanukkah and information you’ll find we’ve got quite a few choices.

It’s The Most MACCABEE Time of the Year!

Oh, it’s probably nothing…

#Amona

A Holiday Classic…

Some things are worth fighting for

At This Time

It definitely wasn’t safe to openly practice being a Jew in Israel at that time. As other times down through history different people of different nations have tried to eradicate Jews and their Judaism. It never began with cattle cars, concentration camps and ovens. It began with yellow stars and discrimination. At the end of WWII the world joined us in vowing #NeverAgain, and now there has been a self serving attempt to cheapen that phrase by a money grubbing, attention seeking political pawn who is protected by armed guards even as he oinks out that those that can’t afford paid protection be rendered defenseless. Little swine.

Did we think that would be the final attempt after the final solution? Well, it wasn’t.

Rashida Tlaib to lead congressional trip to Judea and Samaria

One of the two new muslims elected to congress. She is leading a group to Israel, but not to visit Israeli Jews, Arabs, Christians and Druze. Nope, to visit the ancient people (est. 1964) Falestinians. The other muslim Ilhan Omar of Minnesota is also no friend.

Tlaib in the interview also came out in support of the Boycott, Divestment and Sanctions movement against Israel, which she said brings attention to “issues like the racism and the international human rights violations by Israel right now.”

Ilhan Omar of Minnesota, the other Muslim woman elected to Congress in November, said in an interview shortly after the election that she supports the BDS movement after saying during her campaign that it was “counteractive” and prevents dialogue.

What do people not yet accept or understand about “taquia”?

Menorah toppled near Harvard University

South African synagogue goes up in flames guesses on if it’s arson or not?

New Hampshire Town Reject Menorah Display, Citing Vandalism

One might think it’s becoming unsafe in America for Jews to openly admit or display their faith. One would be correct. From wonderful Jack Engelhard in a column titled Americas Hidden Jews. This whole column is well worth reading.

Professor of Jewish and Holocaust studies at Columbia University Elizabeth Midlarsky is about the only one at the college who dares to identify herself openly as Jewish.

Let that sink in.

Remember, this is America. This is New York.

A few days ago she walked into her office to find it vandalized with anti-Semitic graffiti. “They got me,” she told The Washington Post. “I’m afraid.

And then we have Airbnb or AirBS depending on if you ask the company or me what their name is. AirBS is a site where people can list property, apartments or rooms they have to rent to vacationers. They have recently decided parts of Israel aren’t in Israel. So any of the Jewish homes listed in Judea and Samaria, the Biblical heartland, were removed. Not the Falistinian ones mind you, they’re still listed. Just not the Jewish ones. Have they done this in any other part of the world where there is dissension occurring? Why do I bother asking? Of course not. It is flat out anti-semitism. Are they being called out for it. Nope.

Remember in the Hanukkah story how a Hellenized Jew was going to sacrifice a pig on the alter in Modiin since Matthias Maccabee refused to do so? Matthias killed the corrupted Jew, and then the King’s officer that was commanding such a thing. There would be no pigs sacrificed on alters in the Maccabee name. The Hellenized Jews were trying to corrupt the pure ones. They’re still active today.

America’s Jewish left endorses anti-Jewish discrimination

American Jewish peace activists who have spent much of their lives fighting discrimination against ethnic minorities, have decided there is one form of discrimination they support: discrimination against Jews who reside in Judea-Samaria.

Angry At Airbnb For Boycotting The Settlements? Birthright Does The Same Thing.

According to the leftist Jews, that most likely do celebrate Hanukkah, as most all Jews do celebrate Hanukkah, Modiin, land of the Maccabees, IS NOT IN ISRAEL. Nope. Who knew?

I think Daniel Pipes of the Middle-East Forum has the answer to this one. ‘It’s time for Israel to declare victory’

Daniel Pipes is determined to make it clear to the Arabs in Judea and Samaria that in order to survive they must recognize Israel.

The Maccabeean revolt was a two fold war. One, it was incredible military victories, G-d gave many many miracles over the years the battles were fought. The other was the war of faith, religion or culture. However you choose to look at it.

I think I’m going to have to disagree with Lord Rabbi Jonathan Sacks this time. In his Hanukkah message this year he seems to me at least, to believe the great battle was for education, not military battles. I believe without the military battle of G-d through the Maccabees, there would have been no educational victories. To quote Dinesh D’Souza yesterday, he was discussing the difference between George H.W. Bush and President Trump. He said “sometimes you need Mother Teresa, and sometimes you need Dirty Harry”. I would add and sometimes, no one less than Judah Maccabee will do.

My poem to Judah. Twas a Night During Hanukkah

But I will close with a column by our writer Y.B. Ben Avraham, Cleaning the Menorah for Next Year

The graphics he chose are beautiful, but I love the line he closes it with so very much.

Ein Od Milvado

אין עוד מלבדו

There is no one but him.

Happy Hanukkah!

חג חנוכה שמח

Or, for fans of Queen

 

Facebooktwittergoogle_plusredditpinteresttumblrmail


via The Zelman Partisans
Happy Hanukkah 2018

How To Upload File in Laravel 5.7 Using MySQL

This tutorial help to upload files using Laravel 5.7.We will create html form view that upload file into server and save path information into MySQL table. We will use MySQL and php 7 to create file upload functionality. File Upload Using Laravel 5.7 and Mysql Lets create new laravel application using laravel CLI. The Artisan […]

The post How To Upload File in Laravel 5.7 Using MySQL appeared first on Phpflow.com.

via Planet MySQL
How To Upload File in Laravel 5.7 Using MySQL

Contentful raises $33.5M for its headless CMS platform

Contentful, a Berlin- and San Francisco-based startup that provides content management infrastructure for companies like Spotify, Nike, Lyft and others, today announced that it has raised a $33.5 million Series D funding round led by Sapphire Ventures, with participation from OMERS Ventures and Salesforce Ventures, as well as existing investors General Catalyst, Benchmark, Balderton Capital and Hercules. In total, the company has now raised $78.3 million.

It’s been less than a year since the company raised its Series C round and, as Contentful co-founder and CEO Sascha Konietzke told me, the company didn’t really need to raise right now. “We had just raised our last round about a year ago. We still had plenty of cash in our bank account and we didn’t need to raise as of now,” said Konietzke. “But we saw a lot of economic uncertainty, so we thought it might be a good moment in time to recharge. And at the same time, we already had some interesting conversations ongoing with Sapphire [formerly SAP Ventures] and Salesforce. So we saw the opportunity to add more funding and also start getting into a tight relationship with both of these players.”

The original plan for Contentful was to focus almost explicitly on mobile. As it turns out, though, the company’s customers also wanted to use the service to handle its web-based applications and these days, Contentful happily supports both. “What we’re seeing is that everything is becoming an application,” he told me. “We started with native mobile application, but even the websites nowadays are often an application.”

In its early days, Contentful focused only on developers. Now, however, that’s changing, and having these connections to large enterprise players like SAP and Salesforce surely isn’t going to hurt the company as it looks to bring on larger enterprise accounts.

Currently, the company’s focus is very much on Europe and North America, which account for about 80 percent of its customers. For now, Contentful plans to continue to focus on these regions, though it obviously supports customers anywhere in the world.

Contentful only exists as a hosted platform. As of now, the company doesn’t have any plans for offering a self-hosted version, though Konietzke noted that he does occasionally get requests for this.

What the company is planning to do in the near future, though, is to enable more integrations with existing enterprise tools. “Customers are asking for deeper integrations into their enterprise stack,” Konietzke said. “And that’s what we’re beginning to focus on and where we’re building a lot of capabilities around that.” In addition, support for GraphQL and an expanded rich text editing experience is coming up. The company also recently launched a new editing experience.


via TechCrunch
Contentful raises $33.5M for its headless CMS platform

Game of Thrones’ Final Season Video Teases a Song of Ice and Fire


Fire and ice collide.
Image: HBO (YouTube)

Winter may have already arrived around the globe, but next year we’ll also be getting a winter the likes of which we’ve never seen. We’re a few months away from the final season of Game of Thrones, where good will take its last stand against the forces of the undead, and we’ve gotten a gorgeously symbolic teaser for the final battle to come.

Today, showrunners David Benioff and Dan Weiss appeared alongside Maisie Williams (Arya Stark) and John Bradley (Samwell Tarly) at São Paulo Expo in Brazil, an offshoot of San Diego Comic-Con. There, they premiered a new beautiful teaser for the final season. Following that brief three-second glimpse of footage earlier this year, this animation shows the Houses of Westeros freezing and burning on the table at Dragonstone, clashing together in ice and fire. It’s perfectly poetic.

In the final season of Game of Thrones, Jon Snow and Daenerys Targaryen have joined forces to defeat the White Walkers once and for all. But the coming night isn’t the only threat. Cersei Lannister, still ruling as Queen of the Seven Kingdoms, isn’t giving up her Iron Throne that easily. Ice and fire, indeed.

Game of Thrones returns on HBO, one last time, in April 2019.


For more, make sure you’re following us on our new Instagram @io9dotcom.


via Gizmodo
Game of Thrones’ Final Season Video Teases a Song of Ice and Fire

Small Arms Survey Publish Free Guide to the Identification of Small Arms

cover of An Introductory Guide to the Identification of Small Arms, Light Weapons, and Associated Ammunition

Front cover of the new An Introductory Guide to the Identification of Small Arms, Light Weapons, and Associated Ammunition from Small Arms Survey (Small Arms Survey)

The Geneva-based Small Arms Survey have published a free 352 page guide to help with the identification of small arms, light weapons and their ammunition. The new introductory identification guide was written by a raft of subject matter experts and edited by Nic Jenzen-Jones and Matt Schroeder.

The guide includes sections explaining how the international arms trade works before four chapters layout the identifying characteristics and features of small arms, small arms ammunition, light weapons and other improvised weapons.

The Small Arms Survey explain the aims of the new book on its download page:

Arms and ammunition are evidence. Many weapons carry marks that, combined with their physical characteristics, reveal important information about them, including their manufacturer, age, and origin. This information, in turn, provides vital clues about the sources and flows of weapons in the area in which they were found.

The goal of this Handbook is to provide the reader with a basic understanding of how to identify and analyse small arms and light weapons, and to track their proliferation. The process of identifying arms is complex, and no single guide can provide all of the information required to identify every weapon or round of ammunition that may be encountered at crime scenes or in conflict zones. Instead, this guide explains the process by which weapons and ammunition are identified and arms flows are tracked. Reference material on specific small arms, light weapons, and ammunition is included throughout the guide. This material will help readers to take the steps necessary to identify the make and model of the most commonly encountered weapons and ammunition.

The guide’s introduction explains that “Reference material on specific small arms, light weapons, and ammunition is included throughout the guide. This material will help readers to take the steps necessary to identify the make and model of the most commonly encountered weapons and ammunition.”

Here’s a look at some pages from the guide:

The guide does not assume prior knowledge and explains how various different weapons work (Small Arms Survey)

How to identify a musket (Small Arms Survey)

A flow chart explaining how to use the guide to identify firearms (Small Arms Survey)

A breakdown of a Bulgarian 40mm grenade (Small Arms Survey)

Detachable magazines 101 (Small Arms Survey)

The new identification handbook follows on from another Small Arms Survey publication helmed by Jenzen-Jones, a report on Improvised Weapons was published earlier this month.

Source

via The Firearm Blog
Small Arms Survey Publish Free Guide to the Identification of Small Arms

How startups spend their money: New data shows how burn rates differ by industry and region

(Photo via Bigstock)

After a startup raises venture capital, where does all that money go? What types of services do they purchase? Do burn rates differ depending on where a company is based?

Brex, a San Francisco-based payments startup that raised $125 million in October, aimed to answer some of these questions with its new 2018 State of Startup Spend Report.

Brex helps startup founders get corporate credit cards and counts PayPal founders as investors. It analyzed financial data from YC companies using Brex — Brex was a YC startup and has raised funding from YC — to learn more about how early-stage startups are spending cash. Here are the key takeaways:

  • San Francisco-based startups have the highest burn rate at a whopping $369,000 per month. Bay Area (companies not in Silicon Valley or SF) ranked next, followed by Austin, New York, Silicon Valley, Boston, Los Angeles, Seattle, and San Francisco.
  • Startups that burn through the most money do business with internet services; transportation; and data analytics. Those that spend the least are in consumer electronics; design; operating systems; and clothing.
  • Startups rely on other startups for their tech stack needs. For example: 62 percent of companies use Gusto for payroll; 61 percent use Copper for CRM; 78 percent use Intercom for customer support. For cloud services, 54 percent use AWS, compared to 20 percent on Google. However, for productivity, 61 percent use Google Suite compared to 40 percent for Microsoft. “What we’ve learned is that startups are creating their own ecosystem,” the report notes. “They are investing in other startups that mirror their own goals of creating user-friendly, affordable, and innovative solutions for the market.”

See the full report here.

via GeekWire
How startups spend their money: New data shows how burn rates differ by industry and region

Laravel Stripe Payment Gateway Integration Tutorial With Example

Laravel Stripe Payment Example Tutorial

Laravel Stripe Payment Gateway Integration Tutorial With Example is today’s leading topic. We will use the Cashier package to integrate Stripe Payment Gateway in Laravel. The demand for Saas based platform is increasing day by day and nowadays building a subscription-based system is universal. So to make it easy on the backend like Laravel, we do need some packages that can help us build scalable, securable, and reliable web applications. Laravel Cashier makes it very simple to integrate Payment Gateway like Stripe. So let us see how we can integrate stripe in Laravel on Subscription based system.

Laravel Stripe Payment Gateway Integration

Let us first install and configure Laravel 5.7

Step 1: Install and configure Laravel 5.7

Type the following command.

laravel new stripesub

Go to the project folder and open the project in an editor. I am using VSCode.

cd stripesub && code .

Install the js dependencies using the following command.

npm install

Install the Cashier package for Stripe dependencies.

composer require laravel/cashier

Also, create an authentication scaffold.

php artisan make:auth

Step 2: Create an essential database migrations

Before using Cashier, we’ll also need to prepare the database. We need to add several columns to your users’ table and create new subscriptions and plans table to hold all of our customer’s subscriptions.

So, first go to the create_users_table.php and add the following schema.

$table->string('stripe_id')->nullable()->collation('utf8mb4_bin');
$table->string('card_brand')->nullable();
$table->string('card_last_four', 4)->nullable();
$table->timestamp('trial_ends_at')->nullable();

Your schema now looks like this.

public function up()
{
     Schema::create('users', function (Blueprint $table) {
         $table->increments('id');
         $table->string('name');
         $table->string('email')->unique();
         $table->timestamp('email_verified_at')->nullable();
         $table->string('password');
         $table->string('stripe_id')->nullable()->collation('utf8mb4_bin');
         $table->string('card_brand')->nullable();
         $table->string('card_last_four', 4)->nullable();
         $table->timestamp('trial_ends_at')->nullable();
         $table->rememberToken();
         $table->timestamps();
     });
}

Now, create two more migrations files.

php artisan make:migration create_subscriptions_table
php artisan make:migration create_plans_table

Now, write the schema on both of the files.

First, add the following schema inside the create_subscriptions_table.php file.

public function up()
    {
        Schema::create('subscriptions', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('user_id');
            $table->string('name');
            $table->string('stripe_id')->collation('utf8mb4_bin');
            $table->string('stripe_plan');
            $table->integer('quantity');
            $table->timestamp('trial_ends_at')->nullable();
            $table->timestamp('ends_at')->nullable();
            $table->timestamps();
        });
    }

Write the following schema inside the create_plans_table.php file.

public function up()
    {
        Schema::create('plans', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('slug')->unique();
            $table->string('stripe_plan');
            $table->float('cost');
            $table->text('description')->nullable();
            $table->timestamps();
        });
    }

Save the file and go to the terminal and migrate tables.

php artisan migrate

Now, add two plans manually on the plans table like this.

 

Laravel Stripe Payment Gateway Integration Tutorial With Example

Step 3: Get and Set Stripe API Keys

First, you need to create an account on Stripe. You can do it on here. Then after login to your account, you will find a Test dashboard.

Now, click on the Developers link on the left sidebar. In the submenu, you can find the API keys item. Click on that item, and you will be redirected to this page.

 

Stripe API keys

Here, you can find the Publishable Key or Stripe Key and Secret Key.

You need to add these keys inside the .env file in our project.

// .env

STRIPE_KEY=your key here
STRIPE_SECRET=your secret here

Finally, you should configure your Stripe key in your services.php configuration file. You can retrieve your Stripe API keys from the Stripe control panel.

// services.php

'stripe' => [
    'model'  => App\User::class,
    'key' => env('STRIPE_KEY'),
    'secret' => env('STRIPE_SECRET'),
],

Also, you need to add the Billable Trait inside the User.php model.

// User.php

use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

Step 4: Create Plans on Stripe Dashboard

You can create a plan through Laravel but that will take some time, and our motto is to accept the payment through stripe. So we will create the plans manually on Stripe Dashboard.

First, go to the Billing >> Products link and right now, there are no products available. So we need to create two products. Here products mean plans. So we will create two plans.

  1. Basic
  2. Professional

Create and assign the values same as we have assigned the values on the Plans table in our Laravel application. After creating two plans, your products look like this.

 

Laravel Subscription Based System Using Stripe Payment

Step 5: Display Plans on Frontend

First, define the routes for our application inside the routes >> web.php file.

// web.php

Route::group(['middleware' => 'auth'], function() {
    Route::get('/home', 'HomeController@index')->name('home');
    Route::get('/plans', 'PlanController@index')->name('plans.index');
});

We have taken the auth middleware to protect the routes related to payment and home.

Now, create the Plan.php model and PlanController.php file.

php artisan make:model Plan
php artisan make:controller PlanController

Define the index method inside the PlanController.

// PlanController.php

use App\Plan;

public function index()
{
        $plans = Plan::all();
        return view('plans.index', compact('plans'));
}

Now, inside the resources >> views folder, create one folder called plans and inside that folder, create one file called index.blade.php file. Write the following code.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-12">
            <div class="card">
                <div class="card-header">Plans</div>
                <div class="card-body">
                    <ul class="list-group">
                        @foreach($plans as $plan)
                        <li class="list-group-item clearfix">
                            <div class="pull-left">
                                <h5></h5>
                                <h5>$ monthly</h5>
                                <h5></h5>
                                <a href="" class="btn btn-outline-dark pull-right">Choose</a>
                            </div>
                        </li>
                        @endforeach
                    </ul>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Now, register one user on Laravel application and go to this URL: https://ift.tt/2SsY5nd

You will find the plans like this.

 

Laravel Saas Tutorial

Step 6: Show the plan

So, when the user chooses the plan, we need to redirect the user to a particular plan page.

Define one more route inside the routes >> web.php file.

// web.php

Route::group(['middleware' => 'auth'], function() {
    Route::get('/home', 'HomeController@index')->name('home');
    Route::get('/plans', 'PlanController@index')->name('plans.index');
    Route::get('/plan/{plan}', 'PlanController@show')->name('plans.show');
});

Now, by default, RouteModelBinding works with the ID of the model. But we will not pass the ID to show the particular plan instead we will pass the slug. So we need to define it inside the Plan.php model.

<?php

// Plan.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Plan extends Model
{
    protected $fillable = [
        'name',
        'slug',
        'stripe_plan',
        'cost',
        'description'
    ];

    public function getRouteKeyName()
    {
        return 'slug';
    }
}

Here, we have defined the function called getRouteKeyName. So based on this function, now we can fetch the record based on the slug and not based on the ID. That is why we have taken the slug field as a unique field in the database.

Now, define the show() function inside the PlanController.php file.

// PlanController.php

public function show(Plan $plan, Request $request)
{
     return view('plans.show', compact('plan'));
}

Next step is to create a view file called show.blade.php inside the resources >> views >> plans folder. Add the following code inside the show.blade.php file.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-12">
            <div class="card">
                <div class="card-header"></div>
                <div class="card-body">
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Now, we will display the Payment Form on this page.

Step 7: Display the Payment Form

For this example, I am using Card Element. You can find it on official Stripe Documentation. It is securely collect sensitive card details using Elements, our pre-built UI components.

 

Stripe Payment Form Example

Now, we need to include the External JS files in our project. For that, we need to modify the resources >> views >> layouts >> app.blade.php file.

<!DOCTYPE html>
<html lang="">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="">

    <title></title>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css">

    <!-- Styles -->
    <link href="" rel="stylesheet">
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light navbar-laravel">
            <div class="container">
                <a class="navbar-brand" href="">
                    
                </a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav mr-auto">

                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ml-auto">
                        <!-- Authentication Links -->
                        @guest
                            <li class="nav-item">
                                <a class="nav-link" href=""></a>
                            </li>
                            <li class="nav-item">
                                @if (Route::has('register'))
                                    <a class="nav-link" href=""></a>
                                @endif
                            </li>
                        @else
                          <li class="nav-item">      
                            <a class="nav-link" href=""></a>
                          </li>
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                     <span class="caret"></span>
                                </a>

                                <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href=""
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        
                                    </a>

                                    <form id="logout-form" action="" method="POST" style="display: none;">
                                        @csrf
                                    </form>
                                </div>
                            </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            @yield('content')
        </main>
    </div>
    <!-- Scripts -->
    <script src=""></script>
    @yield('scripts')
</body>
</html>

Here, we have defined one navigation link called plans and also add one section for scripts. So, now we can add the Javascript per pagewise using @yield directive.

Next step is to write the Card Element inside the show.blade.php file.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-12">
            <div class="">
                <p>You will be charged $ for  Plan</p>
            </div>
            <div class="card">
                <form action="#" method="post" id="payment-form">
                    @csrf                    
                    <div class="form-group">
                        <div class="card-header">
                            <label for="card-element">
                                Enter your credit card information
                            </label>
                        </div>
                        <div class="card-body">
                            <div id="card-element">
                            <!-- A Stripe Element will be inserted here. -->
                            </div>
                            <!-- Used to display form errors. -->
                            <div id="card-errors" role="alert"></div>
                            <input type="hidden" name="plan" value="" />
                        </div>
                    </div>
                    <div class="card-footer">
                        <button class="btn btn-dark" type="submit">Pay</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
@endsection
@section('scripts')
<script src="https://js.stripe.com/v3/"></script>
<script>
    // Create a Stripe client.
var stripe = Stripe('');

// Create an instance of Elements.
var elements = stripe.elements();

// Custom styling can be passed to options when creating an Element.
// (Note that this demo uses a wider set of styles than the guide below.)
var style = {
  base: {
    color: '#32325d',
    lineHeight: '18px',
    fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
    fontSmoothing: 'antialiased',
    fontSize: '16px',
    '::placeholder': {
      color: '#aab7c4'
    }
  },
  invalid: {
    color: '#fa755a',
    iconColor: '#fa755a'
  }
};

// Create an instance of the card Element.
var card = elements.create('card', {style: style});

// Add an instance of the card Element into the `card-element` <div>.
card.mount('#card-element');

// Handle real-time validation errors from the card Element.
card.addEventListener('change', function(event) {
  var displayError = document.getElementById('card-errors');
  if (event.error) {
    displayError.textContent = event.error.message;
  } else {
    displayError.textContent = '';
  }
});

// Handle form submission.
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
  event.preventDefault();

  stripe.createToken(card).then(function(result) {
    if (result.error) {
      // Inform the user if there was an error.
      var errorElement = document.getElementById('card-errors');
      errorElement.textContent = result.error.message;
    } else {
      // Send the token to your server.
      stripeTokenHandler(result.token);
    }
  });
});

// Submit the form with the token ID.
function stripeTokenHandler(token) {
  // Insert the token ID into the form so it gets submitted to the server
  var form = document.getElementById('payment-form');
  var hiddenInput = document.createElement('input');
  hiddenInput.setAttribute('type', 'hidden');
  hiddenInput.setAttribute('name', 'stripeToken');
  hiddenInput.setAttribute('value', token.id);
  form.appendChild(hiddenInput);

  // Submit the form
  form.submit();
}
</script>
@endsection

Here, we have used the Card element and Javascript to display the form.

Now, add the link to this page inside the index.blade.php file.

// index.blade.php

<a href="" class="btn btn-outline-dark pull-right">Choose</a>

So, it will be redirected to the page like this.

 

Stripe Payment Gateway Integration in Laravel

So, here we need to enter the following details. You can enter the details like below inputs.

  1. Card Number: 4242 4242 4242 4242
  2. Expiration Date: 10/21 or whatever you like from the future of today’s date
  3. CVC: whatever you like
  4. Zipcode: whatever you like

These are dummy details, but these details generally used in sandbox account to check the application.

Right now nothing will happen because we need to define the form action to store the data inside the database tables. So let us define the post route.

Step 8: Save the subscriptions and accept payment

Define the final route inside the web.php file.

// web.php

Route::group(['middleware' => 'auth'], function() {
    Route::get('/home', 'HomeController@index')->name('home');
    Route::get('/plans', 'PlanController@index')->name('plans.index');
    Route::get('/plan/{plan}', 'PlanController@show')->name('plans.show');
    Route::post('/subscription', 'SubscriptionController@create')->name('subscription.create');
});

We have defined the POST route for subscriptions. Now, create SubscriptionController using the following command.

php artisan make:controller SubscriptionController

Now, add a POST route inside the show.blade.php file when we post the data to the server.

// show.blade.php

<form action="" method="post" id="payment-form">

Inside that controller, we need to define one function called create().

<?php

// SubscriptionController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Plan;

class SubscriptionController extends Controller
{
    public function create(Request $request, Plan $plan)
    {
        $plan = Plan::findOrFail($request->get('plan'));
        
        $request->user()
            ->newSubscription('main', $plan->stripe_plan)
            ->create($request->stripeToken);
        
        return redirect()->route('home')->with('success', 'Your plan subscribed successfully');
    }
}

Here, we are creating the subscription for registered User and charge him.

First, we have to fetch the plan according to the id. Then we need to pass that plan to the newSubscription() function and create a subscription.

So, here we have used the Billable trait’s subscribedToPlan() method and pass the first parameter plan and the second parameter main.

We are creating a new Subscription, if the payment made successfully then, it would redirect to the HomePage with a  success message.

Write the following code inside the home.blade.php file.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            @if(session()->get('success'))
                <div class="alert alert-success">
                    
                </div>
            @endif
            <div class="card">
                <div class="card-header">Dashboard</div>

                <div class="card-body">
                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            
                        </div>
                    @endif

                    Welcome to the Dashboard
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

So, if your all of the configurations are right, then you should go to any of the plans and try to subscribe to the plan. If you are redirecting to the homepage, then you are almost done.

You can see that one database entry inside the subscriptions table is there.

 

Laravel Saas Example

Your user’s table also has been updated as you can see some field’s details is updated.

You can check the Stripe Dashboard as well. As we have subscribed the 50$ Professional Plan.

 

Stripe Payment Example

Step 9: Security Tweaks

Now, we need to keep in mind one thing that if the user is already subscribed to one plan, then we need the user to prevent to choose that plan. So, we need to add one condition on the choose button.

So, add the condition inside the index.blade.php file.

@if(!auth()->user()->subscribedToPlan($plan->stripe_plan, 'main'))
    <a href="" class="btn btn-outline-dark pull-right">Choose</a>
@endif

Also, we need to add the condition inside the PlanController.php file’s show() function.

// PlanController.php

public function show(Plan $plan, Request $request)
{
        if($request->user()->subscribedToPlan($plan->stripe_plan, 'main')) {
            return redirect()->route('home')->with('success', 'You have already subscribed the plan');
        }
        return view('plans.show', compact('plan'));
 }

Also, we need to do the same thing inside the SubscriptionController’s create() method.

// SubscriptionController.php

public function create(Request $request, Plan $plan)
{
        if($request->user()->subscribedToPlan($plan->stripe_plan, 'main')) {
            return redirect()->route('home')->with('success', 'You have already subscribed the plan');
        }
        $plan = Plan::findOrFail($request->get('plan'));
        
        $request->user()
            ->newSubscription('main', $plan->stripe_plan)
            ->create($request->stripeToken);
        
        return redirect()->route('home')->with('success', 'Your plan subscribed successfully');
}

Save the file, and now you are good to go. I am putting this Laravel Stripe Payment Gateway Integration Tutorial’s whole code on Github.

Finally, Laravel Stripe Payment Gateway Integration Tutorial article is over. There are still so many things that we can do with the project. But for basic understanding, this is enough. Thanks.

Github Code

The post Laravel Stripe Payment Gateway Integration Tutorial With Example appeared first on AppDividend.

via Planet MySQL
Laravel Stripe Payment Gateway Integration Tutorial With Example