Laravel + PayPal - Parte 2: Integração client side
Tags: laravel, paypal, tutorial, php
Published: 2021-09-06 19:59:48

Com o sistema base já definido na [Parte 1](https://dev.to/gabmaxs/laravel-paypal-parte-1-criando-projeto-2568), 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](https://developer.paypal.com/home/).

#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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6otx147o2q6iqj1c60t.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/502tosucrv17ed4wcyvz.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c4jfydeqqw6uwugh7lj4.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8xbg25qehy2e0oeve5ov.png)

#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](https://www.paypalobjects.com/ppdevdocs/img/docs/checkout/v2/paypal-checkout-overview-pay-now-orders-api.svg)

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

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

Além disso criamos um novo *controller* com o comando 
```php artisan make:controller MovieController ```

Que fica assim:

```php
<?php

namespace App\Http\Controllers;

use App\Models\Movie;

class MovieController extends Controller
{
    public function show(Movie $movie) {
        return view("movie.show", ["movie" => $movie]);
    }
}
```
E criamos uma rota para poder acessar essa página editando o arquivo **routes/web.php**: 

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

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

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

Com isso, devemos ter esse resultado até agora:

![gif com resultado até agora](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ljev7nmc6cp8v6d424ua.gif)

#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**: 

```html
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID"></script>
```

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

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

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

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
<?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");
```
Após isso é preciso criar um novo método no nosso **MovieController**:

````php
<?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')
		]);
	}
}
```
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:

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

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mf0ezq21wey3xqy7seu0.png)

###Tela de filme individual
![tela de filme individual](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vsd3fsoug19wd94ru9zb.png)

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