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.
Após selecionar o ambiente de sandbox é preciso criar as credenciais para o nosso app, para isso, basta clicar no botão create App.
Para esse projeto será utilizado a opção Merchant pois é o mais indicado para esse tipo de aplicação.
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.
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.
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>
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]);
}
}
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");
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>
Com isso, devemos ter esse 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>
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>
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>
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");
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')
]);
}
}
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>
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 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)