2019-09-05 09:00:12 +02:00
< ? php
/**
* Invoice Ninja ( https :// invoiceninja . com )
*
* @ link https :// github . com / invoiceninja / invoiceninja source repository
*
* @ copyright Copyright ( c ) 2019. Invoice Ninja LLC ( https :// invoiceninja . com )
*
* @ license https :// opensource . org / licenses / AAL
*/
namespace App\PaymentDrivers ;
2019-10-01 11:59:32 +02:00
use App\Events\Payment\PaymentWasCreated ;
2019-09-25 07:55:52 +02:00
use App\Factory\PaymentFactory ;
2019-10-03 14:02:31 +02:00
use App\Jobs\Invoice\UpdateInvoicePayment ;
2019-10-29 03:55:26 +01:00
use App\Jobs\Util\SystemLogger ;
2019-09-17 07:42:10 +02:00
use App\Models\ClientGatewayToken ;
2019-09-08 14:13:55 +02:00
use App\Models\GatewayType ;
2019-09-25 07:55:52 +02:00
use App\Models\Invoice ;
use App\Models\Payment ;
use App\Models\PaymentType ;
2019-10-29 03:55:26 +01:00
use App\Models\SystemLog ;
2019-09-25 07:55:52 +02:00
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
{
2019-09-25 07:55:52 +02:00
use MakesHash ;
2019-09-06 07:22:05 +02:00
protected $refundable = true ;
2019-09-05 09:00:12 +02:00
2019-09-06 07:22:05 +02:00
protected $token_billing = true ;
2019-09-25 07:55:52 +02:00
protected $can_authorise_credit_card = true ;
2019-09-15 13:40:46 +02:00
2019-09-06 07:22:05 +02:00
protected $customer_reference = 'customerReferenceParam' ;
2019-09-05 14:42:26 +02:00
/**
* 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
*/
2019-09-08 14:13:55 +02:00
/************************************** 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
*/
public function init () : void
2019-09-08 14:13:55 +02:00
{
2019-09-16 06:59:59 +02:00
Stripe :: setApiKey ( $this -> company_gateway -> getConfigField ( '23_apiKey' ));
2019-09-08 14:13:55 +02:00
}
2019-09-17 12:27:48 +02:00
2019-09-08 14:13:55 +02:00
/**
* Returns the gateway types
*/
public function gatewayTypes () : array
{
$types = [
GatewayType :: CREDIT_CARD ,
2019-09-25 04:07:33 +02:00
//GatewayType::TOKEN,
2019-09-08 14:13:55 +02: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' ]))
$types [] = GatewayType :: SOFORT ;
if ( $this -> company_gateway -> getAchEnabled ())
$types [] = GatewayType :: BANK_TRANSFER ;
if ( $this -> company_gateway -> getSepaEnabled ())
$types [] = GatewayType :: SEPA ;
if ( $this -> company_gateway -> getBitcoinEnabled ())
2019-11-27 10:47:59 +01:00
$types [] = GatewayType :: CRYPTO ;
2019-09-08 14:13:55 +02:00
if ( $this -> company_gateway -> getAlipayEnabled ())
$types [] = GatewayType :: ALIPAY ;
if ( $this -> company_gateway -> getApplePayEnabled ())
$types [] = GatewayType :: APPLE_PAY ;
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
{
2019-09-18 04:39:53 +02:00
switch ( $gateway_type_id ) {
2019-09-12 13:46:09 +02:00
case GatewayType :: CREDIT_CARD :
2019-09-14 14:34:05 +02:00
return 'portal.default.gateways.stripe.credit_card' ;
2019-09-12 13:46:09 +02:00
break ;
case GatewayType :: TOKEN :
2019-09-14 14:34:05 +02:00
return 'portal.default.gateways.stripe.credit_card' ;
2019-09-12 13:46:09 +02:00
break ;
case GatewayType :: SOFORT :
2019-09-14 14:34:05 +02:00
return 'portal.default.gateways.stripe.sofort' ;
2019-09-12 13:46:09 +02:00
break ;
case GatewayType :: BANK_TRANSFER :
2019-09-14 14:34:05 +02:00
return 'portal.default.gateways.stripe.ach' ;
2019-09-12 13:46:09 +02:00
break ;
case GatewayType :: SEPA :
2019-09-14 14:34:05 +02:00
return 'portal.default.gateways.stripe.sepa' ;
2019-09-12 13:46:09 +02:00
break ;
2019-11-27 10:47:59 +01:00
case GatewayType :: CRYPTO :
2019-09-14 14:34:05 +02:00
return 'portal.default.gateways.stripe.other' ;
2019-09-12 13:46:09 +02:00
break ;
case GatewayType :: ALIPAY :
2019-09-14 14:34:05 +02:00
return 'portal.default.gateways.stripe.other' ;
2019-09-12 13:46:09 +02:00
break ;
case GatewayType :: APPLE_PAY :
2019-09-14 14:34:05 +02:00
return 'portal.default.gateways.stripe.other' ;
2019-09-12 13:46:09 +02:00
break ;
default :
# code...
break ;
}
2019-09-08 14:13:55 +02:00
}
2019-09-13 00:33:48 +02:00
2019-09-25 07:55:52 +02:00
/**
*
* Authorises a credit card for future use
* @ param array $data Array of variables needed for the view
*
* @ return view The gateway specific partial to be rendered
*
*/
2019-09-25 06:03:28 +02:00
public function authorizeCreditCardView ( array $data )
2019-09-14 14:34:05 +02:00
{
2019-09-25 04:07:33 +02:00
2019-09-16 04:05:30 +02:00
$intent [ 'intent' ] = $this -> getSetupIntent ();
2019-09-14 14:34:05 +02:00
2019-09-20 07:13:58 +02:00
return view ( 'portal.default.gateways.stripe.add_credit_card' , array_merge ( $data , $intent ));
2019-09-16 13:03:25 +02:00
}
2019-09-25 07:55:52 +02:00
/**
* Processes the gateway response for credti card authorization
* @ param Request $request The returning request object
* @ return view Returns the user to payment methods screen .
*/
2019-09-16 13:03:25 +02:00
public function authorizeCreditCardResponse ( $request )
{
2019-09-17 07:42:10 +02:00
$server_response = json_decode ( $request -> input ( 'gateway_response' ));
2019-09-16 13:03:25 +02:00
2019-09-17 07:42:10 +02:00
$gateway_id = $request -> input ( 'gateway_id' );
2019-09-18 04:39:53 +02:00
$gateway_type_id = $request -> input ( 'gateway_type_id' );
2019-09-17 13:54:14 +02:00
$is_default = $request -> input ( 'is_default' );
2019-09-17 07:42:10 +02:00
$payment_method = $server_response -> payment_method ;
$customer = $this -> findOrCreateCustomer ();
2019-09-18 04:39:53 +02:00
$this -> init ();
2019-09-17 07:42:10 +02:00
$stripe_payment_method = \Stripe\PaymentMethod :: retrieve ( $payment_method );
2019-09-18 04:39:53 +02:00
$stripe_payment_method_obj = $stripe_payment_method -> jsonSerialize ();
2019-09-17 07:42:10 +02:00
$stripe_payment_method -> attach ([ 'customer' => $customer -> id ]);
2019-09-16 04:05:30 +02:00
2019-09-18 04:39:53 +02:00
$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' ];
}
2019-09-17 07:42:10 +02:00
$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 ;
2019-09-18 04:39:53 +02:00
$cgt -> gateway_type_id = $gateway_type_id ;
2019-09-17 07:42:10 +02:00
$cgt -> gateway_customer_reference = $customer -> id ;
2019-09-18 04:39:53 +02:00
$cgt -> meta = $payment_meta ;
2019-09-17 07:42:10 +02:00
$cgt -> save ();
2019-09-14 14:34:05 +02:00
2019-09-19 13:31:49 +02:00
if ( $is_default == 'true' || $this -> client -> gateway_tokens -> count () == 1 )
2019-09-17 07:42:10 +02:00
{
2019-09-17 07:59:09 +02:00
$this -> client -> gateway_tokens () -> update ([ 'is_default' => 0 ]);
2019-09-17 07:42:10 +02:00
$cgt -> is_default = 1 ;
$cgt -> save ();
}
return redirect () -> route ( 'client.payment_methods.index' );
2019-09-14 14:34:05 +02:00
}
2019-09-25 04:07:33 +02:00
/**
* Processes the payment with this gateway
*
* @ var invoices
* @ var amount
* @ var fee
* @ var amount_with_fee
* @ var token
* @ var payment_method_id
2019-09-25 06:03:28 +02:00
* @ var hashed_ids
*
2019-09-25 04:07:33 +02:00
* @ param array $data variables required to build payment page
* @ return view Gateway and payment method specific view
*/
2019-09-25 06:03:28 +02:00
public function processPaymentView ( array $data )
2019-09-25 04:07:33 +02:00
{
$payment_intent_data = [
2019-10-10 12:43:50 +02:00
'amount' => $this -> convertToStripeAmount ( $data [ 'amount_with_fee' ], $this -> client -> currency () -> precision ),
2019-10-10 13:08:02 +02:00
'currency' => $this -> client -> getCurrencyCode (),
2019-09-25 04:07:33 +02:00
'customer' => $this -> findOrCreateCustomer (),
2019-09-25 06:03:28 +02:00
'description' => $data [ 'invoices' ] -> pluck ( 'id' ), //todo more meaningful description here:
2019-09-25 04:07:33 +02:00
];
if ( $data [ 'token' ])
$payment_intent_data [ 'payment_method' ] = $data [ 'token' ] -> token ;
else {
2019-09-25 06:03:28 +02:00
$payment_intent_data [ 'setup_future_usage' ] = 'off_session' ;
2019-09-25 04:07:33 +02:00
// $payment_intent_data['save_payment_method'] = true;
// $payment_intent_data['confirm'] = true;
}
$data [ 'intent' ] = $this -> createPaymentIntent ( $payment_intent_data );
2019-10-02 03:16:51 +02:00
2019-09-25 04:07:33 +02:00
$data [ 'gateway' ] = $this ;
2019-09-25 06:03:28 +02:00
2019-09-25 04:07:33 +02:00
return view ( $this -> viewForType ( $data [ 'payment_method_id' ]), $data );
}
2019-09-25 06:03:28 +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
2019-10-10 12:43:50 +02:00
+ " ->currency() " : " usd "
2019-09-25 06:03:28 +02:00
+ " 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 "
*/
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.
2019-09-25 06:03:28 +02:00
{
$server_response = json_decode ( $request -> input ( 'gateway_response' ));
2019-09-25 04:07:33 +02:00
2019-09-25 06:03:28 +02:00
$payment_method = $server_response -> payment_method ;
$payment_status = $server_response -> status ;
$save_card = $request -> input ( 'store_card' );
2019-09-25 07:55:52 +02:00
$gateway_type_id = $request -> input ( 'payment_method_id' );
$hashed_ids = $request -> input ( 'hashed_ids' );
$invoices = Invoice :: whereIn ( 'id' , $this -> transformKeys ( explode ( " , " , $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 ();
2019-09-25 06:03:28 +02:00
$payment_intent = \Stripe\PaymentIntent :: retrieve ( $server_response -> id );
$customer = $payment_intent -> customer ;
2019-09-25 07:55:52 +02:00
if ( $payment_status == 'succeeded' )
2019-09-25 06:03:28 +02:00
{
2019-09-25 07:55:52 +02:00
$this -> init ();
2019-09-25 06:03:28 +02:00
$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' ];
2019-09-25 07:55:52 +02:00
$payment_type = PaymentType :: parseCardType ( $stripe_payment_method_obj [ 'card' ][ 'brand' ]);
2019-09-25 06:03:28 +02:00
}
2019-09-25 07:55:52 +02:00
if ( $save_card == 'true' )
{
$stripe_payment_method -> attach ([ 'customer' => $customer ]);
2019-09-25 06:03:28 +02:00
$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 ();
2019-09-25 07:55:52 +02:00
if ( $this -> client -> gateway_tokens -> count () == 1 )
2019-09-25 06:03:28 +02:00
{
$this -> client -> gateway_tokens () -> update ([ 'is_default' => 0 ]);
$cgt -> is_default = 1 ;
$cgt -> save ();
}
2019-09-25 07:55:52 +02:00
}
//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 ;
2019-10-01 11:59:32 +02:00
$data = [
'payment_method' => $payment_method ,
'payment_type' => $payment_type ,
'amount' => $server_response -> amount ,
];
/* Create payment*/
$payment = $this -> createPayment ( $data );
/* Link invoices to payment*/
2019-10-01 03:56:48 +02:00
$this -> attachInvoices ( $payment , $hashed_ids );
2019-09-25 07:55:52 +02:00
2019-10-01 11:59:32 +02:00
event ( new PaymentWasCreated ( $payment ));
2019-09-25 08:23:51 +02:00
2019-10-03 14:02:31 +02:00
UpdateInvoicePayment :: dispatchNow ( $payment );
2019-10-29 03:55:26 +01:00
SystemLogger :: dispatch ([
'server_response' => $payment_intent ,
'data' => $data
],
SystemLog :: CATEGORY_GATEWAY_RESPONSE ,
SystemLog :: EVENT_GATEWAY_SUCCESS ,
SystemLog :: TYPE_STRIPE ,
$this -> client
);
2019-10-01 11:59:32 +02:00
return redirect () -> route ( 'client.payments.show' , [ 'payment' => $this -> encodePrimaryKey ( $payment -> id )]);
2019-09-25 08:23:51 +02:00
2019-09-25 06:03:28 +02:00
}
2019-10-01 11:59:32 +02:00
else
{
/*Fail and log*/
2019-10-29 03:55:26 +01:00
SystemLogger :: dispatch ([
'server_response' => $server_response ,
'data' => $data
],
SystemLog :: CATEGORY_GATEWAY_RESPONSE ,
SystemLog :: EVENT_GATEWAY_FAILURE ,
SystemLog :: TYPE_STRIPE ,
$this -> client
);
2019-10-01 11:59:32 +02:00
2019-10-04 08:22:22 +02:00
throw new \Exception ( " Failed to process payment " , 1 );
2019-10-01 11:59:32 +02:00
}
}
2019-10-02 03:16:51 +02:00
public function createPayment ( $data ) : Payment
2019-10-01 11:59:32 +02:00
{
$payment = parent :: createPayment ( $data );
$client_contact = $this -> getContact ();
$client_contact_id = $client_contact ? $client_contact -> id : null ;
2019-10-10 12:43:50 +02:00
$payment -> amount = $this -> convertFromStripeAmount ( $data [ 'amount' ], $this -> client -> currency () -> precision );
2019-10-01 11:59:32 +02:00
$payment -> payment_type_id = $data [ 'payment_type' ];
$payment -> transaction_reference = $data [ 'payment_method' ];
$payment -> client_contact_id = $client_contact_id ;
$payment -> save ();
return $payment ;
2019-09-25 07:55:52 +02:00
2019-09-25 06:03:28 +02:00
}
2019-09-25 04:07:33 +02:00
2019-09-25 07:55:52 +02:00
private function convertFromStripeAmount ( $amount , $precision )
{
return $amount / pow ( 10 , $precision );
}
private function convertToStripeAmount ( $amount , $precision )
{
return $amount * pow ( 10 , $precision );
}
2019-09-13 00:33:48 +02:00
/**
* Creates a new String Payment Intent
2019-09-17 13:54:14 +02:00
*
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
*/
2019-09-25 04:07:33 +02:00
public function createPaymentIntent ( $data ) : ? \Stripe\PaymentIntent
2019-09-13 00:33:48 +02:00
{
2019-09-17 13:54:14 +02:00
2019-09-16 06:59:59 +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-17 13:54:14 +02:00
2019-09-13 00:33:48 +02:00
}
2019-09-14 14:34:05 +02:00
/**
2019-09-17 13:54:14 +02:00
* Returns a setup intent that allows the user
* to enter card details without initiating a transaction .
2019-09-14 14:34:05 +02:00
*
* @ return \Stripe\SetupIntent
*/
2019-09-17 13:54:14 +02:00
public function getSetupIntent () : \Stripe\SetupIntent
2019-09-14 14:34:05 +02:00
{
2019-09-17 13:54:14 +02:00
2019-09-16 06:59:59 +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
2019-09-14 14:34:05 +02:00
}
2019-09-17 13:54:14 +02:00
/**
* Returns the Stripe publishable key
* @ return NULL | string The stripe publishable key
*/
public function getPublishableKey () : ? string
2019-09-14 14:34:05 +02:00
{
2019-09-17 13:54:14 +02:00
2019-09-14 14:34:05 +02:00
return $this -> company_gateway -> getPublishableKey ();
2019-09-17 13:54:14 +02:00
2019-09-14 14:34:05 +02:00
}
2019-09-16 06:59:59 +02:00
2019-09-17 13:54:14 +02:00
/**
* Finds or creates a Stripe Customer object
*
* @ return NULL | \Stripe\Customer A Stripe customer object
*/
2019-09-16 06:59:59 +02:00
public function findOrCreateCustomer () : ? \Stripe\Customer
{
$customer = null ;
$this -> init ();
2019-09-17 07:59:09 +02:00
$client_gateway_token = ClientGatewayToken :: whereClientId ( $this -> client -> id ) -> whereCompanyGatewayId ( $this -> company_gateway -> id ) -> first ();
2019-09-16 06:59:59 +02:00
2019-09-17 13:54:14 +02:00
if ( $client_gateway_token && $client_gateway_token -> gateway_customer_reference ){
2019-09-16 06:59:59 +02:00
$customer = \Stripe\Customer :: retrieve ( $client_gateway_token -> gateway_customer_reference );
2019-09-17 13:54:14 +02:00
}
2019-09-16 06:59:59 +02:00
else {
2019-09-17 07:59:09 +02:00
$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-16 06:59:59 +02:00
}
2019-09-17 07:42:10 +02:00
2019-09-25 04:07:33 +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
2019-09-16 06:59:59 +02:00
return $customer ;
}
2019-09-05 14:42:26 +02:00
/************************************** Omnipay API methods **********************************************************/
2019-09-05 09:00:12 +02:00
}