diff --git a/app/Http/Controllers/ClientPortal/PaymentController.php b/app/Http/Controllers/ClientPortal/PaymentController.php index ae856a240f31..b8a52f6a284f 100644 --- a/app/Http/Controllers/ClientPortal/PaymentController.php +++ b/app/Http/Controllers/ClientPortal/PaymentController.php @@ -95,9 +95,11 @@ class PaymentController extends Controller return $invoice; }); - $invoices->each(function ($invoice) { - InjectSignature::dispatch($invoice, request()->signature); - }); + if ((bool) request()->signature) { + $invoices->each(function ($invoice) { + InjectSignature::dispatch($invoice, request()->signature); + }); + } $payment_methods = auth()->user()->client->getPaymentMethods($amount); $gateway = CompanyGateway::find(request()->input('company_gateway_id')); @@ -116,13 +118,19 @@ class PaymentController extends Controller 'hashed_ids' => request()->invoices, ]; - return $gateway->driver(auth()->user()->client)->processPaymentView($data); + return $gateway + ->driver(auth()->user()->client) + ->setPaymentMethod('App\\PaymentDrivers\\Stripe\\CreditCard') + ->processPaymentView($data); } public function response(Request $request) { $gateway = CompanyGateway::find($request->input('company_gateway_id')); - return $gateway->driver(auth()->user()->client)->processPaymentResponse($request); + return $gateway + ->driver(auth()->user()->client) + ->setPaymentMethod('App\\PaymentDrivers\\Stripe\\CreditCard') + ->processPaymentResponse($request); } } diff --git a/app/Http/Controllers/ClientPortal/PaymentMethodController.php b/app/Http/Controllers/ClientPortal/PaymentMethodController.php index ee24a565dbd8..892b40f11fc2 100644 --- a/app/Http/Controllers/ClientPortal/PaymentMethodController.php +++ b/app/Http/Controllers/ClientPortal/PaymentMethodController.php @@ -48,7 +48,10 @@ class PaymentMethodController extends Controller 'token' => false, ]; - return $gateway->driver(auth()->user()->client)->authorizeCreditCardView($data); + return $gateway + ->driver(auth()->user()->client) + ->setPaymentMethod('App\\PaymentDrivers\\Stripe\\CreditCard') + ->authorizeView($data); } /** @@ -61,7 +64,10 @@ class PaymentMethodController extends Controller { $gateway = auth()->user()->client->getCreditCardGateway(); - return $gateway->driver(auth()->user()->client)->authorizeCreditCardResponse($request); + return $gateway + ->driver(auth()->user()->client) + ->setPaymentMethod('App\\PaymentDrivers\\Stripe\\CreditCard') + ->authorizeCreditCardResponse($request); } /** diff --git a/app/PaymentDrivers/BasePaymentDriver.php b/app/PaymentDrivers/BasePaymentDriver.php index c0d88d1096dd..8b25238c7f4d 100644 --- a/app/PaymentDrivers/BasePaymentDriver.php +++ b/app/PaymentDrivers/BasePaymentDriver.php @@ -48,7 +48,7 @@ class BasePaymentDriver use MakesHash; /* The company gateway instance*/ - protected $company_gateway; + public $company_gateway; /* The Omnipay payment driver instance*/ protected $gateway; diff --git a/app/PaymentDrivers/Stripe/CreditCard.php b/app/PaymentDrivers/Stripe/CreditCard.php new file mode 100644 index 000000000000..678500a7101a --- /dev/null +++ b/app/PaymentDrivers/Stripe/CreditCard.php @@ -0,0 +1,232 @@ +stripe = $stripe; + } + + public function authorizeView(array $data) + { + $intent['intent'] = $this->stripe->getSetupIntent(); + + return render('gateways.stripe.add_credit_card', array_merge($data, $intent)); + } + + public function authorizeResponse($request) + { + $server_response = json_decode($request->input('gateway_response')); + + $gateway_id = $request->input('gateway_id'); + $gateway_type_id = $request->input('gateway_type_id'); + $is_default = $request->input('is_default'); + + $payment_method = $server_response->payment_method; + + $customer = $this->stripe->findOrCreateCustomer(); + + $this->stripe->init(); + + $stripe_payment_method = \Stripe\PaymentMethod::retrieve($payment_method); + $stripe_payment_method_obj = $stripe_payment_method->jsonSerialize(); + $stripe_payment_method->attach(['customer' => $customer->id]); + + $payment_meta = new \stdClass; + $payment_meta->exp_month = $stripe_payment_method_obj['card']['exp_month']; + $payment_meta->exp_year = $stripe_payment_method_obj['card']['exp_year']; + $payment_meta->brand = $stripe_payment_method_obj['card']['brand']; + $payment_meta->last4 = $stripe_payment_method_obj['card']['last4']; + $payment_meta->type = GatewayType::CREDIT_CARD; + + $client_gateway_token = new ClientGatewayToken(); + $client_gateway_token->company_id = $this->stripe->client->company->id; + $client_gateway_token->client_id = $this->stripe->client->id; + $client_gateway_token->token = $payment_method; + $client_gateway_token->company_gateway_id = $this->stripe->company_gateway->id; + $client_gateway_token->gateway_type_id = $gateway_type_id; + $client_gateway_token->gateway_customer_reference = $customer->id; + $client_gateway_token->meta = $payment_meta; + $client_gateway_token->save(); + + if ($is_default == 'true' || $this->stripe->client->gateway_tokens->count() == 1) { + $this->stripe->client->gateway_tokens()->update(['is_default' => 0]); + + $client_gateway_token->is_default = 1; + $client_gateway_token->save(); + } + + return redirect()->route('client.payment_methods.index'); + } + + public function paymentView(array $data) + { + $payment_intent_data = [ + 'amount' => $this->stripe->convertToStripeAmount($data['amount_with_fee'], $this->stripe->client->currency()->precision), + 'currency' => $this->stripe->client->getCurrencyCode(), + 'customer' => $this->stripe->findOrCreateCustomer(), + 'description' => $data['invoices']->pluck('id'), //todo more meaningful description here: + ]; + + if ($data['token']) { + $payment_intent_data['payment_method'] = $data['token']->token; + } else { + $payment_intent_data['setup_future_usage'] = 'off_session'; + // $payment_intent_data['save_payment_method'] = true; + // $payment_intent_data['confirm'] = true; + } + + $data['intent'] = $this->stripe->createPaymentIntent($payment_intent_data); + $data['gateway'] = $this->stripe; + + return render('gateways.stripe.credit_card', $data); + } + + public function paymentResponse($request) + { + $server_response = json_decode($request->input('gateway_response')); + + $state = [ + 'payment_method' => $server_response->payment_method, + 'payment_status' => $server_response->status, + 'save_card' => $request->store_card, + 'gateway_type_id' => $request->payment_method_id, + 'hashed_ids' => $request->hashed_ids, + 'server_response' => $server_response, + ]; + + $invoices = Invoice::whereIn('id', $this->stripe->transformKeys($state['hashed_ids'])) + ->whereClientId($this->stripe->client->id) + ->get(); + + if ($this->stripe->getContact()) { + $client_contact = $this->stripe->getContact(); + } else { + $client_contact = $invoices->first()->invitations->first()->contact; + } + + $this->stripe->init(); + + $state['payment_intent'] = \Stripe\PaymentIntent::retrieve($server_response->id); + $state['customer'] = $state['payment_intent']->customer; + + if ($state['payment_status'] == 'succeeded') { + return $this->processSuccessfulPayment($state); + } + + return $this->processUnsuccessfulPayment($server_response); + } + + private function processSuccessfulPayment($state) + { + $state['charge_id'] = $state['payment_intent']->charges->data[0]->id; + + $this->stripe->init(); + + $state['payment_method'] = PaymentMethod::retrieve($state['payment_method']); + $payment_method_object = $state['payment_method']->jsonSerialize(); + + $state['payment_meta'] = [ + 'exp_month' => $payment_method_object['card']['exp_month'], + 'exp_year' => $payment_method_object['card']['exp_year'], + 'brand' => $payment_method_object['card']['brand'], + 'last4' => $payment_method_object['card']['last4'], + 'type' => $payment_method_object['type'], + ]; + + $payment_type = PaymentType::parseCardType($payment_method_object['card']['brand']); + + if ($state['save_card'] === true) { + $this->saveCard($state); + } + + // Todo: Need to fix this to support payment types other than credit card.... sepa etc etc + if (!isset($state['payment_type'])) { + $state['payment_type'] = PaymentType::CREDIT_CARD_OTHER; + } + + $data = [ + 'payment_method' => $state['charge_id'], // ???? + 'payment_type' => $state['payment_type'], + 'amount' => $state['server_response']->amount, + ]; + + $payment = $this->stripe->createPayment($data); + + $this->stripe->attachInvoices($payment, $state['hashed_ids']); + + $payment->service()->updateInvoicePayment(); + + event(new PaymentWasCreated($payment, $payment->company)); + + $logger_message = [ + 'server_response' => $state['payment_intent'], + 'data' => $data + ]; + + SystemLogger::dispatch($logger_message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->stripe->client); + + return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]); + } + + private function processUnsuccessfulPayment($server_response) + { + PaymentFailureMailer::dispatch($this->stripe->client, $server_response->cancellation_reason, $this->stripe->client->company, $server_response->amount); + + $message = [ + 'server_response' => $server_response, + 'data' => $data // - undefined @todo + ]; + + SystemLogger::dispatch($message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); + + throw new \Exception('Failed to process the payment.', 1); + } + + private function saveCard($state) + { + $state['payment_method']->attach(['customer' => $state['customer']]); + + $company_gateway_token = new ClientGatewayToken(); + $company_gateway_token->company_id = $this->stripe->client->company->id; + $company_gateway_token->client_id = $this->stripe->client->id; + $company_gateway_token->token = $state['payment_method']; + $company_gateway_token->company_gateway_id = $this->stripe->company_gateway->id; + $company_gateway_token->gateway_type_id = $state['gateway_type_id']; + $company_gateway_token->gateway_customer_reference = $state['customer']; + $company_gateway_token->meta = $state['payment_meta']; + $company_gateway_token->save(); + + if ($this->stripe->client->gateway_tokens->count() == 1) { + $this->stripe->client->gateway_tokens()->update(['is_default' => 0]); + + $company_gateway_token->is_default = 1; + $company_gateway_token->save(); + } + } +} diff --git a/app/PaymentDrivers/Stripe/Utilities.php b/app/PaymentDrivers/Stripe/Utilities.php new file mode 100644 index 000000000000..3d01a7961106 --- /dev/null +++ b/app/PaymentDrivers/Stripe/Utilities.php @@ -0,0 +1,16 @@ +company_gateway->getConfigField('apiKey')); } + public function setPaymentMethod(string $method) + { + // Example: setPaymentMethod('App\\PaymentDrivers\\Stripe\\CreditCard'); + + $this->payment_method = new $method($this); + + return $this; + } + /** * Returns the gateway types */ @@ -128,102 +140,39 @@ class StripePaymentDriver extends BasePaymentDriver } /** - * Authorises a credit card for future use. - * - * @param array $data Array of variables needed for the view + * Proxy method to pass the data into payment method authorizeView(). + * + * @param array $data + * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function authorizeCreditCardView(array $data) + public function authorizeView(array $data) { - $intent['intent'] = $this->getSetupIntent(); - - return render('gateways.stripe.add_credit_card', array_merge($data, $intent)); + return $this->payment_method->authorizeView($data); } /** * Processes the gateway response for credit card authorization. * - * @param Request $request The returning request object - * @return view Returns the user to payment methods screen. - * @throws \Stripe\Exception\ApiErrorException + * @param \Illuminate\Http\Request $request The returning request object + + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ public function authorizeCreditCardResponse($request) { - $server_response = json_decode($request->input('gateway_response')); - - $gateway_id = $request->input('gateway_id'); - $gateway_type_id = $request->input('gateway_type_id'); - $is_default = $request->input('is_default'); - - $payment_method = $server_response->payment_method; - - $customer = $this->findOrCreateCustomer(); - - $this->init(); - $stripe_payment_method = \Stripe\PaymentMethod::retrieve($payment_method); - $stripe_payment_method_obj = $stripe_payment_method->jsonSerialize(); - $stripe_payment_method->attach(['customer' => $customer->id]); - - $payment_meta = new \stdClass; - - if ($stripe_payment_method_obj['type'] == 'card') { - $payment_meta->exp_month = $stripe_payment_method_obj['card']['exp_month']; - $payment_meta->exp_year = $stripe_payment_method_obj['card']['exp_year']; - $payment_meta->brand = $stripe_payment_method_obj['card']['brand']; - $payment_meta->last4 = $stripe_payment_method_obj['card']['last4']; - $payment_meta->type = GatewayType::CREDIT_CARD; - } - - $cgt = new ClientGatewayToken; - $cgt->company_id = $this->client->company->id; - $cgt->client_id = $this->client->id; - $cgt->token = $payment_method; - $cgt->company_gateway_id = $this->company_gateway->id; - $cgt->gateway_type_id = $gateway_type_id; - $cgt->gateway_customer_reference = $customer->id; - $cgt->meta = $payment_meta; - $cgt->save(); - - if ($is_default == 'true' || $this->client->gateway_tokens->count() == 1) { - $this->client->gateway_tokens()->update(['is_default'=>0]); - - $cgt->is_default = 1; - $cgt->save(); - } - - return redirect()->route('client.payment_methods.index'); + return $this->payment_method->authorizeResponse($request); } /** * Process the payment with gateway. * * @param array $data + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|void - * @throws \Exception */ public function processPaymentView(array $data) { - $payment_intent_data = [ - 'amount' => $this->convertToStripeAmount($data['amount_with_fee'], $this->client->currency()->precision), - 'currency' => $this->client->getCurrencyCode(), - 'customer' => $this->findOrCreateCustomer(), - 'description' => $data['invoices']->pluck('id'), //todo more meaningful description here: - ]; - - if ($data['token']) { - $payment_intent_data['payment_method'] = $data['token']->token; - } else { - $payment_intent_data['setup_future_usage'] = 'off_session'; -// $payment_intent_data['save_payment_method'] = true; -// $payment_intent_data['confirm'] = true; - } - - - $data['intent'] = $this->createPaymentIntent($payment_intent_data); - - $data['gateway'] = $this; - - return render($this->viewForType($data['payment_method_id']), $data); + return $this->payment_method->paymentView($data); } /** @@ -256,132 +205,7 @@ class StripePaymentDriver extends BasePaymentDriver */ public function processPaymentResponse($request) //We never have to worry about unsuccessful payments as failures are handled at the front end for this driver. { - $server_response = json_decode($request->input('gateway_response')); - - $payment_method = $server_response->payment_method; - $payment_status = $server_response->status; - $save_card = $request->input('store_card'); - - $gateway_type_id = $request->input('payment_method_id'); - $hashed_ids = $request->input('hashed_ids'); - $invoices = Invoice::whereIn('id', $this->transformKeys($hashed_ids)) - ->whereClientId($this->client->id) - ->get(); - /** - * Potential statuses that can be returned - * - * requires_action - * processing - * canceled - * requires_action - * requires_confirmation - * requires_payment_method - * - */ - - if ($this->getContact()) { - $client_contact = $this->getContact(); - } else { - $client_contact = $invoices->first()->invitations->first()->contact; - } - - $this->init(); - $payment_intent = \Stripe\PaymentIntent::retrieve($server_response->id); - $customer = $payment_intent->customer; - - if ($payment_status == 'succeeded') { - - $charge_id = $payment_intent->charges->data[0]->id; - - $this->init(); - $stripe_payment_method = \Stripe\PaymentMethod::retrieve($payment_method); - $stripe_payment_method_obj = $stripe_payment_method->jsonSerialize(); - - $payment_meta = new \stdClass; - - if ($stripe_payment_method_obj['type'] == 'card') { - $payment_meta->exp_month = $stripe_payment_method_obj['card']['exp_month']; - $payment_meta->exp_year = $stripe_payment_method_obj['card']['exp_year']; - $payment_meta->brand = $stripe_payment_method_obj['card']['brand']; - $payment_meta->last4 = $stripe_payment_method_obj['card']['last4']; - $payment_meta->type = $stripe_payment_method_obj['type']; - - $payment_type = PaymentType::parseCardType($stripe_payment_method_obj['card']['brand']); - } - - if ($save_card == 'true') { - $stripe_payment_method->attach(['customer' => $customer]); - - $cgt = new ClientGatewayToken; - $cgt->company_id = $this->client->company->id; - $cgt->client_id = $this->client->id; - $cgt->token = $payment_method; - $cgt->company_gateway_id = $this->company_gateway->id; - $cgt->gateway_type_id = $gateway_type_id; - $cgt->gateway_customer_reference = $customer; - $cgt->meta = $payment_meta; - $cgt->save(); - - if ($this->client->gateway_tokens->count() == 1) { - $this->client->gateway_tokens()->update(['is_default'=>0]); - - $cgt->is_default = 1; - $cgt->save(); - } - } - - //todo need to fix this to support payment types other than credit card.... sepa etc etc - if (!$payment_type) { - $payment_type = PaymentType::CREDIT_CARD_OTHER; - } - - - $data = [ - 'payment_method' => $charge_id, - 'payment_type' => $payment_type, - 'amount' => $server_response->amount, - ]; - - /* Create payment*/ - $payment = $this->createPayment($data); - - /* Link invoices to payment*/ - $this->attachInvoices($payment, $hashed_ids); - - $payment->service()->UpdateInvoicePayment(); - - event(new PaymentWasCreated($payment, $payment->company)); - - SystemLogger::dispatch( - [ - 'server_response' => $payment_intent, - 'data' => $data - ], - SystemLog::CATEGORY_GATEWAY_RESPONSE, - SystemLog::EVENT_GATEWAY_SUCCESS, - SystemLog::TYPE_STRIPE, - $this->client - ); - - return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]); - } else { - - PaymentFailureMailer::dispatch($this->client, $server_response->cancellation_reason, $this->client->company, $server_response->amount); - - /*Fail and log*/ - SystemLogger::dispatch( - [ - 'server_response' => $server_response, - 'data' => $data - ], - SystemLog::CATEGORY_GATEWAY_RESPONSE, - SystemLog::EVENT_GATEWAY_FAILURE, - SystemLog::TYPE_STRIPE, - $this->client - ); - - throw new \Exception("Failed to process payment", 1); - } + return $this->payment_method->paymentResponse($request); } public function createPayment($data) :Payment @@ -400,15 +224,6 @@ class StripePaymentDriver extends BasePaymentDriver return $payment; } - private function convertFromStripeAmount($amount, $precision) - { - return $amount / pow(10, $precision); - } - - private function convertToStripeAmount($amount, $precision) - { - return $amount * pow(10, $precision); - } /** * Creates a new String Payment Intent *