Skip to content

Instantly share code, notes, and snippets.

@themodernpk
Created February 18, 2020 09:38
Show Gist options
  • Save themodernpk/4dadd4238635561091f9855cd3a0ca1a to your computer and use it in GitHub Desktop.
Save themodernpk/4dadd4238635561091f9855cd3a0ca1a to your computer and use it in GitHub Desktop.
Paypal.php
<?php
namespace Modules\Assignable\Http\Controllers\Frontend;
use Anouar\Paypalpayment\Facades\PaypalPayment;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller;
use Modules\Assignable\Entities\Cart;
use Modules\Assignable\Entities\CartItem;
use Modules\Business\Entities\BdCompany;
use Modules\Clients\Entities\ClientUser;
use Modules\Core\Entities\User;
use Softon\Indipay\Facades\Indipay;
use PayPal\Api\Payment;
use PayPal\Api\PaymentExecution;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Rest\ApiContext;
class CheckoutController extends Controller
{
public $data;
function __construct( Request $request ) {
$this->data = new \stdClass();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
public function index(Request $request)
{
$cart_block = Cart::getCartCalculation($request, true);
$this->data->cart_block = $cart_block;
//find billing information
$this->data->billing = BdCompany::where('user_id', \Auth::user()->id)->first();
if(!$this->data->billing)
{
$this->data->billing = new BdCompany();
$this->data->billing->user_id = \Auth::user()->id;
$this->data->billing->save();
}
$this->data->show_billing_form = false;
if(
!$this->data->billing->name
&& !$this->data->billing->country
&& !$this->data->billing->address
&& !$this->data->billing->city
&& !$this->data->billing->pin
)
{
$this->data->show_billing_form = true;
}
$this->data->body_class = "";
$this->data->title = "Checkout - Assignable.io";
$view = themeViewFolder().".checkout";
return view( $view )
->with( "data", $this->data );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
public function saveBilling(Request $request)
{
$response = null;
$rules = array(
'user_name' => 'required|max:255',
'address' => 'required|max:255',
'city' => 'required|max:100',
'state' => 'required|max:100',
'country' => 'required|max:255',
'pin' => 'required|max:20',
);
$validator = \Validator::make( $request->all(), $rules);
if ( $validator->fails() ) {
$errors = errorsToArray($validator->errors());
$response['status'] = 'failed';
$response['errors'] = $errors;
return response()->json($response);
}
//find user company
$company = BdCompany::where('user_id', \Auth::user()->id)->first();
if(!$company)
{
$company = new BdCompany();
}
$company->fill($request->all());
$company->save();
$user = \Auth::user();
if(\Auth::user()->name != $request->get('user_name'))
{
if($user)
{
$user->name = $request->get('user_name');
$user->save();
}
}
$response['status'] = 'success';
$response['data']['company'] = $company;
$response['data']['user_name'] = $user->name;
$response['messages'][] = "Successfully Saved";
return response()->json($response);
}
//-----------------------------------------------------------------------------
public function processPaypal(Request $request)
{
/*
* To resolve error like following:
* sizeof(): Parameter must be an array or an object that implements Countable
* find the solution at: https://github.com/anouarabdsslm/laravel-paypalpayment/issues/139
* open "vendor\paypal\rest-api-sdk-php\lib\PayPal\Common\PayPalModel.php"
* replace
* } elseif (sizeof($v) <= 0 && is_array($v) ) {
* to
* } elseif (is_array($v) && sizeof($v) <= 0) {
*/
$cart_uid = Cart::getCartUid($request);
$cart = Cart::where('uid', $cart_uid)->whereNull('is_paid')->first();
if(!$cart)
{
$response['status'] = 'failed';
$response['errors'][] = 'No item in the cart';
return response()->json($response);
}
$cart_items = CartItem::where('as_cart_id', $cart->id)
->with(['task' => function($t){
$t->select('id', 'as_technology_id', 'title', 'slug', 'excerpt');
}])->get();
if(!$cart_items)
{
$response['status'] = 'failed';
$response['errors'][] = 'No item in the cart';
return response()->json($response);
}
$company = BdCompany::where('user_id', \Auth::user()->id)->first();
//address validation
$company_array = $company->toArray();
$rules = array(
'address' => 'required',
//'address_extended' => 'required',
'city' => 'required',
'state' => 'required',
'pin' => 'required',
'country' => 'required|size:2',
);
$messages = [
'country.size' => "Country code of 2 digits are allowed. For India it is IN"
];
$validator = \Validator::make( $company_array, $rules, $messages);
if ( $validator->fails() ) {
$errors = errorsToArray($validator->errors());
$response['status'] = 'failed';
$response['errors'] = $errors;
return response()->json($response);
}
// ### Address
// Base Address object used as shipping or billing
// address in a payment. [Optional]
/*
* Note: setCountryCode accepts country code like "IN" instead of "india"
*/
$shippingAddress= Paypalpayment::shippingAddress();
$shippingAddress->setLine1($company->address)
->setLine2($company->address_extended)
->setCity($company->city)
->setState($company->state)
->setPostalCode($company->pin)
->setCountryCode($company->country)
->setPhone(\Auth::user()->mobile)
->setRecipientName(\Auth::user()->name);
// ### Payer
// A resource representing a Payer that funds a payment
// Use the List of `FundingInstrument` and the Payment Method
// as 'credit_card'
$payer = Paypalpayment::payer();
$payer->setPaymentMethod("paypal");
if($cart_items)
{
$i = 0;
$paypal_items = [];
$item_total = 0;
foreach ($cart_items as $item)
{
$paypal_items[$i] = Paypalpayment::item();
$paypal_items[$i]->setName($item->task->title)
->setCurrency('USD')
->setSku('ASSIGN#'.$item->task->id)
->setQuantity($item->quantity)
->setPrice($item->payable);
$item_total = $item_total+$item->payable;
$i++;
}
}
$itemList = Paypalpayment::itemList();
$itemList->setItems($paypal_items)
->setShippingAddress($shippingAddress);
//Payment Amount
$amount = Paypalpayment::amount();
$amount->setCurrency("USD")
// the total is $17.8 = (16 + 0.6) * 1 ( of quantity) + 1.2 ( of Shipping).
->setTotal(round($cart->payable));
// ### Transaction
// A transaction defines the contract of a
// payment - what is the payment for and who
// is fulfilling it. Transaction is created with
// a `Payee` and `Amount` types
$transaction = Paypalpayment::transaction();
$transaction->setAmount($amount)
->setItemList($itemList)
->setDescription("Assignable.io Cart ID: ".$cart->uid)
->setInvoiceNumber(uniqid());
// ### Payment
// A Payment Resource; create one using
// the above types and intent as 'sale'
$redirectUrls = Paypalpayment::redirectUrls();
$redirectUrls->setReturnUrl(url("/payments/status/success"))
->setCancelUrl(url("/payments/status/fails"));
$payment = Paypalpayment::payment();
$payment->setIntent("sale")
->setPayer($payer)
->setRedirectUrls($redirectUrls)
->setTransactions([$transaction]);
try {
// ### Create Payment
// Create a payment by posting to the APIService
// using a valid ApiContext
// The return object contains the status;
$payment->create(Paypalpayment::apiContext());
$approved_url = $payment->getApprovalLink();
$approved_url = parse_url($approved_url);
parse_str($approved_url['query'], $approved_query_params);
$cart->payment_attempts =$cart->payment_attempts+1;
$cart->payment_method = 'paypal';
$cart->payment_id = $payment->id;
$cart->payment_url = $payment->getApprovalLink();
$cart->payment_status = 1;
$cart->meta = json_encode($approved_query_params);
$cart->save();
} catch (\Exception $ex) {
$subject = "Assignable Payment Error Occurred";
$message = "Following error occurred:";
$message .= "<hr/>";
$message .= "- ".$ex->getMessage()."<br/>";
$message .= "- ".$ex->getFile()."<br/>";
$message .= "- ".$ex->getCode()."<br/>";
$message .= "- ".$ex->getLine()."<br/>";
$message .= "<hr/>";
$message .= url()->current();
User::notifyAdmins($subject, $message);
$response['status'] = 'failed';
$response['errors'][]= $ex->getMessage();
return response()->json($response);
}
$response['status'] = 'success';
$response['data']['redirect_url'] = $payment->getApprovalLink();
$response['messages'][] = 'Redirecting you to Paypal';
return response()->json($response);
//return response()->json([$payment->toArray(), 'approval_url' => $payment->getApprovalLink()], 200);
}
//-----------------------------------------------------------------------------
public function getApiContext( $clientId, $clientSecret, $paypal_mode ) {
// #### SDK configuration
// Register the sdk_config.ini file in current directory
// as the configuration source.
/*
if(!defined("PP_CONFIG_PATH")) {
define("PP_CONFIG_PATH", __DIR__);
}
*/
// ### Api context
// Use an ApiContext object to authenticate
// API calls. The clientId and clientSecret for the
// OAuthTokenCredential class can be retrieved from
// developer.paypal.com
$apiContext = new ApiContext(
new OAuthTokenCredential(
$clientId,
$clientSecret
)
);
// Comment this line out and uncomment the PP_CONFIG_PATH
// 'define' block if you want to use static file
// based configuration
$apiContext->setConfig(
array(
'mode' => $paypal_mode,
'log.LogEnabled' => true,
'log.FileName' => '../PayPal.log',
'log.LogLevel' => 'DEBUG',
// PLEASE USE `INFO` LEVEL FOR LOGGING IN LIVE ENVIRONMENTS
'cache.enabled' => true,
//'cache.FileName' => '/PaypalCache' // for determining paypal cache directory
// 'http.CURLOPT_CONNECTTIMEOUT' => 30
// 'http.headers.PayPal-Partner-Attribution-Id' => '123123123'
//'log.AdapterFactory' => '\PayPal\Log\DefaultLogFactory' // Factory class implementing \PayPal\Log\PayPalLogFactory
)
);
// Partner Attribution Id
// Use this header if you are a PayPal partner. Specify a unique BN Code to receive revenue attribution.
// To learn more or to request a BN Code, contact your Partner Manager or visit the PayPal Partner Portal
// $apiContext->addRequestHeader('PayPal-Partner-Attribution-Id', '123123123');
return $apiContext;
}
//-----------------------------------------------------------------------------
public function paymentFailed(Request $request)
{
try {
$cart = Cart::where('meta', 'like', '%"token":"'.$request->token.'"%')
->first();
$payment = Paypalpayment::getById($cart->payment_id, Paypalpayment::apiContext());
$payment_details = json_encode($payment->toArray(), JSON_PRETTY_PRINT);
$subject = "Assignable Payment Failed";
$message = "Following payment is failed:";
$message .= "<hr/>";
$message .= "- ".nl2br($payment_details)."<br/>";
$message .= "<hr/>";
$message .= url()->current();
User::notifyAdmins($subject, $message);
$errors = [
"Your payment is failed. You can try again or our support team will get back to you soon."
];
return redirect()->route('as.checkout')->withErrors($errors);
} catch (\Exception $ex) {
$subject = "Assignable Payment Error Occurred";
$message = "Following Assignable Payment error occurred:";
$message .= "<hr/>";
$message .= "- ".$ex->getMessage()."<br/>";
$message .= "- ".$ex->getFile()."<br/>";
$message .= "- ".$ex->getCode()."<br/>";
$message .= "- ".$ex->getLine()."<br/>";
$message .= "<hr/>";
$message .= url()->current();
User::notifyAdmins($subject, $message);
$errors = [
"Your payment is failed. You can try again or our support team will get back to you soon."
];
return redirect()->route('as.checkout')->withErrors($errors);
}
}
//-----------------------------------------------------------------------------
public function paymentSuccess(Request $request)
{
if ( !$request->has( 'paymentId' ) && !$request->has( 'PayerID' ) ) {
$errors = [
"Your payment is failed. You can try again or our support team will get back to you soon."
];
return redirect()->route('as.checkout')->withErrors($errors);
}
$paymentId = $request->paymentId;
$payerId = $request->PayerID;
$payment_details = $payment = Paypalpayment::getById($paymentId, Paypalpayment::apiContext());;
$payment_details = $payment_details->toArray();
$apiContext = $this->getApiContext( env( 'PAYPAL_CLIENT_ID'), env( 'PAYPAL_CLIENT_SECRET'), env('PAYPAL_MODE') );
$payment = Payment::get( $paymentId, $apiContext );
$execution = new PaymentExecution();
$execution->setPayerId( $payerId );
try {
$cart_uid = Cart::getCartUid($request);
if (!$cart_uid) {
return redirect()->route('as.welcome')->withErrors(['Cart is empty']);
}
$order_id = Cart::generateOrderID();
//on success perform following actions
// 1) Generate order id, Set cart & items as paid
$cart = Cart::where('uid', $cart_uid)->first();
$cart->is_paid = 1;
$cart->payment_status = 2;
$cart->payment_date = \Carbon::now();
$cart->payment_meta = null;
$cart->paid = $cart->payable; //TODO::this amount should come from payment gateway
$cart->order_id = $order_id;
$cart->status = 2; //paid
$cart->save();
$cart_items = CartItem::where('as_cart_id', $cart->id)->with('task')->get();
foreach ($cart_items as $item) {
$item->status = 2;
$item->save();
}
//delete the cart id from cookie
\Cookie::queue(\Cookie::forget('cart_uid'));
// 2) Create a new project if no project exist for the user
//$response = Cart::processCart($cart_uid);
\Session::flash('flash_success', "Payment was successful");
return redirect()->route('as.checkout.process.order');
} catch ( \Exception $e ) {
$errors = [
"Your payment is failed. You can try again or our support team will get back to you soon."
];
return redirect()->route('as.checkout')->withErrors($errors);
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
public function processPayUMoney(Request $request)
{
$cart_uid = Cart::getCartUid($request);
$cart = Cart::where('uid', $cart_uid)->whereNull('is_paid')->first();
if(!$cart)
{
$response['status'] = 'failed';
$response['errors'][] = 'No item in the cart';
return response()->json($response);
}
$cart_items = CartItem::where('as_cart_id', $cart->id)
->with(['task' => function($t){
$t->select('id', 'as_technology_id', 'title', 'slug', 'excerpt');
}])->get();
if(!$cart_items)
{
$response['status'] = 'failed';
$response['errors'][] = 'No item in the cart';
return response()->json($response);
}
$cart->payment_method = 'PayUMoney';
$cart->order_id = Cart::generateOrderID();
$cart->save();
$user = User::find(\Auth::user()->id);
$name_d = explode(" ", $user->name);
$first_name = $user->name;
$last_name = " ";
if(isset($name_d[0]))
{
$first_name = $name_d[0];
}
if(isset($name_d[1]))
{
$last_name = $name_d[1];
}
$parameters = [
'tid' => uniqid(),
'order_id' => $cart->order_id,
'amount' => $cart->payable,
'firstname' => $first_name,
'lastname' => $last_name,
'email' => $user->email,
'phone' => $user->mobile,
'productinfo' => "Order ID: ".$cart->order_id,
'udf1' => $cart->order_id,
];
$order = Indipay::gateway('PayUMoney')->prepare($parameters);
return Indipay::process($order);
//return response()->json($response);
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
public function payuFailed(Request $request)
{
try {
$subject = "Assignable Payment Failed";
$message = "Following payment is failed:";
$cart = Cart::where('order_id', $request->get('udf1'))
->first();
if(!$cart)
{
$message .= "<hr/>";
$message .= "- ".nl2br($request->all())."<br/>";
$message .= "<hr/>";
$message .= url()->current();
User::notifyAdmins($subject, $message);
$errors = [
"Your payment is failed. You can try again or our support team will get back to you soon."
];
return redirect()->route('as.checkout')->withErrors($errors);
}
$message .= "<hr/>";
$message .= "- ".nl2br($request->all())."<br/>";
$message .= "- ".nl2br($cart->toArray())."<br/>";
$message .= "<hr/>";
$message .= url()->current();
User::notifyAdmins($subject, $message);
$errors = [
"Your payment is failed. You can try again or our support team will get back to you soon."
];
return redirect()->route('as.checkout')->withErrors($errors);
} catch (\Exception $ex) {
$subject = "Assignable Payment Error Occurred";
$message = "Following Assignable Payment error occurred:";
$message .= "<hr/>";
$message .= "- ".$ex->getMessage()."<br/>";
$message .= "- ".$ex->getFile()."<br/>";
$message .= "- ".$ex->getCode()."<br/>";
$message .= "- ".$ex->getLine()."<br/>";
$message .= "<hr/>";
$message .= url()->current();
User::notifyAdmins($subject, $message);
$errors = [
"Your payment is failed. You can try again or our support team will get back to you soon."
];
return redirect()->route('as.checkout')->withErrors($errors);
}
}
//-----------------------------------------------------------------------------
public function payuSuccess(Request $request)
{
$cart_uid = Cart::getCartUid($request);
if(!$cart_uid)
{
return redirect()->route('as.welcome')->withErrors(['Cart is empty']);
}
//on success perform following actions
// 1) Generate order id, Set cart & items as paid
$cart = Cart::where('uid', $cart_uid)->first();
$cart->is_paid = 1;
$cart->payment_status = 2;
$cart->payment_date = \Carbon::now();
//$cart->payment_meta = $request->all();
$cart->paid = $cart->payable; //TODO::this amount should come from payment gateway
$cart->status = 2; //paid
$cart->save();
$cart_items = CartItem::where('as_cart_id', $cart->id)->with('task')->get();
foreach ($cart_items as $item)
{
$item->status = 2;
$item->save();
}
//delete the cart id from cookie
\Cookie::queue(\Cookie::forget('cart_uid'));
// 2) Create a new project if now project exist for the user
//$response = Cart::processCart($cart_uid);
\Session::flash('flash_success', "Payment was successful");
return redirect()->route('as.checkout.process.order');
}
//-----------------------------------------------------------------------------
public function processInstamojo(Request $request)
{
try{
$cart_uid = Cart::getCartUid($request);
$cart = Cart::where('uid', $cart_uid)->whereNull('is_paid')->first();
if(!$cart)
{
$response['status'] = 'failed';
$response['errors'][] = 'No item in the cart';
return response()->json($response);
}
$cart_items = CartItem::where('as_cart_id', $cart->id)
->with(['task' => function($t){
$t->select('id', 'as_technology_id', 'title', 'slug', 'excerpt');
}])->get();
if(!$cart_items)
{
$response['status'] = 'failed';
$response['errors'][] = 'No item in the cart';
return response()->json($response);
}
$cart->payment_method = 'InstaMojo';
$cart->order_id = Cart::generateOrderID();
$cart->save();
$user = User::find(\Auth::user()->id);
$name_d = explode(" ", $user->name);
$first_name = $user->name;
$last_name = " ";
if(isset($name_d[0]))
{
$first_name = $name_d[0];
}
if(isset($name_d[1]))
{
$last_name = $name_d[1];
}
$parameters = [
'tid' => uniqid(),
'order_id' => $cart->order_id,
'amount' => $cart->payable,
'firstname' => $first_name,
'lastname' => $last_name,
'email' => $user->email,
'phone' => str_replace("-", "", $user->mobile),
'purpose' => "Order ID: ".$cart->order_id,
'udf1' => $cart->order_id,
];
$validator = \Validator::make($parameters, [
'purpose' => 'required|max:30',
'amount' => 'required|numeric|between:9,200000',
'buyer_name' => 'max:100',
'email' => 'email|max:75',
'phone' => 'digits:10',
]);
if ( $validator->fails() ) {
return redirect()->back()->withErrors(errorsToArray($validator->errors()));
}
$order = Indipay::gateway('InstaMojo')->prepare($parameters);
$cart->payment_id = $order->response->payment_request->id;
$cart->save();
return Indipay::process($order);
}catch(Exception $e)
{
$response['status'] = 'failed';
$response['errors'][] = $e->getMessage();
return redirect()->back()->withErrors([$e->getMessage()]);
}
}
//-----------------------------------------------------------------------------
public function instaStatus(Request $request)
{
if($request->has('payment_status') && $request->get('payment_status') == 'Credit')
{
//on success perform following actions
// 1) Generate order id, Set cart & items as paid
$cart = Cart::where('payment_id', $request->get('payment_request_id'))->first();
$cart->is_paid = 1;
$cart->payment_status = 2;
$cart->payment_date = \Carbon::now();
$cart->payment_meta = $request->all();
$cart->paid = $cart->payable; //TODO::this amount should come from payment gateway
$cart->status = 2; //paid
$cart->save();
$cart_items = CartItem::where('as_cart_id', $cart->id)->with('task')->get();
foreach ($cart_items as $item)
{
$item->status = 2;
$item->save();
}
//delete the cart id from cookie
\Cookie::queue(\Cookie::forget('cart_uid'));
// 2) Create a new project if no project exist for the user
//$response = Cart::processCart($cart_uid);
\Session::flash('flash_success', "Payment was successful");
return redirect()->route('as.checkout.process.order');
} else
{
try {
$subject = "Assignable Payment Failed";
$message = "Following payment is failed:";
$cart = Cart::where('payment_id', $request->get('payment_request_id'))
->first();
if(!$cart)
{
$message .= "<hr/>";
$message .= "- ".nl2br($request->all())."<br/>";
$message .= "<hr/>";
$message .= url()->current();
User::notifyAdmins($subject, $message);
$errors = [
"Your payment is failed. You can try again or our support team will get back to you soon."
];
return redirect()->route('as.checkout')->withErrors($errors);
}
$message .= "<hr/>";
$message .= "- ".nl2br($request->all())."<br/>";
$message .= "- ".nl2br($cart->toArray())."<br/>";
$message .= "<hr/>";
$message .= url()->current();
User::notifyAdmins($subject, $message);
$errors = [
"Your payment is failed. You can try again or our support team will get back to you soon."
];
return redirect()->route('as.checkout')->withErrors($errors);
} catch (\Exception $ex) {
$subject = "Assignable Payment Error Occurred";
$message = "Following Assignable Payment error occurred:";
$message .= "<hr/>";
$message .= "- ".$ex->getMessage()."<br/>";
$message .= "- ".$ex->getFile()."<br/>";
$message .= "- ".$ex->getCode()."<br/>";
$message .= "- ".$ex->getLine()."<br/>";
$message .= "<hr/>";
$message .= url()->current();
User::notifyAdmins($subject, $message);
$errors = [
"Your payment is failed. You can try again or our support team will get back to you soon."
];
return redirect()->route('as.checkout')->withErrors($errors);
}
}
}
//-----------------------------------------------------------------------------
public function processOrder(Request $request)
{
//here is any unprocess cart available
$this->data->body_class = "";
$this->data->title = "Unprocessed Order - Assignable.io";
$carts = Cart::whereNotNull('is_paid')
->where('user_id', \Auth::user()->id)
->with(['items' => function($q){
$q->with(['package', 'task' => function($t){
$t->select('id', 'title', 'slug');
}]);
}])
->where('status', 2)->get();
$this->data->carts = $carts;
$this->data->projects = $projects = ClientUser::getProjects();
$view = themeViewFolder().".checkout-process-order";
return view( $view )
->with( "data", $this->data );
}
//-----------------------------------------------------------------------------
public function assignProject(Request $request)
{
if(!$request->has('project_name') && !$request->has('collab_project_id'))
{
$response['status'] = 'failed';
$response['errors'][] = 'Enter new project name or select from existing project';
return response()->json($response);
}
if($request->has('project_name') && $request->has('collab_project_id'))
{
$response['status'] = 'failed';
$response['errors'][] = 'Enter new project name or select from existing project';
return response()->json($response);
}
$carts = Cart::whereNotNull('is_paid')
->where('user_id', \Auth::user()->id)
->with(['items' => function($q){
$q->with(['package', 'task' => function($t){
$t->select('id', 'title', 'slug');
}]);
}])
->where('status', 2)->get();
$response['status'] = 'failed';
$response['errors'][] = 'Unable to to find any unprocessed orders';
if($carts)
{
foreach ($carts as $cart)
{
if($request->has('project_name'))
{
$response = Cart::processCart($cart->uid, $request->get('project_name'));
}
if($request->has('collab_project_id'))
{
$response = Cart::processCart($cart->uid, null, $request->get('collab_project_id'));
}
}
if($response['status'] == 'success')
{
$response['messages'][] = "Successfully Assigned";
$response['data']['redirect_url'] = url()->route('client.dashboard');
}
}
return response()->json($response);
}
//-----------------------------------------------------------------------------
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment