Paypal Integration added

This commit is contained in:
jitendra 2018-11-16 15:41:08 +05:30
parent c845fdddb8
commit f0ef4f910a
22 changed files with 727 additions and 10 deletions

View File

@ -71,6 +71,7 @@
"Webkul\\Theme\\": "packages/Webkul/Theme/src",
"Webkul\\Shipping\\": "packages/Webkul/Shipping/src",
"Webkul\\Payment\\": "packages/Webkul/Payment/src",
"Webkul\\Paypal\\": "packages/Webkul/Paypal/src",
"Webkul\\Sales\\": "packages/Webkul/Sales/src",
"Webkul\\Tax\\": "packages/Webkul/Tax/src"
}

View File

@ -216,6 +216,7 @@ return [
Webkul\Checkout\Providers\CheckoutServiceProvider::class,
Webkul\Shipping\Providers\ShippingServiceProvider::class,
Webkul\Payment\Providers\PaymentServiceProvider::class,
Webkul\Paypal\Providers\PaypalServiceProvider::class,
Webkul\Sales\Providers\SalesServiceProvider::class,
Webkul\Tax\Providers\TaxServiceProvider::class,
],

View File

@ -17,6 +17,17 @@ return [
'class' => 'Webkul\Payment\Payment\MoneyTransfer',
'order_status' => 'pending',
'active' => true
],
'paypal_standard' => [
'code' => 'paypal_standard',
'title' => 'Paypal Standard',
'description' => 'Paypal Standard',
'class' => 'Webkul\Paypal\Payment\Standard',
'order_status' => 'pending_payment',
'sandbox' => true,
'active' => true,
'business_account' => 'test@webkul.com'
]
];

View File

@ -113,6 +113,10 @@ class OrderDataGrid
return '<span class="badge badge-md badge-info">Closed</span>';
else if($value == "pending")
return '<span class="badge badge-md badge-warning">Pending</span>';
else if($value == "pending_payment")
return '<span class="badge badge-md badge-warning">Pending Payment</span>';
else if($value == "fraud")
return '<span class="badge badge-md badge-danger">Fraud</span>';
},
],
],

View File

@ -1058,6 +1058,8 @@ class Cart {
$data = $this->toArray();
$finalData = [
'cart_id' => $this->getCart()->id,
'customer_id' => $data['customer_id'],
'is_guest' => $data['is_guest'],
'customer_email' => $data['customer_email'],

View File

@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Config;
class Payment
{
/**
* Returns all supported payment methods
*
@ -16,15 +17,15 @@ class Payment
$paymentMethods = [];
foreach (Config::get('paymentmethods') as $paymentMethod) {
$object = new $paymentMethod['class'];
$object = app($paymentMethod['class']);
if($object->isAvailable()) {
$paymentMethods[] = [
'method' => $object->getCode(),
'method_title' => $object->getTitle(),
'description' => $object->getDescription(),
];
}
if($object->isAvailable()) {
$paymentMethods[] = [
'method' => $object->getCode(),
'method_title' => $object->getTitle(),
'description' => $object->getDescription(),
];
}
}
return [
@ -32,4 +33,16 @@ class Payment
'html' => view('shop::checkout.onepage.payment', compact('paymentMethods'))->render()
];
}
/**
* Returns payment redirect url if have any
*
* @return array
*/
public function getRedirectUrl($cart)
{
$payment = app(Config::get('paymentmethods.' . $cart->payment->method . '.class'));
return $payment->getRedirectUrl();
}
}

View File

@ -2,6 +2,12 @@
namespace Webkul\Payment\Payment;
/**
* Cash On Delivery payment method class
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class CashOnDelivery extends Payment
{
/**
@ -10,4 +16,9 @@ class CashOnDelivery extends Payment
* @var string
*/
protected $code = 'cashondelivery';
public function getRedirectUrl()
{
}
}

View File

@ -2,6 +2,12 @@
namespace Webkul\Payment\Payment;
/**
* Money Transfer payment method class
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class MoneyTransfer extends Payment
{
/**
@ -10,4 +16,9 @@ class MoneyTransfer extends Payment
* @var string
*/
protected $code = 'moneytransfer';
public function getRedirectUrl()
{
}
}

View File

@ -3,9 +3,17 @@
namespace Webkul\Payment\Payment;
use Illuminate\Support\Facades\Config;
use Webkul\Checkout\Facades\Cart;
abstract class Payment
{
/**
* Cart object
*
* @var Cart
*/
protected $cart;
/**
* Checks if payment method is available
*
@ -62,4 +70,43 @@ abstract class Payment
{
return core()->getConfigData('paymentmethods.' . $this->getCode() . '.' . $field);
}
abstract public function getRedirectUrl();
/**
* Assign cart
*
* @var void
*/
public function setCart()
{
if(!$this->cart)
$this->cart = Cart::getCart();
}
/**
* Returns cart insrance
*
* @var mixed
*/
public function getCart()
{
if(!$this->cart)
$this->setCart();
return $this->cart;
}
/**
* Return paypal redirect url
*
* @var Collection
*/
public function getCartItems()
{
if(!$this->cart)
$this->setCart();
return $this->cart->items;
}
}

View File

@ -0,0 +1,176 @@
<?php
namespace Webkul\Paypal\Helpers;
use Webkul\Sales\Respositories\OrderRepository;
use Webkul\Sales\Respositories\InvoiceRepository;
/**
* Paypal ipn listener helper
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class Ipn
{
/**
* Ipn post data
*
* @var array
*/
protected $post;
/**
* Order object
*
* @var object
*/
protected $order;
/**
* OrderRepository object
*
* @var object
*/
protected $orderRepository;
/**
* InvoiceRepository object
*
* @var object
*/
protected $invoiceRepository;
/**
* Create a new helper instance.
*
* @param Webkul\Sales\Repositories\OrderRepository $orderRepository
* @param Webkul\Sales\Repositories\InvoiceRepository $invoiceRepository
* @return void
*/
public function __construct(
OrderRepository $orderRepository,
InvoiceRepository $invoiceRepository
)
{
$this->orderRepository = $orderRepository;
$this->invoiceRepository = $invoiceRepository;
}
/**
* This function process the ipn sent from paypal end
*
* @param array $post
* @return void
*/
public function processIpn($post)
{
$this->post = $post;
if(!$this->postBack())
return;
try {
if (isset($this->post['txn_type']) && 'recurring_payment' == $this->post['txn_type']) {
} else {
$this->getOrder();
$this->processOrder();
}
} catch (\Exception $e) {
throw $e;
}
}
/**
* Load order via ipn invoice id
*
*
* @return void
*/
protected function getOrder()
{
if (empty($this->order)) {
$this->order = $this->orderRepository->findOneByField(['cart_id' => $this->post['invoice']]);
}
}
/**
* Process order and create invoice
*
*
* @return void
*/
protected function processOrder()
{
if($this->post['payment_status'] == 'completed') {
if($this->post['mc_gross'] != $this->order->grand_total) {
} else {
$this->orderRepository->update(['status' => 'processing'], $this->order->id);
if($this->order->canInvoice()) {
$this->invoiceRepository->create($this->prepareInvoiceData());
}
}
}
}
/**
* Prepares order's invoice data for creation
*
*
* @return array
*/
protected function prepareInvoiceData()
{
$invoiceData = [
"order_id" => $this->order->id
];
foreach ($this->order->items as $item) {
$invoiceData['invoice']['items'][$item] = $item->qty_to_invoice;
}
return $invoiceData;
}
/**
* Post back to PayPal to check whether this request is a valid one
*
* @param Zend_Http_Client_Adapter_Interface $httpAdapter
*/
protected function postBack()
{
if(array_key_exists('test_ipn', $this->post) && 1 === (int) $this->post['test_ipn'])
$url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
else
$url = 'https://www.paypal.com/cgi-bin/webscr';
// Set up request to PayPal
$request = curl_init();
curl_setopt_array($request, array
(
CURLOPT_URL => $url,
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => http_build_query(array('cmd' => '_notify-validate') + $this->post),
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_HEADER => FALSE,
));
// Execute request and get response and status code
$response = curl_exec($request);
$status = curl_getinfo($request, CURLINFO_HTTP_CODE);
// Close connection
curl_close($request);
if($status == 200 && $response == 'VERIFIED') {
return true;
}
return false;
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Webkul\Paypal\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
class Controller extends BaseController
{
use DispatchesJobs, ValidatesRequests;
}

View File

@ -0,0 +1,94 @@
<?php
namespace Webkul\Paypal\Http\Controllers;
use Webkul\Checkout\Facades\Cart;
use Webkul\Sales\Repositories\OrderRepository;
use Webkul\Paypal\Helpers\Ipn;
/**
* Paypal Standard controller
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class StandardController extends Controller
{
/**
* OrderRepository object
*
* @var array
*/
protected $orderRepository;
/**
* Ipn object
*
* @var array
*/
protected $ipnHelper;
/**
* Create a new controller instance.
*
* @param Webkul\Attribute\Repositories\OrderRepository $orderRepository
* @return void
*/
public function __construct(
OrderRepository $orderRepository,
Ipn $ipnHelper
)
{
$this->orderRepository = $orderRepository;
$this->ipnHelper = $ipnHelper;
}
/**
* Redirects to the paypal.
*
* @return \Illuminate\Http\Response
*/
public function redirect()
{
return view('paypal::standard-redirect');
}
/**
* Cancel payment from paypal.
*
* @return \Illuminate\Http\Response
*/
public function cancel()
{
session()->flash('error', 'Paypal payment has been canceled.');
return redirect()->route('shop.checkout.cart.index');
}
/**
* Success payment
*
* @return \Illuminate\Http\Response
*/
public function success()
{
$order = $this->orderRepository->create(Cart::prepareDataForOrder());
Cart::deActivateCart();
session()->flash('order', $order);
return redirect()->route('shop.checkout.success');
}
/**
* Paypal Ipn listener
*
* @return \Illuminate\Http\Response
*/
public function ipn()
{
$this->ipnHelper->processIpn(request()->all());
}
}

View File

@ -0,0 +1,14 @@
<?php
Route::group(['middleware' => ['web']], function () {
Route::prefix('paypal/standard')->group(function () {
Route::get('/redirect', 'Webkul\Paypal\Http\Controllers\StandardController@redirect')->name('paypal.standard.redirect');
Route::get('/success', 'Webkul\Paypal\Http\Controllers\StandardController@success')->name('paypal.standard.success');
Route::get('/cancel', 'Webkul\Paypal\Http\Controllers\StandardController@cancel')->name('paypal.standard.cancel');
Route::get('/ipn', 'Webkul\Paypal\Http\Controllers\StandardController@ipn')->name('paypal.standard.ipn');
});
});

View File

@ -0,0 +1,87 @@
<?php
namespace Webkul\Paypal\Payment;
use Illuminate\Support\Facades\Config;
use Webkul\Payment\Payment\Payment;
/**
* Paypal class
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
abstract class Paypal extends Payment
{
/**
* PayPal web URL generic getter
*
* @param array $params
* @return string
*/
public function getPaypalUrl($params = [])
{
return sprintf('https://www.%spaypal.com/cgi-bin/webscr%s',
$this->getConfigData('sandbox') ? 'sandbox.' : '',
$params ? '?' . http_build_query($params) : ''
);
}
/**
* Add order item fields
*
* @param array $fields
* @param int $i
* @return void
*/
protected function addLineItemsFields(&$fields, $i = 1)
{
$cartItems = $this->getCartItems();
foreach ($cartItems as $item) {
foreach ($this->itemFieldsFormat as $modelField => $paypalField) {
$fields[sprintf($paypalField, $i)] = $item->{$modelField};
}
$i++;
}
}
/**
* Add billing address fields
*
* @param array $fields
* @return void
*/
protected function addAddressFields(&$fields)
{
$cart = $this->getCart();
$billingAddress = $cart->billing_address;
$fields = array_merge($fields, [
'city' => $billingAddress->city,
'country' => $billingAddress->country,
'email' => $billingAddress->email,
'first_name' => $billingAddress->first_name,
'last_name' => $billingAddress->last_name,
'zip' => $billingAddress->postcode,
'state' => $billingAddress->state,
'address1' => $billingAddress->address1,
'address2' => $billingAddress->address2,
'address_override' => 1
]);
}
/**
* Checks if line items enabled or not
*
* @param array $fields
* @return void
*/
public function getIsLineItemsEnabled()
{
return true;
}
}

View File

@ -0,0 +1,112 @@
<?php
namespace Webkul\Paypal\Payment;
/**
* Paypal Standard payment method class
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class Standard extends Paypal
{
/**
* Payment method code
*
* @var string
*/
protected $code = 'paypal_standard';
/**
* Line items fields mapping
*
* @var array
*/
protected $itemFieldsFormat = [
'id' => 'item_number_%d',
'name' => 'item_name_%d',
'quantity' => 'quantity_%d',
'price' => 'amount_%d',
];
/**
* Return paypal redirect url
*
* @var string
*/
public function getRedirectUrl()
{
return route('paypal.standard.redirect');
}
/**
* Return form field array
*
* @return array
*/
public function getFormFields()
{
$cart = $this->getCart();
$fields = [
'business' => $this->getConfigData('business_account'),
'invoice' => $cart->id,
'currency_code' => $cart->cart_currency_code,
'paymentaction' => 'sale',
'return' => route('paypal.standard.success'),
'cancel_return' => route('paypal.standard.cancel'),
'notify_url' => route('paypal.standard.ipn'),
'charset' => 'utf-8',
'item_name' => core()->getCurrentChannel()->name,
'amount' => $cart->sub_total,
'tax' => $cart->tax_total,
'shipping' => $cart->selected_shipping_rate->price,
'discount_amount' => $cart->discount
];
if ($this->getIsLineItemsEnabled()) {
$fields = array_merge($fields, array(
'cmd' => '_cart',
'upload' => 1,
));
$this->addLineItemsFields($fields);
$this->addShippingAsLineItems($fields, $cart->items()->count() + 1);
if (isset($fields['tax'])) {
$fields['tax_cart'] = $fields['tax'];
}
if (isset($fields['discount_amount'])) {
$fields['discount_amount_cart'] = $fields['discount_amount'];
}
} else {
$fields = array_merge($fields, array(
'cmd' => '_ext-enter',
'redirect_cmd' => '_xclick',
));
}
$this->addAddressFields($fields);
return $fields;
}
/**
* Add shipping as item
*
* @param array $fields
* @param int $i
* @return void
*/
protected function addShippingAsLineItems(&$fields, $i)
{
$cart = $this->getCart();
$fields[sprintf('item_number_%d', $i)] = $cart->selected_shipping_rate->carrier_title;
$fields[sprintf('item_name_%d', $i)] = 'Shipping';
$fields[sprintf('quantity_%d', $i)] = 1;
$fields[sprintf('amount_%d', $i)] = $cart->selected_shipping_rate->price;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Webkul\Paypal\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Routing\Router;
class PaypalServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*/
public function boot(Router $router)
{
include __DIR__ . '/../Http/routes.php';
$this->loadViewsFrom(__DIR__ . '/../Resources/views', 'paypal');
}
}

View File

@ -0,0 +1,20 @@
<?php $paypalStandard = app('Webkul\Paypal\Payment\Standard') ?>
<body data-gr-c-s-loaded="true" cz-shortcut-listen="true">
You will be redirected to the PayPal website in a few seconds.
<form action="{{ $paypalStandard->getPaypalUrl() }}" id="paypal_standard_checkout" method="POST">
<input value="Click here if you are not redirected within 10 seconds..." type="submit">
@foreach ($paypalStandard->getFormFields() as $name => $value)
<input type="hidden" name="{{ $name }}" value="{{ $value }}">
@endforeach
</form>
<script type="text/javascript">
document.getElementById("paypal_standard_checkout").submit();
</script>
</body>

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AlterOrderTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('orders', function (Blueprint $table) {
$table->integer('cart_id')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AlterInvoiceTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('invoices', function (Blueprint $table) {
$table->string('transaction_id')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -11,10 +11,12 @@ class Order extends Model implements OrderContract
protected $statusLabel = [
'pending' => 'Pending',
'pending_payment' => 'Pending Payment',
'processing' => 'Processing',
'completed' => 'Completed',
'canceled' => 'Canceled',
'closed' => 'Closed',
'fraud' => 'Fraud'
];
/**
@ -138,6 +140,9 @@ class Order extends Model implements OrderContract
*/
public function canShip()
{
if($this->status == 'fraud')
return false;
foreach ($this->items as $item) {
if ($item->qty_to_ship > 0) {
return true;
@ -152,6 +157,9 @@ class Order extends Model implements OrderContract
*/
public function canInvoice()
{
if($this->status == 'fraud')
return false;
foreach ($this->items as $item) {
if ($item->qty_to_invoice > 0) {
return true;
@ -166,6 +174,9 @@ class Order extends Model implements OrderContract
*/
public function canCancel()
{
if($this->status == 'fraud')
return false;
foreach($this->items as $item) {
if ($item->qty_to_cancel > 0) {
return true;

View File

@ -90,7 +90,7 @@ class OrderRepository extends Repository
unset($data['channel']);
}
$data['status'] = core()->getConfigData('paymentmethods.' . $data['payment']['method'] . '.status') ?? 'pending';
$data['status'] = core()->getConfigData('paymentmethods.' . $data['payment']['method'] . '.order_status') ?? 'pending';
$order = $this->model->create(array_merge($data, ['increment_id' => $this->generateIncrementId()]));

View File

@ -127,6 +127,15 @@ class OnepageController extends Controller
$this->validateOrder();
$cart = Cart::getCart();
if($redirectUrl = Payment::getRedirectUrl($cart)) {
return response()->json([
'success' => true,
'redirect_url' => $redirectUrl
]);
}
$order = $this->orderRepository->create(Cart::prepareDataForOrder());
Cart::deActivateCart();
@ -134,7 +143,7 @@ class OnepageController extends Controller
session()->flash('order', $order);
return response()->json([
'success' => true
'success' => true,
]);
}