DEV Community

Gabriel Maximiniano
Gabriel Maximiniano

Posted on

Laravel + PayPal - Parte 2: Integração client side

Com o sistema base já definido na Parte 1, o próximo passo é iniciar as integrações com o PayPal, nessa etapa iremos abordar somente a integração feita com os botões do PayPal (não utilizando o SDK do PHP ainda).

Independente da abordagem que for utilizada para a integração é preciso criar um conta no Developers PayPal.

Configurando o Sandbox

Após entrar na área do desenvolvedor, é apresentado dois ambientes para a aplicação: o sandbox e live. O sandbox é um ambiente de desenvolvimento para a realização dos testes da sua aplicação, ele funciona da mesma forma que o ambiente live porém utiliza de contas fictícias para as transações. Nesse projeto iremos utilizar somente o ambiente do sandbox, porém quando chegar a hora de colocar seu projeto em produção, basta seguir os mesmos passos mas no ambiente live.

imagem apresentando os ambientes de desenvolvimento

Após selecionar o ambiente de sandbox é preciso criar as credenciais para o nosso app, para isso, basta clicar no botão create App.

selecionar botao para criar app

Para esse projeto será utilizado a opção Merchant pois é o mais indicado para esse tipo de aplicação.

formulário de criação do app

Após criar o app, será apresentado uma tela com o Client ID e Secret do app, nós iremos utilizar eles mais para frente, então guarde essas informações.

Para os testes de pagamento é necessário utilizar uma conta PayPal ficticia. No item Accounts do menu Sandbox é possível visualizar os usuário de testes existentes e criar novos.

menu sandbox

Como funciona os botões do PayPal

Utilizar os botões de pagamento do PayPal é o jeito mais simples de integrar a sua aplicação com o plataforma de pagamentos. Ele funciona de uma forma independente do seu sistema, onde ao clicar no botão, o usuário é redirecionado para a tela de pagamento (podendo ser uma Pop-up ou uma nova página) podendo utilizar sua própria conta do PayPal ou um cartão de crédito para realizar a compra. Após o PayPal processar a transação, o usuário e redirecionado de volta para o seu sistema, onde assim, você pode realizar a finalização da compra, salvando as informações necessária para liberar o produto para o seu usuário.

imagem apresentando o processo do paypal

Após entender como funciona esse método de integração com o PayPal, é hora de escrever nossos códigos.

Criando página do filme

Para a integração com o PayPal é preciso existir uma página onde é apresentado o filme de forma individual para o usuário comprar de forma direta, para isso criamos um arquivo dentro da pasta views chamado:

movies/show.blade.php

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Detalhes filme') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 bg-white border-b border-gray-200 flex justify-center">
                    <div class="flex flex-wrap justify-around border-0 rounded w-9/12 p-5 bg-gray-100">
                        <div class="w-1/3 flex justify-center">
                            <img src="{{ $movie->image }}" alt="">
                        </div>
                        <div class="w-2/3 flex flex-col justify-between">
                            <div>
                                <h1 class="font-bold text-2xl">{{ $movie->title }}</h1>
                            </div>
                            <div>
                                <p>
                                    {{ $movie->overview }}
                                </p>
                            </div>
                            <div class="flex justify-evenly">
                                <button id="purchase" value="{{ $movie->purchase_price }}" class="cursor-pointer w-auto p-2 border border-red-500 rounded bg-red-500 text-white">Aluguel: R${{ $movie->purchase_price }}</button>
                                <button id="rental" value="{{ $movie->rental_price }}" class="cursor-pointer w-auto p-2 border border-red-500 rounded text-red-500">Compra: R${{ $movie->rental_price }}</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>
Enter fullscreen mode Exit fullscreen mode

Além disso criamos um novo controller com o comando

php artisan make:controller MovieController

Que fica assim:

<?php

namespace App\Http\Controllers;

use App\Models\Movie;

class MovieController extends Controller
{
    public function show(Movie $movie) {
        return view("movie.show", ["movie" => $movie]);
    }
}
Enter fullscreen mode Exit fullscreen mode

E criamos uma rota para poder acessar essa página editando o arquivo routes/web.php:

Route::get("/movie/{movie}", [MovieController::class, "show"])->middleware(['auth'])->name("movie.show");
Enter fullscreen mode Exit fullscreen mode

Com isso, nossa página de filme individual já existe, porém ela ainda não pode ser acessada pelos usuários, para isso vamos adicionar um link na view home.blade.php:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Filmes disponíveis') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 bg-white border-b border-gray-200">
                    <div class="flex flex-wrap justify-around">
                        @foreach ($movies as $movie)
                            <div class="w-60 rounded shadow-xl m-2 bg-gray-100">
                                <img src="{{ $movie->image }}" class="h-auto w-full" alt="...">
                                <div class="px-6 py-4">
                                    <h5 class="font-bold text-xl mb-2">{{ $movie->title}}</h5>
                                    {{-- Adicionando link --}}
                                    <a href="{{ route('movie.show', $movie) }}" class="py-1 font-semibold text-blue-500 mr-4">Alugar</a>
                                    <a href="{{ route('movie.show', $movie) }}" class="py-1 font-semibold text-blue-500 mr-4">Comprar</a>
                                </div>
                            </div>
                        @endforeach
                    </div>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>
Enter fullscreen mode Exit fullscreen mode

Com isso, devemos ter esse resultado até agora:

gif com resultado até agora

Adicionando os botões do PayPal

O primeiro passo para a integração é importar o SDK do JavaScript na página. No arquivo app.blade.php adicione essa linha na área de importação dos seus scripts utilizando o seu Client ID:

<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID"></script>
Enter fullscreen mode Exit fullscreen mode

Com isso, já é possível adicionar os botões do PayPal em qualquer lugar do projeto. Para isso, modificamos o código da nossa view show.blade.php

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Detalhes filme') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 bg-white border-b border-gray-200 flex justify-center">
                    <div class="flex flex-wrap justify-around border-0 rounded w-9/12 p-5 bg-gray-100">
                        <div class="w-1/3 flex justify-center">
                            <img src="{{ $movie->image }}" alt="">
                        </div>
                        <div class="w-2/3 flex flex-col justify-between">
                            <div>
                                <h1 class="font-bold text-2xl">{{ $movie->title }}</h1>
                            </div>
                            <div>
                                <p>
                                    {{ $movie->overview }}
                                </p>
                            </div>
                            <div class="flex justify-evenly">
                                <button id="purchase" value="{{ $movie->purchase_price }}" class="cursor-pointer w-auto p-2 border border-red-500 rounded bg-red-500 text-white">Aluguel: R${{ $movie->purchase_price }}</button>
                                <button id="rental" value="{{ $movie->rental_price }}" class="cursor-pointer w-auto p-2 border border-red-500 rounded text-red-500">Compra: R${{ $movie->rental_price }}</button>
                            </div>
                            <div class="h-1/4" id="paypal-button-container"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
        paypal.Buttons().render('#paypal-button-container')
    </script>
</x-app-layout>
Enter fullscreen mode Exit fullscreen mode

Com esse script, os botões do PayPal já aparecem no HTML e ao clicar, ele já redireciona para o serviço de pagamento do PayPal porém ele ainda não esta configurado para pegar o preço do nosso sistema, para isso vamos editar o código deixando ele assim:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Detalhes filme') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 bg-white border-b border-gray-200 flex justify-center">
                    <div class="flex flex-wrap justify-around border-0 rounded w-9/12 p-5 bg-gray-100">
                        <div class="w-1/3 flex justify-center">
                            <img src="{{ $movie->image }}" alt="">
                        </div>
                        <div class="w-2/3 flex flex-col justify-between">
                            <div>
                                <h1 class="font-bold text-2xl">{{ $movie->title }}</h1>
                            </div>
                            <div>
                                <p>
                                    {{ $movie->overview }}
                                </p>
                            </div>
                            <div class="flex justify-evenly">
                                <button onclick="handleButtonClick({{ $movie->purchase_price }}, 'purchase')" id="purchase" value="{{ $movie->purchase_price }}" class="cursor-pointer w-auto p-2 border border-red-500 rounded bg-red-500 text-white">Aluguel: R${{ $movie->purchase_price }}</button>
                                <button onclick="handleButtonClick({{ $movie->rental_price }}, 'rental')" id="rental" value="{{ $movie->rental_price }}" class="cursor-pointer w-auto p-2 border border-red-500 rounded text-red-500">Compra: R${{ $movie->rental_price }}</button>
                            </div>
                            <div class="h-1/4" id="paypal-button-container"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>

        const changeSelectedButton = () => {
            const selectedButton = document.getElementsByClassName("bg-red-500")[0]
            const newSelectedButton = document.getElementsByClassName("text-red-500")[0]

            selectedButton.classList.remove("bg-red-500", "text-white")
            selectedButton.classList.add("text-red-500", "bg-white")

            newSelectedButton.classList.remove("text-red-500", "bg-white")
            newSelectedButton.classList.add("bg-red-500", "text-white")
        }

        const generatePayPalButton = (price, type) => {
            paypal.Buttons({
                createOrder: function(data, actions) {
                    return actions.order.create({
                        purchase_units: [{
                            amount: {
                                value: price
                            }
                        }]
                    })
                },
                onApprove: function(data, actions) {
                    return actions.order.capture().then(function(details) {
                        console.log(details)
                        alert('Transaction completed by ' + details.payer.name.given_name)
                    })
                },
                style: {
                    height: 30
                }
            }).render("#paypal-button-container")
        }

        const handleButtonClick = (price, type) => {
            changeSelectedButton()

            document.getElementById("paypal-button-container").innerHTML = ""

            generatePayPalButton(price, type)
        }

        generatePayPalButton(@json($movie->purchase_price))
    </script>
</x-app-layout>
Enter fullscreen mode Exit fullscreen mode

Após essas alterações, nós temos 3 funções feitas no JavaScript e uma chamada de evento onClick no botões de aluguel e compra.

  • changeSelectButton: função que serve exclusivamente para trocar o CSS do botão selecionado, o projeto inicia com o botão de aluguel selecionado;
  • generatePayPalButton(price): função que serve para gerar um novo botão do PayPal. Na função Buttons é necessário passar um objeto contendo a configuração dos botões, no momento estamos passando duas callbacks, sendo elas a createOrder e onApprove. A createOrder serve para a configuração inicial dos botões, no nosso caso, estamos passando somente o valor da compra e uma propriedade do style para definir a altura dos botões. Já a função onApprove é uma função que é executada quando a transação acontece com sucesso.
  • handleButtonClick(price): função onde é disparada pelo evento onClick dos botões de compra/aluguel. É responsável por deletar o botão PayPal existente e gerar um novo.

Observação: Caso você queira editar mais o estilo dos botões PayPal, tem o link da documentação oficial no final do artigo

Integrando os botões com nosso sistema

Após colocar os botões do PayPal é importante salvarmos no nosso sistema que a compra/aluguel foi realizado com sucesso. Para isso, precisamos criar um rota de API no arquivo routes/api.php:

<?php

use App\Http\Controllers\MovieController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::post("/movie/{movie}/buy", [MovieController::class, "buy"])->name("movie.buy");
Enter fullscreen mode Exit fullscreen mode

Após isso é preciso criar um novo método no nosso MovieController:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Movie;
use Carbon\Carbon;

class MovieController extends Controller
{
    ...

    public function buy(Request $request, Movie $movie) {
        $movie->users()->attach($request->get('user_id'), [
            "acquired_in" => Carbon::now(),
            "expires_at" => $request->get("type") == "purchase" ? Carbon::now()->addMonth() : null
        ]);

        return response()->json([
            "message" => "Compra realizada com sucesso!",
            "movie" => $movie->load('users')
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Essa função buy é responsável inserir na tabela intermediária (movie_user) a data e a validade (caso for um aluguel) do filme. Como não foi usado um sistema de login de API, nós vamos passar o ID do usuário pelo body da requisição.

Após definir o a função no backend precisamos atualizar a função onApprove do nosso botão PayPal:

<script>
...

        const saveOnBack = async (type) => {
            const user_id = @json(auth()->id())

            const movie_id = @json($movie->id)

            const resp = await fetch(`/api/movie/${movie_id}/buy`, {
                method: "POST",
                body: JSON.stringify({
                    type: type,
                    user_id: user_id
                }),
                header: new Headers({
                    "Content-Type": "application/json"
                })
            })
            const json = await resp.json()

            alert(json.message)
        }

        const generatePayPalButton = (price, type) => {
            paypal.Buttons({
                createOrder: function(data, actions) {
                    return actions.order.create({
                        purchase_units: [{
                            amount: {
                                value: price
                            }
                        }]
                    })
                },
                onApprove: function(data, actions) {
                    return actions.order.capture().then(function(details) {
                        saveOnBack(type)
                    })
                },
                style: {
                    height: 30
                }
            }).render("#paypal-button-container")
        }

...

</script>
Enter fullscreen mode Exit fullscreen mode

Na função saveOnBack nós enviamos o tipo de compra e o ID do usuário para a nossa rota do backend, onde é feito o attach entre as tabelas de filme e usuário.

Resultado final da parte 2

Com isso, devemos ter essas telas como resultado até o momento:

Tela inicial

tela inicial

Tela de filme individual

tela de filme individual

E agora?

Com isso finalizamos essa integração com o JavaScript, ela foi feita de forma simples focada na integração com o PayPal. Existe alguns pontos onde é possível melhorar o código, mas isso fica como um desafio para quem fizer essa integração, alguns pontos são:

  • Diferenciar quando o usuário clica no botão "Alugar" ou "Comprar" na página inicial, fazendo assim, com que os botões "Aluguel" e "Compra" da página de filme individual sela selecionado de forma automática;
  • Verificar e limitar somente a uma compra por filme.

Para a próxima parte será realizado essas alterações listadas como desafios e adicionaremos uma funcionalidade onde verifica se o alguel do filme expirou, atualizando a tabela no banco de dados.

Referências

https://laravel.com/docs/8.x
https://developer.mozilla.org/pt-BR/docs/Web/API/Fetch_API/Using_Fetch
https://developer.paypal.com/docs/checkout/
https://developer.paypal.com/docs/checkout/integrate/
https://developer.paypal.com/docs/platforms/checkout/reference/style-guide/

Top comments (0)