1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-06 03:02:34 +01:00
invoiceninja/app/PaymentDrivers/StripePaymentDriver.php

402 lines
12 KiB
PHP
Raw Normal View History

2019-09-05 09:00:12 +02:00
<?php
2020-06-24 16:07:12 +02:00
2019-09-05 09:00:12 +02:00
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
2019-09-05 09:00:12 +02:00
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers;
2019-10-01 11:59:32 +02:00
use App\Events\Payment\PaymentWasCreated;
use App\Factory\PaymentFactory;
2020-06-27 17:39:28 +02:00
use App\Http\Requests\Payments\PaymentWebhookRequest;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
2019-09-17 07:42:10 +02:00
use App\Models\ClientGatewayToken;
2020-06-27 17:39:28 +02:00
use App\Models\Company;
use App\Models\CompanyGateway;
2019-09-08 14:13:55 +02:00
use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\Stripe\ACH;
use App\PaymentDrivers\Stripe\Alipay;
2020-07-14 14:50:16 +02:00
use App\PaymentDrivers\Stripe\Charge;
use App\PaymentDrivers\Stripe\CreditCard;
use App\PaymentDrivers\Stripe\SOFORT;
2020-06-01 14:17:29 +02:00
use App\PaymentDrivers\Stripe\Utilities;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
2019-09-13 00:33:48 +02:00
use Stripe\PaymentIntent;
2019-09-14 14:34:05 +02:00
use Stripe\SetupIntent;
2019-09-05 14:42:26 +02:00
use Stripe\Stripe;
2019-09-05 09:00:12 +02:00
class StripePaymentDriver extends BasePaymentDriver
{
2020-06-01 14:17:29 +02:00
use MakesHash, Utilities;
2020-07-15 07:05:02 +02:00
public $refundable = true;
2019-09-05 09:00:12 +02:00
2020-07-15 07:05:02 +02:00
public $token_billing = true;
2020-07-15 07:05:02 +02:00
public $can_authorise_credit_card = true;
2019-09-15 13:40:46 +02:00
protected $customer_reference = 'customerReferenceParam';
2020-06-01 14:03:18 +02:00
protected $payment_method;
public static $methods = [
GatewayType::CREDIT_CARD => CreditCard::class,
GatewayType::BANK_TRANSFER => ACH::class,
GatewayType::ALIPAY => Alipay::class,
GatewayType::SOFORT => SOFORT::class,
GatewayType::APPLE_PAY => 1,
GatewayType::SEPA => 1,
];
/**
* Methods in this class are divided into
* two separate streams
*
* 1. Omnipay Specific
* 2. Stripe Specific
*
* Our Stripe integration is deeper than
* other gateways and therefore
* relies on direct calls to the API
*/
/************************************** Stripe API methods **********************************************************/
2019-09-05 14:42:26 +02:00
2019-09-17 12:27:48 +02:00
/**
* Initializes the Stripe API
* @return void
*/
2020-06-24 16:07:12 +02:00
public function init(): void
{
Stripe::setApiKey($this->company_gateway->getConfigField('apiKey'));
}
2019-09-17 12:27:48 +02:00
public function setPaymentMethod($payment_method_id)
2020-06-01 14:03:18 +02:00
{
$class = self::$methods[$payment_method_id];
2020-06-24 16:07:12 +02:00
$this->payment_method = new $class($this);
2020-06-01 14:03:18 +02:00
return $this;
}
/**
* Returns the gateway types
*/
2020-06-24 16:07:12 +02:00
public function gatewayTypes(): array
2019-09-08 14:13:55 +02:00
{
$types = [
GatewayType::CREDIT_CARD,
2019-09-25 04:07:33 +02:00
//GatewayType::TOKEN,
2019-09-08 14:13:55 +02:00
];
2020-03-23 18:10:42 +01:00
if ($this->company_gateway->getSofortEnabled() && $this->invitation && $this->client() && isset($this->client()->country) && in_array($this->client()->country, ['AUT', 'BEL', 'DEU', 'ITA', 'NLD', 'ESP'])) {
2019-09-08 14:13:55 +02:00
$types[] = GatewayType::SOFORT;
}
2019-09-08 14:13:55 +02:00
if ($this->company_gateway->getAchEnabled()) {
$types[] = GatewayType::BANK_TRANSFER;
}
2019-09-08 14:13:55 +02:00
if ($this->company_gateway->getSepaEnabled()) {
2019-09-08 14:13:55 +02:00
$types[] = GatewayType::SEPA;
}
2020-03-23 18:10:42 +01:00
if ($this->company_gateway->getBitcoinEnabled()) {
$types[] = GatewayType::CRYPTO;
}
2020-03-23 18:10:42 +01:00
if ($this->company_gateway->getAlipayEnabled()) {
2019-09-08 14:13:55 +02:00
$types[] = GatewayType::ALIPAY;
}
2020-03-23 18:10:42 +01:00
if ($this->company_gateway->getApplePayEnabled()) {
2019-09-08 14:13:55 +02:00
$types[] = GatewayType::APPLE_PAY;
}
2020-03-23 18:10:42 +01:00
2019-09-08 14:13:55 +02:00
return $types;
2019-09-12 13:46:09 +02:00
}
2019-09-18 04:39:53 +02:00
public function viewForType($gateway_type_id)
2019-09-12 13:46:09 +02:00
{
switch ($gateway_type_id) {
case GatewayType::CREDIT_CARD:
case GatewayType::TOKEN:
2020-03-23 18:10:42 +01:00
return 'gateways.stripe.credit_card';
break;
case GatewayType::SOFORT:
2020-03-23 18:10:42 +01:00
return 'gateways.stripe.sofort';
break;
case GatewayType::BANK_TRANSFER:
2020-03-23 18:10:42 +01:00
return 'gateways.stripe.ach';
break;
case GatewayType::SEPA:
2020-03-23 18:10:42 +01:00
return 'gateways.stripe.sepa';
break;
case GatewayType::CRYPTO:
case GatewayType::ALIPAY:
case GatewayType::APPLE_PAY:
2020-03-23 18:10:42 +01:00
return 'gateways.stripe.other';
break;
default:
break;
}
2019-09-08 14:13:55 +02:00
}
2019-09-13 00:33:48 +02:00
/**
2020-06-01 14:03:18 +02:00
* Proxy method to pass the data into payment method authorizeView().
*
* @param array $data
*
2020-03-23 18:10:42 +01:00
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
2020-06-01 14:03:18 +02:00
public function authorizeView(array $data)
2019-09-14 14:34:05 +02:00
{
2020-06-01 14:03:18 +02:00
return $this->payment_method->authorizeView($data);
2019-09-16 13:03:25 +02:00
}
/**
2020-03-23 18:10:42 +01:00
* Processes the gateway response for credit card authorization.
*
2020-06-01 14:29:41 +02:00
* @param \Illuminate\Http\Request $request The returning request object
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
2020-06-26 13:25:58 +02:00
public function authorizeResponse($request)
2019-09-16 13:03:25 +02:00
{
2020-06-01 14:14:41 +02:00
return $this->payment_method->authorizeResponse($request);
2019-09-14 14:34:05 +02:00
}
2019-09-25 04:07:33 +02:00
/**
2020-03-23 18:10:42 +01:00
* Process the payment with gateway.
*
2020-03-23 18:10:42 +01:00
* @param array $data
2020-06-01 14:29:41 +02:00
2020-03-23 18:10:42 +01:00
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|void
2019-09-25 04:07:33 +02:00
*/
public function processPaymentView(array $data)
2019-09-25 04:07:33 +02:00
{
2020-06-01 14:29:41 +02:00
return $this->payment_method->paymentView($data);
2019-09-25 04:07:33 +02:00
}
/**
* Payment Intent Reponse looks like this
+"id": "pi_1FMR7JKmol8YQE9DuC4zMeN3"
+"object": "payment_intent"
+"allowed_source_types": array:1 [
0 => "card"
]
+"amount": 2372484
+"canceled_at": null
+"cancellation_reason": null
+"capture_method": "automatic"
+"client_secret": "pi_1FMR7JKmol8YQE9DuC4zMeN3_secret_J3yseWJG6uV0MmsrAT1FlUklV"
+"confirmation_method": "automatic"
+"created": 1569381877
+"->currency()": "usd"
+"description": "[3]"
+"last_payment_error": null
+"livemode": false
+"next_action": null
+"next_source_action": null
+"payment_method": "pm_1FMR7ZKmol8YQE9DQWqPuyke"
+"payment_method_types": array:1 []
+"receipt_email": null
+"setup_future_usage": "off_session"
+"shipping": null
+"source": null
+"status": "succeeded"
2020-06-24 16:07:12 +02:00
*/
2019-10-03 14:17:48 +02:00
public function processPaymentResponse($request) //We never have to worry about unsuccessful payments as failures are handled at the front end for this driver.
{
2020-06-01 16:19:03 +02:00
return $this->payment_method->paymentResponse($request);
2019-10-01 11:59:32 +02:00
}
2020-06-24 16:07:12 +02:00
public function createPayment($data, $status = Payment::STATUS_COMPLETED): Payment
2019-10-01 11:59:32 +02:00
{
$payment = parent::createPayment($data, $status);
2019-10-01 11:59:32 +02:00
$client_contact = $this->getContact();
$client_contact_id = $client_contact ? $client_contact->id : null;
$payment->amount = $this->convertFromStripeAmount($data['amount'], $this->client->currency()->precision);
$payment->type_id = $data['payment_type'];
2019-10-01 11:59:32 +02:00
$payment->transaction_reference = $data['payment_method'];
$payment->client_contact_id = $client_contact_id;
2020-06-27 17:39:28 +02:00
$payment->gateway_type_id = GatewayType::ALIPAY;
2019-10-01 11:59:32 +02:00
$payment->save();
return $payment;
}
2019-09-25 04:07:33 +02:00
2019-09-13 00:33:48 +02:00
/**
* Creates a new String Payment Intent
*
2019-09-13 00:33:48 +02:00
* @param array $data The data array to be passed to Stripe
* @return PaymentIntent The Stripe payment intent object
*/
2020-06-24 16:07:12 +02:00
public function createPaymentIntent($data): ?\Stripe\PaymentIntent
2019-09-13 00:33:48 +02:00
{
$this->init();
2019-09-17 13:54:14 +02:00
2019-09-13 00:33:48 +02:00
return PaymentIntent::create($data);
}
2019-09-14 14:34:05 +02:00
/**
* Returns a setup intent that allows the user
2019-09-17 13:54:14 +02:00
* to enter card details without initiating a transaction.
2019-09-14 14:34:05 +02:00
*
* @return \Stripe\SetupIntent
*/
2020-06-24 16:07:12 +02:00
public function getSetupIntent(): \Stripe\SetupIntent
2019-09-14 14:34:05 +02:00
{
$this->init();
2019-09-17 13:54:14 +02:00
2019-09-14 14:34:05 +02:00
return SetupIntent::create();
}
2019-09-17 13:54:14 +02:00
/**
* Returns the Stripe publishable key
* @return NULL|string The stripe publishable key
*/
2020-06-24 16:07:12 +02:00
public function getPublishableKey(): ?string
2019-09-14 14:34:05 +02:00
{
return $this->company_gateway->getPublishableKey();
}
2019-09-17 13:54:14 +02:00
/**
* Finds or creates a Stripe Customer object
*
2019-09-17 13:54:14 +02:00
* @return NULL|\Stripe\Customer A Stripe customer object
*/
2020-06-24 16:07:12 +02:00
public function findOrCreateCustomer(): ?\Stripe\Customer
{
$customer = null;
$this->init();
$client_gateway_token = ClientGatewayToken::whereClientId($this->client->id)->whereCompanyGatewayId($this->company_gateway->id)->first();
if ($client_gateway_token && $client_gateway_token->gateway_customer_reference) {
$customer = \Stripe\Customer::retrieve($client_gateway_token->gateway_customer_reference);
} else {
$data['name'] = $this->client->present()->name();
$data['phone'] = $this->client->present()->phone();
if (filter_var($this->client->present()->email(), FILTER_VALIDATE_EMAIL)) {
$data['email'] = $this->client->present()->email();
}
$customer = \Stripe\Customer::create($data);
}
2019-09-17 07:42:10 +02:00
if (!$customer) {
2019-10-04 08:22:22 +02:00
throw new \Exception('Unable to create gateway customer');
}
2019-09-25 04:07:33 +02:00
return $customer;
}
public function refund(Payment $payment, $amount)
{
$this->gateway();
$response = $this->gateway
2020-06-24 16:07:12 +02:00
->refund(['transactionReference' => $payment->transaction_reference, 'amount' => $amount, 'currency' => $payment->client->getCurrencyCode()])
->send();
if ($response->isSuccessful()) {
2020-06-24 16:07:12 +02:00
SystemLogger::dispatch([
'server_response' => $response->getMessage(), 'data' => request()->all(),
], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->client);
return [
'transaction_reference' => $response->getData()['id'],
'transaction_response' => json_encode($response->getData()),
'success' => $response->getData()['refunded'],
'description' => $response->getData()['description'],
'code' => $response->getCode(),
];
}
2020-06-24 16:07:12 +02:00
SystemLogger::dispatch([
'server_response' => $response->getMessage(), 'data' => request()->all(),
], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->client);
return [
'transaction_reference' => null,
'transaction_response' => json_encode($response->getData()),
'success' => false,
'description' => $response->getData()['error']['message'],
'code' => $response->getData()['error']['code'],
];
}
public function verificationView(ClientGatewayToken $payment_method)
{
return $this->payment_method->verificationView($payment_method);
}
public function processVerification(ClientGatewayToken $payment_method)
{
return $this->payment_method->processVerification($payment_method);
}
2020-06-27 17:39:28 +02:00
public function processWebhookRequest(PaymentWebhookRequest $request, Company $company, CompanyGateway $company_gateway, Payment $payment)
{
if ($request->type == 'source.chargable') {
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
}
return response([], 200);
}
2020-07-14 14:50:16 +02:00
public function tokenBilling(ClientGatewayToken $cgt, float $amount, ?Invoice $invoice = null)
{
2020-07-15 07:05:02 +02:00
return (new Charge($this))->tokenBilling($cgt, $amount, $invoice);
2020-07-14 14:50:16 +02:00
}
2020-07-08 04:20:44 +02:00
2020-07-15 07:05:02 +02:00
/**
* Creates a payment record for the given
* data array.
*
* @param array $data An array of payment attributes
* @param float $amount The amount of the payment
* @return Payment The payment object
*/
public function createPaymentRecord($data, $amount) :?Payment
{
2020-07-08 04:20:44 +02:00
2020-07-15 07:05:02 +02:00
$payment = PaymentFactory::create($this->client->company_id, $this->client->user_id);
$payment->client_id = $this->client->id;
$payment->company_gateway_id = $this->company_gateway->id;
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->gateway_type_id = $data['gateway_type_id'];
$payment->type_id = $data['type_id'];
$payment->currency_id = $this->client->getSetting('currency_id');
$payment->date = Carbon::now();
$payment->transaction_reference = $data['transaction_reference'];
$payment->amount = $amount;
$payment->client->getNextPaymentNumber($this->client);
$payment->save();
return $payment;
}
}