Skip to content

Instantly share code, notes, and snippets.

@sayhicoelho
Last active July 2, 2019 22:50
Show Gist options
  • Save sayhicoelho/96f11f126d578bcc481ac0e0333ec837 to your computer and use it in GitHub Desktop.
Save sayhicoelho/96f11f126d578bcc481ac0e0333ec837 to your computer and use it in GitHub Desktop.
@extends('layouts.app')
@section('title', 'Checkout')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<h1>Checkout (via PagSeguro)</h1>
<form action="{{ route('checkout.store') }}" method="POST" id="checkoutForm">
@csrf
@method('POST')
<input type="hidden" name="senderHash" id="senderHash">
<input type="hidden" name="creditCardToken" id="creditCardToken">
<h2>Dados pessoais</h2>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="name">Nome <i class="fa fa-question-circle" data-toggle="tooltip" title="O nome que você definiu nas configurações da conta."></i></label>
<input type="text" name="name" id="name" class="form-control form-control-lg" value="{{ Auth::user()->fullname }}" disabled>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="email">Email <i class="fa fa-question-circle" data-toggle="tooltip" title="O email que você definiu nas configurações da conta."></i></label>
<input type="email" name="email" id="email" class="form-control form-control-lg" value="{{ Auth::user()->email }}" disabled>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="phone">Telefone*</label>
<input type="text" name="phone" id="phone" class="form-control{{ $errors->has('phone') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('phone') }}" required>
@if ($errors->has('phone'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('phone') }}</strong>
</span>
@endif
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="cpf">CPF*</label>
<input type="text" name="cpf" id="cpf" class="form-control{{ $errors->has('cpf') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('cpf') }}" required>
@if ($errors->has('cpf'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('cpf') }}</strong>
</span>
@endif
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="birthdate">Data de nascimento*</label>
<input type="date" name="birthdate" id="birthdate" class="form-control{{ $errors->has('birthdate') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('birthdate') }}" required>
@if ($errors->has('birthdate'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('birthdate') }}</strong>
</span>
@endif
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="cnpj">CNPJ <i class="fa fa-question-circle" data-toggle="tooltip" title="Se você é uma Entidade Legal, você deve preencher este campo."></i></label>
<input type="text" name="cnpj" id="cnpj" class="form-control{{ $errors->has('cnpj') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('cnpj') }}">
@if ($errors->has('cnpj'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('cnpj') }}</strong>
</span>
@endif
</div>
</div>
</div>
<h2>Endereço</h2>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="addressPostalCode">CEP*</label>
<input type="text" name="addressPostalCode" id="addressPostalCode" class="form-control{{ $errors->has('addressPostalCode') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('addressPostalCode') }}" required>
@if ($errors->has('addressPostalCode'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('addressPostalCode') }}</strong>
</span>
@endif
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="addressStreet">Logradouro*</label>
<input type="text" name="addressStreet" id="addressStreet" class="form-control{{ $errors->has('addressStreet') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('addressStreet') }}" required>
@if ($errors->has('addressStreet'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('addressStreet') }}</strong>
</span>
@endif
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="addressNumber">Número*</label>
<input type="text" name="addressNumber" id="addressNumber" class="form-control{{ $errors->has('addressNumber') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('addressNumber') }}" required>
@if ($errors->has('addressNumber'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('addressNumber') }}</strong>
</span>
@endif
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="addressDistrict">Bairro*</label>
<input type="text" name="addressDistrict" id="addressDistrict" class="form-control{{ $errors->has('addressDistrict') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('addressDistrict') }}" required>
@if ($errors->has('addressDistrict'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('addressDistrict') }}</strong>
</span>
@endif
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="addressCity">Cidade*</label>
<input type="text" name="addressCity" id="addressCity" class="form-control{{ $errors->has('addressCity') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('addressCity') }}" required>
@if ($errors->has('addressCity'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('addressCity') }}</strong>
</span>
@endif
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="addressState">UF*</label>
<input type="text" name="addressState" id="addressState" class="form-control{{ $errors->has('addressState') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('addressState') }}" required>
@if ($errors->has('addressState'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('addressState') }}</strong>
</span>
@endif
</div>
</div>
</div>
<h2>Cartão de crédito</h2>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label for="creditCardNumber">Número*</label>
<input type="text" name="creditCardNumber" id="creditCardNumber" class="form-control{{ $errors->has('creditCardNumber') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('creditCardNumber') }}" required>
@if ($errors->has('creditCardNumber'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('creditCardNumber') }}</strong>
</span>
@endif
<div class="credit-card-brands mt-2">
<img class="" id="creditcard-visa" src="{{ asset('img/visa.svg') }}" alt="Visa" height="30" data-toggle="tooltip" title="Visa">
<img class="" id="creditcard-mastercard" src="{{ asset('img/mastercard.svg') }}" alt="Mastercard" height="30" data-toggle="tooltip" title="Mastercard">
<img class="" id="creditcard-amex" src="{{ asset('img/amex.svg') }}" alt="American Express" height="30" data-toggle="tooltip" title="American Express">
<img class="" id="creditcard-diners" src="{{ asset('img/diners.svg') }}" alt="Diners Club" height="30" data-toggle="tooltip" title="Diners Club">
<img class="" id="creditcard-unknown" src="{{ asset('img/unknown-creditcard.svg') }}" alt="Other Brand" height="30" data-toggle="tooltip" title="Outro">
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="creditCardCVV">Código de segurança*</label>
<input type="text" name="creditCardCVV" id="creditCardCVV" class="form-control{{ $errors->has('creditCardCVV') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('creditCardCVV') }}" required>
@if ($errors->has('creditCardCVV'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('creditCardCVV') }}</strong>
</span>
@endif
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="creditCardExpirationMonth">Mês de validade*</label>
<input type="text" name="creditCardExpirationMonth" id="creditCardExpirationMonth" class="form-control{{ $errors->has('creditCardExpirationMonth') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('creditCardExpirationMonth') }}" required>
@if ($errors->has('creditCardExpirationMonth'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('creditCardExpirationMonth') }}</strong>
</span>
@endif
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="creditCardExpirationYear">Ano de Expiração*</label>
<input type="text" name="creditCardExpirationYear" id="creditCardExpirationYear" class="form-control{{ $errors->has('creditCardExpirationYear') ? ' is-invalid' : '' }} form-control-lg" value="{{ old('creditCardExpirationYear') }}" required>
@if ($errors->has('creditCardExpirationYear'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('creditCardExpirationYear') }}</strong>
</span>
@endif
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="installment">Parcelamento*</label>
<select name="installment" id="installment" class="form-control{{ $errors->has('installment') ? ' is-invalid' : '' }} form-control-lg" required disabled></select>
@if ($errors->has('installment'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('installment') }}</strong>
</span>
@endif
</div>
</div>
</div>
<p class="small">Os campos com * são obrigatórios.</p>
<h2>Resumo do Pedido</h2>
<!-- Aqui colocar as informações do pedido, como: Valor total, tabela com os itens, etc. -->
<p class="text-muted lead">Ao publicar meu pedido, concordo com os termos de uso do <a href="{{ route('terms') }}">{{ config('app.name') }}</a>.</p>
<hr/>
<button type="submit" class="btn btn-primary btn-lg btn-block">
Confirmar e pagar
</button>
</form>
</div>
</div>
</div>
<div class="modal fade" id="modalCreditcardError" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<p class="lead text-muted text-center mb-4">
Por favor, selecione um cartão de crédito válido para prosseguir.
</p>
<button type="button" class="btn btn-lg btn-info btn-block text-white" data-dismiss="modal">OK!</button>
</div>
</div>
</div>
</div>
@endsection
@push('styles')
<style>
.creditcard-disabled {
opacity: .3;
}
</style>
@endpush
@push('scripts')
<script type="text/javascript" src="https://stc{{ config('pagseguro.sandbox') ? '.sandbox' : '' }}.pagseguro.uol.com.br/pagseguro/api/v2/checkout/pagseguro.directpayment.js"></script>
<script>
document.addEventListener('DOMContentLoaded', e => {
maskInputs();
PagSeguroDirectPayment.setSessionId('{{ PagSeguro::startSession() }}');
let selectedBrand = null; // this will be updated on input event from cardNumber
const totalPrice = 'VALOR_TOTAL_DO_PEDIDO';
$('#installment').on('change', e => {
const totalAmount = parseFloat($(e.target).val().split('|')[2]);
$('#total-price').text(currency(totalAmount));
});
$('#checkoutForm').on('submit', e => {
e.preventDefault();
if (selectedBrand) {
const cardNumber = $('#creditCardNumber').val();
const cvv = $('#creditCardCVV').val();
const expirationMonth = $('#creditCardExpirationMonth').val();
const expirationYear = $('#creditCardExpirationYear').val();
getCreditCardToken(cardNumber, selectedBrand, cvv, expirationMonth, expirationYear).then(token => {
setSenderHash();
setCreditCardToken(token);
e.target.submit();
}).catch(err => {
showModalError();
});
} else {
showModalError();
}
});
$('#addressPostalCode').on('input', e => {
const postalCode = e.target.value.replace(/[^0-9]/g, '');
if (postalCode.length === 8) {
$.ajax({
url: `https://viacep.com.br/ws/${postalCode}/json/`,
type: 'GET',
success: function (response) {
const { bairro, localidade, logradouro, uf } = response;
$('#addressDistrict').val(bairro);
$('#addressCity').val(localidade);
$('#addressStreet').val(logradouro);
$('#addressState').val(uf);
}
})
}
});
$('#creditCardNumber').on('input', e => {
const cardNumber = e.target.value.replace(/[^0-9]/g, '');
// reset selected brand
selectedBrand = null;
// reset credit card number errors
clearInputError('#creditCardNumber');
// reset credit card brand icons
$('.credit-card-brands img').removeClass('creditcard-disabled');
if (cardNumber.length >= 14 && cardNumber.length <= 16) {
const bin = cardNumber.slice(0, 6); // the first 6 digits is the card bin
getBrand(bin).then(brand => {
$('.credit-card-brands img').addClass('creditcard-disabled');
if ($(`#creditcard-${brand}`).length > 0) {
$(`#creditcard-${brand}`).removeClass('creditcard-disabled');
} else {
$('#creditcard-unknown').removeClass('creditcard-disabled');
}
selectedBrand = brand;
setInstallments(brand);
}).catch(err => {
setInputError('#creditCardNumber', 'Número do cartão inválido.');
});
}
});
$('#creditCardNumber').trigger('input');
function currency(value) {
return 'R$' + parseFloat(value || 0).toLocaleString('pt-BR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
function showModalError() {
$('#modalCreditcardError').modal({ show: true });
}
function maskInputs() {
maskPhone();
$('#cpf').mask('000.000.000-00');
$('#cnpj').mask('00.000.000/0000-00');
$('#addressPostalCode').mask('00000-000');
$('#creditCardNumber').mask('0000 0000 0000 0000');
$('#creditCardCVV').mask('000');
$('#creditCardExpirationMonth').mask('00');
$('#creditCardExpirationYear').mask('0000');
}
function maskPhone() {
const SPMaskBehavior = function (val) {
return val.replace(/\D/g, '').length === 11 ? '(00) 00000-0000' : '(00) 0000-00009';
};
const spOptions = {
onKeyPress: function(val, e, field, options) {
field.mask(SPMaskBehavior.apply({}, arguments), options);
}
};
$('#phone').mask(SPMaskBehavior, spOptions);
}
function setInstallments(brand) {
getInstallments(totalPrice, brand)
.then(installments => {
installments.forEach(installment => {
const option = `<option value="${installment.quantity}|${installment.installmentAmount}|${installment.totalAmount}">
${installment.quantity}x
de \R$${installment.installmentAmount}
</option>`;
$('#installment').append(option);
});
$('#installment').prop('disabled', false);
})
.catch(err => {
console.error(err);
});
}
function setSenderHash() {
$('#senderHash').val(PagSeguroDirectPayment.getSenderHash());
}
function setCreditCardToken(token) {
$('#creditCardToken').val(token);
}
function setInputError(input, error) {
const errorElement = `<span class="invalid-feedback" role="alert">
<strong>${error}</strong>
</span>`;
$(input).addClass('is-invalid')
.after(errorElement);
}
function clearInputError(input) {
$(input).removeClass('is-invalid').next('span.invalid-feedback').remove();
}
function getBrand(bin) { // 6 primeiros dígitos do cartão
return new Promise((resolve, reject) => {
PagSeguroDirectPayment.getBrand({
cardBin: bin,
success: function(response) {
// bandeira encontrada
resolve(response.brand.name);
},
error: function(response) {
// tratamento do erro
reject(response);
},
complete: function(response) {
// tratamento comum para todas chamadas
}
});
});
}
function getInstallments(amount, brand) {
return new Promise((resolve, reject) => {
PagSeguroDirectPayment.getInstallments({
amount,
brand,
success: function(response){
// Retorna as opções de parcelamento disponíveis
resolve(response.installments[brand])
},
error: function(response) {
// callback para chamadas que falharam.
reject(response);
},
complete: function(response){
// Callback para todas chamadas.
}
});
})
}
function getCreditCardToken(cardNumber, brand, cvv, expirationMonth, expirationYear) {
return new Promise((resolve, reject) => {
PagSeguroDirectPayment.createCardToken({
cardNumber, // Número do cartão de crédito
brand, // Bandeira do cartão
cvv, // CVV do cartão
expirationMonth, // Mês da expiração do cartão
expirationYear, // Ano da expiração do cartão, é necessário os 4 dígitos.
success: function(response) {
// Retorna o cartão tokenizado.
const token = response.card.token;
resolve(token);
},
error: function(response) {
// Callback para chamadas que falharam.
reject(response);
},
complete: function(response) {
// Callback para todas chamadas.
}
});
});
}
});
</script>
@endpush
<?php
namespace App\Http\Controllers;
use PagSeguro;
use Carbon\Carbon;
use App\Transaction;
use Artistas\PagSeguro\PagSeguroException;
use Illuminate\Http\Request;
class OrderController extends Controller
{
public function showCheckoutForm()
{
return view('checkout');
}
public function addOrder(PaymentRequest $request)
{
$installment = explode('|', $request->installment);
$installmentQuantity = (int)$installment[0];
$installmentValue = (float)$installment[1];
$transaction = new Transaction();
$transaction->buyer()->associate($request->user());
$transaction->installment_quantity = $installmentQuantity;
$transaction->installment_value = $installmentValue;
$transaction->total_installment = ($installmentValue * $installmentQuantity);
$transaction->save();
try {
$senderInfoIDKey = $request->cpf ? 'senderCPF' : 'senderCNPJ';
$pagseguro = PagSeguro::setReference($transaction->id)
->setSenderInfo([
'senderName' => $request->user()->fullname, // Deve conter nome e sobrenome
'senderPhone' => $request->phone, // Código de área enviado junto com o telefone
'senderEmail' => $request->user()->email,
'senderHash' => $request->senderHash,
$senderInfoIDKey => $request->cpf ?? $request->cnpj // Ou CNPJ se for Pessoa Júridica
])
->setCreditCardHolder([
'creditCardHolderName' => $request->user()->fullname, // Deve conter nome e sobrenome
'creditCardHolderPhone' => $request->phone, // Código de área enviado junto com o telefone
'creditCardHolderCPF' => $request->cpf, // Ou CNPJ se for Pessoa Júridica
'creditCardHolderBirthDate' => (new Carbon($request->birthdate))->format('d/m/Y'),
])
->setBillingAddress([
'billingAddressStreet' => $request->addressStreet,
'billingAddressNumber' => $request->addressNumber,
'billingAddressDistrict' => $request->addressDistrict,
'billingAddressPostalCode' => $request->addressPostalCode,
'billingAddressCity' => $request->addressCity,
'billingAddressState' => $request->addressState,
])
->setItems([
[
'itemId' => 'ID_DO_PRODUTO',
'itemDescription' => 'NOME_DO_PRODUTO',
'itemAmount' => 'VALOR_DO_PRODUTO', // Valor unitário
'itemQuantity' => 1, // Quantidade de itens
],
])
->send([
'paymentMethod' => 'creditCard', // checar na biblioteca os métodos disponíveis
'creditCardToken' => $request->creditCardToken,
'installmentQuantity' => $installmentQuantity, // Parcelamento
'installmentValue' => $installmentValue
]);
} catch (PagSeguroException $e) {
$transaction->delete();
return redirect()->back()->withInput()->with('alert', [
'type' => 'danger',
'message' => 'Algo deu errado. Por favor, tente novamente ou entre em contato conosco.'
]);
}
$transaction->code = $pagseguro->code;
$transaction->save();
return redirect()->back()->with('alert', [
'type' => 'success',
'message' => 'Pagamento via Pagseguro, realizado com sucesso!'
]);
}
}
<?php
namespace App\Http\Controllers;
use PagSeguro;
use Artistas\PagSeguro\PagSeguroException;
use App\Transaction;
use Illuminate\Http\Request;
class PagSeguroController extends Controller
{
public function notification(Request $request)
{
try {
$notification = PagSeguro::notification($request->notificationCode, $request->notificationType); // Ou PagSeguroRecorrente
} catch (PagSeguroException $e) {
return response()->json(['error' => [
'code' => $e->getCode(),
'message' => $e->getMessage()
]], 500);
}
$status = (string)$notification->status;
$transactionId = (string)$notification->reference;
$transaction = Transaction::find($transactionId);
$transaction->status = $status;
$transaction->save();
return response()->json(['success' => true]);
}
}
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PaymentRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'installment' => ['required', 'installment'],
'cpf' => ['required', 'cpf'],
'phone' => ['required', 'phone'],
'cnpj' => ['nullable', 'cnpj'],
'birthdate' => ['required', 'date_format:Y-m-d', 'before:today'],
'addressStreet' => ['required', 'string'],
'addressNumber' => ['required', 'string'],
'addressDistrict' => ['required', 'string'],
'addressPostalCode' => ['required', 'cep'],
'addressCity' => ['required', 'string'],
'addressState' => ['required', 'string', 'size:2'],
];
}
}
<?php
Route::post('pagseguro', 'PagSeguroController@notification')
->middleware('set.pagseguro.origin.headers');
Route::get('checkout.show', 'OrderController@showCheckoutForm');
Route::post('checkout.store', 'OrderController@addOrder');
<?php
namespace App\Http\Middleware;
use Closure;
class SetPagseguroOriginHeadersMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$sandbox = config('pagseguro.sandbox') ? 'sandbox.' : '';
$response = $next($request);
$response->header('Access-Control-Allow-Origin', "https://{$sandbox}pagseguro.uol.com.br");
return $response;
}
}
<?php
namespace App\Providers;
use Validator;
use Illuminate\Support\ServiceProvider;
class ValidatorServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
Validator::extend('cpf', function ($attribute, $value, $parameters, $validator) {
return is_cpf($value);
});
Validator::extend('cnpj', function ($attribute, $value, $parameters, $validator) {
return is_cnpj($value);
});
Validator::extend('cep', function ($attribute, $value, $parameters, $validator) {
return is_cep($value);
});
Validator::extend('phone', function ($attribute, $value, $parameters, $validator) {
$phone = only_number($value);
return strlen($phone) == 10
|| strlen($phone) == 11;
});
Validator::extend('installment', function ($attribute, $value, $parameters, $validator) {
$installment = explode('|', $value);
$installmentQuantity = (int)$installment[0];
$installmentValue = (float)$installment[1];
$totalAmount = (float)$installment[2];
return !empty($installmentQuantity)
&& !empty($installmentValue)
&& !empty($totalAmount);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment