511 lines
20 KiB
PHP
511 lines
20 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers;
|
||
|
||
use App\Enums\PaymentTransactionStatus;
|
||
use App\Models\PaymentAggregator;
|
||
use App\Models\PaymentRefund;
|
||
use App\Models\PaymentTransaction;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Http\Response;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Illuminate\Support\Str;
|
||
use Stripe\PaymentIntent;
|
||
use Stripe\StripeClient;
|
||
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
|
||
use Throwable;
|
||
|
||
class StripeController extends Controller
|
||
{
|
||
|
||
private $client;
|
||
private $timeout = 60; //In seconds
|
||
|
||
/**
|
||
* Create a new controller instance.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function __construct()
|
||
{
|
||
// Create a client with a base URI
|
||
$this->client = new StripeClient(config('variables.stripe_secret'));
|
||
|
||
}
|
||
|
||
|
||
public function getMethods()
|
||
{
|
||
$data = $this->client->paymentMethods->all()->toArray();
|
||
return $this->successResponse([
|
||
'hasWebview' => true,
|
||
'methods' => $data,
|
||
]);
|
||
}
|
||
|
||
public function getCheckout($payment_token)
|
||
{
|
||
$transaction = PaymentTransaction::where('payment_token', $payment_token)->first();
|
||
if(empty($transaction)){
|
||
return $this->errorResponse(__('errors.model_not_found', ['model' => 'transaction']), Response::HTTP_NOT_FOUND);
|
||
}
|
||
|
||
$amount = $transaction->amount;
|
||
$receiver = config('variables.receiver_name');
|
||
$receiver_logo = asset('assets/images/logo.jpeg');
|
||
return view('stripe-checkout', compact('amount','receiver','receiver_logo','payment_token'));
|
||
}
|
||
|
||
public function pay(Request $request)
|
||
{
|
||
try {
|
||
|
||
$this->validate($request, [
|
||
'aggregator_id' => 'required|integer',
|
||
'amount' => 'required|numeric|min:5',
|
||
'currency' => 'required|string|size:3',
|
||
'customer_id' => 'nullable',
|
||
'payment_method' => 'nullable|string',
|
||
'customer_email' => 'required|email',
|
||
'customer_name' => 'nullable|string',
|
||
'customer_surname' => 'required|string',
|
||
'customer_phone_number' => 'nullable|string',
|
||
'customer_address' => 'required|string',
|
||
'customer_city' => 'required_if:payment_method,CARD|string',
|
||
'customer_country' => 'required|string|size:2',
|
||
'customer_state' => 'required_if:payment_method,CARD|string', //Etat du pays dans lequel se trouve le client. Cette valeur est obligatoire si le client se trouve au États Unis d’Amérique (US) ou au Canada (CA)
|
||
'customer_zip_code' => 'required_if:payment_method,CARD|string|size:5',
|
||
'reason' => 'required|string'
|
||
]);
|
||
|
||
$transaction_id = $this->getTransactionID();
|
||
$payment_method = $request->input('payment_method','CARD');
|
||
$amount = $request->input('amount');
|
||
$currency = $request->input('currency');
|
||
|
||
if($amount < 325 && $currency == 'XAF'){
|
||
$amount = 325;
|
||
}
|
||
|
||
$payment_token = $this->getTransactionToken();
|
||
$payment_url = route('stripe.checkout',['payment_token' =>$payment_token]);
|
||
PaymentTransaction::create([
|
||
'aggregator_id' => $request->input('aggregator_id'),
|
||
"currency" => $request->input('currency'),
|
||
"transaction_id" => $transaction_id,
|
||
"amount" => $amount,
|
||
"payment_method" => $payment_method,
|
||
"payment_token" => $payment_token,
|
||
"payment_url" => $payment_url,
|
||
'status' => PaymentTransactionStatus::INITIATED,
|
||
"reason" => $request->input('reason'),
|
||
"customer_id" => $request->input('customer_id'),
|
||
"customer_name" => $request->input('customer_name'),
|
||
"customer_surname" => $request->input('customer_surname'),
|
||
"customer_email" => $request->input('customer_email'),
|
||
"customer_phone_number" => $request->input('customer_phone_number'),
|
||
"customer_address" => $request->input('customer_address'),
|
||
"customer_city" => $request->input('customer_city'),
|
||
"customer_country" => $request->input('customer_country'),
|
||
"customer_state" => $request->input('customer_state'),
|
||
"customer_zip_code" => $request->input('customer_zip_code'),
|
||
]);
|
||
|
||
return $this->successResponse([
|
||
'message' => 'Payment initiated',
|
||
'payment_url' => $payment_url
|
||
]);
|
||
|
||
}catch (Throwable $e){
|
||
Log::error($e->getMessage());
|
||
}
|
||
|
||
return $this->errorResponse(trans('errors.unexpected_error'));
|
||
|
||
}
|
||
|
||
public function post(Request $request)
|
||
{
|
||
|
||
$this->validate($request, [
|
||
'payment_token' => 'required|string|exists:payment_transactions,payment_token',
|
||
'stripeToken' => 'required|string',
|
||
]);
|
||
|
||
$token = $request->input('payment_token');
|
||
$transaction = PaymentTransaction::where('payment_token', $token)->where('status', PaymentTransactionStatus::INITIATED)->firstOrFail();
|
||
|
||
|
||
// Init payment
|
||
$charge = $this->client->charges->create([
|
||
"amount" => stripeFormatCurrency(round($transaction->amount,2), $transaction->currency),
|
||
"currency" => $transaction->currency,
|
||
"description" => $transaction->reason,
|
||
// "customer" => 15,
|
||
"source" => $request->input('stripeToken'),
|
||
"receipt_email" => $transaction->customer_email,
|
||
"metadata" => [
|
||
"transaction_id" => $transaction->transaction_id,
|
||
'lang' => app()->getLocale(),
|
||
"customer_id" => $transaction->customer_id,
|
||
"customer_name" => $transaction->customer_name,
|
||
"customer_surname" => $transaction->customer_surname,
|
||
"customer_email" => $transaction->customer_email,
|
||
"customer_phone_number" => $transaction->customer_phone_number,
|
||
"customer_address" => $transaction->customer_address,
|
||
"customer_city" => $transaction->customer_city,
|
||
"customer_country" => $transaction->customer_country,
|
||
"customer_state" => $transaction->customer_state,
|
||
"customer_zip_code" => $transaction->customer_zip_code,
|
||
],
|
||
|
||
]);
|
||
|
||
if($charge){
|
||
|
||
$transaction->update([
|
||
'aggregator_payment_ref' => $charge->id,
|
||
'payment_date' => date('Y-m-d H:i:s', $charge->created),
|
||
'status' => PaymentTransactionStatus::ACCEPTED,
|
||
'payment_method_exact' => $request->input('card_number')
|
||
]);
|
||
|
||
return redirect()->route('checkout',['payment_token' => $token]);
|
||
}
|
||
|
||
session()->flash('error',__('errors.unexpected_error'));
|
||
return redirect()->to(route('stripe.checkout',['payment_token' => $token]));
|
||
}
|
||
|
||
private function getTransactionToken()
|
||
{
|
||
do {
|
||
$code = 'STP-'.Str::random(64);
|
||
$result = collect(DB::select('SELECT * FROM payment_transactions WHERE payment_token = :code', ['code' => $code]));
|
||
$codeCorrect = sizeof($result) < 0;
|
||
} while ($codeCorrect);
|
||
return $code;
|
||
}
|
||
|
||
|
||
public function refund(Request $request)
|
||
{
|
||
$this->validate($request, [
|
||
'transaction_id' => 'required|string|exists:payment_transactions,transaction_id',
|
||
'reason' => 'nullable|string|in:duplicate,fraudulent,requested_by_customer'
|
||
]);
|
||
|
||
$transaction = PaymentTransaction::where('transaction_id', $request->input('transaction_id'))->where('status', PaymentTransactionStatus::ACCEPTED)->firstOrFail();
|
||
$reason = $request->input('reason','requested_by_customer');
|
||
|
||
|
||
$reference = $transaction->aggregator_payment_ref;
|
||
$twoFirstChars = substr($reference,0,2);
|
||
$keyToRefund = $twoFirstChars == 'ch' ? 'charge' : 'payment_intent';
|
||
$refund = $this->client->refunds->create([
|
||
$keyToRefund => $reference,
|
||
'reason' => $reason
|
||
]);
|
||
|
||
if(!empty($refund)){
|
||
PaymentRefund::create([
|
||
'refund_id' => $this->getTransactionID('payment_refunds'),
|
||
'transaction_id' => $transaction->id,
|
||
"currency" => strtoupper($refund->currency),
|
||
"amount" => $refund->amount,
|
||
"reason" => $reason,
|
||
'date' => date('Y-m-d H:i:s', $refund->created),
|
||
'status' => strtoupper($refund->status)
|
||
]);
|
||
|
||
return $this->successResponse("Payment refunded successfully");
|
||
}
|
||
|
||
|
||
return $this->errorResponse(__('errors.unexpected_error'));
|
||
}
|
||
|
||
|
||
|
||
public function payIn(Request $request)
|
||
{
|
||
$this->validate($request, [
|
||
'card_no' => 'required',
|
||
'exp_month' => 'required',
|
||
'exp_year' => 'required',
|
||
'cvc' => 'required',
|
||
// 'aggregator_id' => 'required|integer',
|
||
'amount' => 'required|numeric|min:5',
|
||
'currency' => 'required|string|size:3',
|
||
'customer_id' => 'nullable',
|
||
// 'payment_method' => 'nullable|string', // Actuallu it's only CARD
|
||
'customer_email' => 'required|email',
|
||
'customer_name' => 'nullable|string',
|
||
'customer_surname' => 'required|string',
|
||
'customer_phone_number' => 'nullable|string',
|
||
'customer_address' => 'required|string',
|
||
'customer_city' => 'required|string',
|
||
'customer_country' => 'required|string|size:2',
|
||
'customer_state' => 'nullable|string', //Etat du pays dans lequel se trouve le client. Cette valeur est obligatoire si le client se trouve au États Unis d’Amérique (US) ou au Canada (CA)
|
||
'customer_zip_code' => 'nullable|string|size:5',
|
||
'reason' => 'required|string'
|
||
]);
|
||
|
||
|
||
$aggregator = PaymentAggregator::where('name','like','%stripe%')->firstOrFail();
|
||
|
||
$amount = $request->input('amount');
|
||
$currency = $request->input('currency');
|
||
|
||
if($amount < 325 && $currency == 'XAF'){
|
||
$amount = 325;
|
||
}
|
||
|
||
|
||
$transaction = PaymentTransaction::create([
|
||
'aggregator_id' => $aggregator->id,
|
||
"currency" => $request->input('currency'),
|
||
"transaction_id" => $this->getTransactionID(),
|
||
"amount" => $amount,
|
||
"payment_method" => 'CARD',
|
||
'status' => PaymentTransactionStatus::INITIATED,
|
||
"reason" => $request->input('reason'),
|
||
"customer_id" => $request->input('customer_id'),
|
||
"customer_name" => $request->input('customer_name'),
|
||
"customer_surname" => $request->input('customer_surname'),
|
||
"customer_email" => $request->input('customer_email'),
|
||
"customer_phone_number" => $request->input('customer_phone_number'),
|
||
"customer_address" => $request->input('customer_address'),
|
||
"customer_city" => $request->input('customer_city'),
|
||
"customer_country" => $request->input('customer_country'),
|
||
"customer_state" => $request->input('customer_state'),
|
||
"customer_zip_code" => $request->input('customer_zip_code'),
|
||
]);
|
||
|
||
// Create payment method in Stripe
|
||
// https://stripe.com/docs/api/payment_methods/create
|
||
$method = $this->client->paymentMethods->create([
|
||
'type' => 'card',
|
||
'card' => [
|
||
'number' => $request->input('card_no'),
|
||
'exp_month' => $request->input('exp_month'),
|
||
'exp_year' => $request->input('exp_year'),
|
||
'cvc' => $request->input('cvc'),
|
||
]
|
||
]);
|
||
|
||
if($method){
|
||
|
||
$transaction->update([
|
||
'payment_method_exact' => $method->id
|
||
]);
|
||
|
||
|
||
// Create payment intent
|
||
// https://stripe.com/docs/api/payment_intents/create
|
||
$paymentIntent = $this->client->paymentIntents->create([
|
||
"amount" => stripeFormatCurrency($amount, $request->input('currency')),
|
||
"currency" => $request->input('currency'),
|
||
"description" => $request->input('reason'),
|
||
'payment_method_types' => ['card'],
|
||
'payment_method' => $method->id,
|
||
'confirm' => 'true',
|
||
'capture_method' => 'automatic',
|
||
'return_url' => route('stripe.webhook',['transaction_id' => $transaction->transaction_id]), // Redirect url if 3d secure required
|
||
'payment_method_options' => [
|
||
'card' => [
|
||
'request_three_d_secure' => 'automatic'
|
||
]
|
||
],
|
||
]);
|
||
|
||
|
||
if($paymentIntent){
|
||
|
||
$transaction->update([
|
||
'payment_date' => date('Y-m-d H:i:s', $paymentIntent->created),
|
||
'aggregator_payment_ref' => $paymentIntent->id
|
||
]);
|
||
|
||
if(!empty($paymentIntent->next_action->redirect_to_url->url)){
|
||
return $this->successResponse([
|
||
'message' => '3D secure redirect',
|
||
'payment_url' => $paymentIntent->next_action->redirect_to_url->url
|
||
], ResponseAlias::HTTP_MOVED_PERMANENTLY);
|
||
}
|
||
|
||
return $this->handlePaymentIntentResult($transaction, $paymentIntent);
|
||
}
|
||
|
||
}
|
||
|
||
return $this->errorResponse(__('errors.unexpected_error'));
|
||
}
|
||
|
||
|
||
public function payOut(Request $request)
|
||
{
|
||
$this->validate($request, [
|
||
'card_no' => 'required_if:payment_method,CARD',
|
||
'exp_month' => 'required_if:payment_method,CARD',
|
||
'exp_year' => 'required_if:payment_method,CARD',
|
||
// 'cvc' => 'required_if:payment_method,CARD',
|
||
'bank_country' => 'required_if:payment_method,BANK|string|size:2',
|
||
'bank_currency' => 'required_if:payment_method,BANK|string|size:3',
|
||
'bank_account_number' => 'required_if:payment_method,BANK|string',
|
||
// 'aggregator_id' => 'required|integer',
|
||
'amount' => 'required|numeric|min:5',
|
||
'currency' => 'required|string|size:3',
|
||
'customer_id' => 'nullable',
|
||
'payment_method' => 'required|string|in:CARD,BANK',
|
||
'customer_email' => 'required|email',
|
||
'customer_name' => 'nullable|string',
|
||
'customer_surname' => 'required|string',
|
||
'customer_phone_number' => 'nullable|string',
|
||
'customer_address' => 'required|string',
|
||
'customer_city' => 'required|string',
|
||
'customer_country' => 'required|string|size:2',
|
||
'customer_state' => 'nullable|string', //Etat du pays dans lequel se trouve le client. Cette valeur est obligatoire si le client se trouve au États Unis d’Amérique (US) ou au Canada (CA)
|
||
'customer_zip_code' => 'nullable|string|size:5',
|
||
'reason' => 'required|string'
|
||
]);
|
||
|
||
|
||
$aggregator = PaymentAggregator::where('name','like','%stripe%')->firstOrFail();
|
||
|
||
$amount = $request->input('amount');
|
||
$currency = $request->input('currency');
|
||
$payment_method = $request->input('payment_method');
|
||
|
||
$transaction = PaymentTransaction::create([
|
||
'aggregator_id' => $aggregator->id,
|
||
"currency" => $request->input('currency'),
|
||
"transaction_id" => $this->getTransactionID(),
|
||
"amount" => $amount,
|
||
"payment_method" => $payment_method,
|
||
'status' => PaymentTransactionStatus::INITIATED,
|
||
"reason" => $request->input('reason'),
|
||
"customer_id" => $request->input('customer_id'),
|
||
"customer_name" => $request->input('customer_name'),
|
||
"customer_surname" => $request->input('customer_surname'),
|
||
"customer_email" => $request->input('customer_email'),
|
||
"customer_phone_number" => $request->input('customer_phone_number'),
|
||
"customer_address" => $request->input('customer_address'),
|
||
"customer_city" => $request->input('customer_city'),
|
||
"customer_country" => $request->input('customer_country'),
|
||
"customer_state" => $request->input('customer_state'),
|
||
"customer_zip_code" => $request->input('customer_zip_code'),
|
||
]);
|
||
|
||
// Create destination account in Stripe
|
||
|
||
if($payment_method == 'CARD'){
|
||
//https://stripe.com/docs/api/external_account_cards/create
|
||
|
||
$destination = $this->client->accounts->createExternalAccount(
|
||
config('variables.stripe_account'), [
|
||
'external_account' => [
|
||
'object' => 'card',
|
||
'number' => $request->input('card_no'),
|
||
'exp_month' => $request->input('exp_month'),
|
||
'exp_year' => $request->input('exp_year'),
|
||
// 'cvc' => $request->input('cvc'),
|
||
]
|
||
]
|
||
);
|
||
}else{
|
||
//https://stripe.com/docs/api/external_account_bank_accounts/create#account_create_bank_account
|
||
$destination = $this->client->accounts->createExternalAccount(
|
||
config('variables.stripe_account'), [
|
||
'external_account' => [
|
||
'object' => 'bank_account',
|
||
'country' => $request->input('bank_country'),
|
||
'currency' => $request->input('bank_currency'),
|
||
'account_number' => $request->input('bank_account_number'),
|
||
]
|
||
]
|
||
);
|
||
}
|
||
|
||
if($destination){
|
||
|
||
$transaction->update([
|
||
'payment_method_exact' => $destination->id
|
||
]);
|
||
|
||
// Create payout in Stripe
|
||
// https://stripe.com/docs/api/payouts/create
|
||
$payout = $this->client->payouts->create([
|
||
"amount" => stripeFormatCurrency($amount, $currency),
|
||
"currency" => $currency,
|
||
'description' => $request->input('reason'),
|
||
"destination" => $destination->id,
|
||
"method" => $payment_method == 'CARD' ? 'instant' : 'standard'
|
||
]);
|
||
|
||
if($payout) {
|
||
|
||
$transaction->update([
|
||
'payment_date' => date('Y-m-d H:i:s', $payout->arrival_date),
|
||
'aggregator_payment_ref' => $payout->id,
|
||
'status' => strtolower($payout->status)
|
||
]);
|
||
|
||
return $this->successResponse([
|
||
'transaction_id' => $transaction->transaction_id,
|
||
'status' => $transaction->status
|
||
]);
|
||
}
|
||
}
|
||
|
||
return $this->errorResponse(__('errors.unexpected_error'));
|
||
}
|
||
|
||
|
||
public function capturePaymentResult(Request $request)
|
||
{
|
||
$this->validate($request, [
|
||
'payment_intent' => 'nullable|string',
|
||
'transaction_id' => 'required|string|exists:payment_transactions,transaction_id',
|
||
]);
|
||
|
||
$transaction = PaymentTransaction::where('transaction_id',$request->input('transaction_id'))->firstOrFail();
|
||
|
||
if($request->has('payment_intent')) {
|
||
|
||
$paymentIntent = $this->client->paymentIntents->retrieve($request->input('payment_intent'));
|
||
|
||
return $this->handlePaymentIntentResult($transaction, $paymentIntent);
|
||
}
|
||
|
||
return $this->errorResponse(__('errors.unexpected_error'));
|
||
}
|
||
|
||
private function handlePaymentIntentResult(PaymentTransaction $transaction, PaymentIntent $intent){
|
||
|
||
if($intent->status == 'succeeded'){
|
||
$transaction->update([
|
||
'status' => PaymentTransactionStatus::ACCEPTED
|
||
]);
|
||
|
||
return redirect()->route('paymentResult',[
|
||
'message' => 'Payment Accepted',
|
||
'transaction_id' => $transaction->transaction_id,
|
||
'status' => 1
|
||
]);
|
||
|
||
}else{
|
||
|
||
$transaction->update([
|
||
'status' => strtolower($intent->status)
|
||
]);
|
||
|
||
return redirect()->route('paymentResult', [
|
||
'message' => "Payment ".$transaction->status,
|
||
'status' => 0
|
||
]);
|
||
}
|
||
}
|
||
}
|