diff --git a/app/Http/Livewire/PaymentsTable.php b/app/Http/Livewire/PaymentsTable.php index e9b2cd3dbbfc..7c2cb8970840 100644 --- a/app/Http/Livewire/PaymentsTable.php +++ b/app/Http/Livewire/PaymentsTable.php @@ -41,7 +41,7 @@ class PaymentsTable extends Component { $query = Payment::query() ->with('type', 'client') - ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_REFUNDED, Payment::STATUS_PARTIALLY_REFUNDED]) + ->whereIn('status_id', [Payment::STATUS_FAILED, Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_REFUNDED, Payment::STATUS_PARTIALLY_REFUNDED]) ->where('company_id', $this->company->id) ->where('client_id', auth()->guard('contact')->user()->client->id) ->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc') diff --git a/app/Jobs/Mail/PaymentFailedMailer.php b/app/Jobs/Mail/PaymentFailedMailer.php index 77f7585bdac1..4188a3d035f3 100644 --- a/app/Jobs/Mail/PaymentFailedMailer.php +++ b/app/Jobs/Mail/PaymentFailedMailer.php @@ -72,7 +72,6 @@ class PaymentFailedMailer implements ShouldQueue */ public function handle() { - //Set DB MultiDB::setDb($this->company->db); App::setLocale($this->client->locale()); diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 6df3448565e2..feddf40625de 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -103,7 +103,7 @@ class Gateway extends StaticModel case 20: return [ GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], - GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable','charge.succeeded','payment_intent.succeeded']], + GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable','charge.succeeded','payment_intent.succeeded','charge.failed','payment_intent.payment_failed']], GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false], GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false], GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], //Stripe diff --git a/app/PaymentDrivers/Stripe/ACH.php b/app/PaymentDrivers/Stripe/ACH.php index 7e96cd3439be..f3b57eb03978 100644 --- a/app/PaymentDrivers/Stripe/ACH.php +++ b/app/PaymentDrivers/Stripe/ACH.php @@ -30,8 +30,12 @@ use App\PaymentDrivers\StripePaymentDriver; use App\Utils\Traits\MakesHash; use Exception; use Stripe\Customer; +use Stripe\Exception\ApiErrorException; +use Stripe\Exception\AuthenticationException; use Stripe\Exception\CardException; use Stripe\Exception\InvalidRequestException; +use Stripe\Exception\RateLimitException; +use Stripe\PaymentIntent; class ACH { @@ -45,6 +49,9 @@ class ACH $this->stripe = $stripe; } + /** + * Authorize a bank account - requires microdeposit verification + */ public function authorizeView(array $data) { $data['gateway'] = $this->stripe; @@ -134,7 +141,11 @@ class ACH return back()->with('error', $e->getMessage()); } } - + + /** + * Make a payment WITH instant verification. + */ + public function paymentView(array $data) { $data['gateway'] = $this->stripe; @@ -177,6 +188,9 @@ class ACH $description = "Payment with no invoice for amount {$amount} for client {$this->stripe->client->present()->name()}"; } + if(substr($cgt->token, 0, 2) === "pm") + return $this->paymentIntentTokenBilling($amount, $invoice, $description, $cgt, false); + $this->stripe->init(); $response = null; @@ -220,20 +234,178 @@ class ACH } + public function paymentIntentTokenBilling($amount, $invoice, $description, $cgt, $client_present = true) + { + $this->stripe->init(); + + try { + $data = [ + 'amount' => $this->stripe->convertToStripeAmount($amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()), + 'currency' => $this->stripe->client->getCurrencyCode(), + 'payment_method' => $cgt->token, + 'customer' => $cgt->gateway_customer_reference, + 'confirm' => true, + 'description' => $description, + 'metadata' => [ + 'payment_hash' => $this->stripe->payment_hash->hash, + 'gateway_type_id' => $cgt->gateway_type_id, + ], + ]; + + if($cgt->gateway_type_id == GatewayType::BANK_TRANSFER) + $data['payment_method_types'] = ['us_bank_account']; + + $response = $this->stripe->createPaymentIntent($data, $this->stripe->stripe_connect_auth); + + SystemLogger::dispatch($response, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->stripe->client, $this->stripe->client->company); + + }catch(\Exception $e) { + + $data =[ + 'status' => '', + 'error_type' => '', + 'error_code' => '', + 'param' => '', + 'message' => '', + ]; + + switch ($e) { + case ($e instanceof CardException): + $data['status'] = $e->getHttpStatus(); + $data['error_type'] = $e->getError()->type; + $data['error_code'] = $e->getError()->code; + $data['param'] = $e->getError()->param; + $data['message'] = $e->getError()->message; + break; + case ($e instanceof RateLimitException): + $data['message'] = 'Too many requests made to the API too quickly'; + break; + case ($e instanceof InvalidRequestException): + $data['message'] = 'Invalid parameters were supplied to Stripe\'s API'; + break; + case ($e instanceof AuthenticationException): + $data['message'] = 'Authentication with Stripe\'s API failed'; + break; + case ($e instanceof ApiErrorException): + $data['message'] = 'Network communication with Stripe failed'; + break; + + default: + $data['message'] = $e->getMessage(); + break; + } + + $this->stripe->processInternallyFailedPayment($this->stripe, $e); + + SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client, $this->stripe->client->company); + } + + if (! $response) { + return false; + } + + $payment_method_type = PaymentType::ACH; + + $data = [ + 'gateway_type_id' => $cgt->gateway_type_id, + 'payment_type' => PaymentType::ACH, + 'transaction_reference' => $response->charges->data[0]->id, + 'amount' => $amount, + ]; + + $payment = $this->stripe->createPayment($data, Payment::STATUS_PENDING); + $payment->meta = $cgt->meta; + $payment->save(); + + $this->stripe->payment_hash->payment_id = $payment->id; + $this->stripe->payment_hash->save(); + + if($client_present){ + return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]); + } + + return $payment; + } + public function handlePaymentIntentResponse($request) { - nlog($request->all()); - dd($request->all()); + + $response = json_decode($request->gateway_response); + $bank_account_response = json_decode($request->bank_account_response); + + $method = $bank_account_response->payment_method->us_bank_account; + $method->id = $response->payment_method; + $method->state = 'authorized'; + + $this->stripe->payment_hash = PaymentHash::where("hash", $request->input("payment_hash"))->first(); + + if($response->id && $response->status === "processing") { + $payment_intent = PaymentIntent::retrieve($response->id, $this->stripe->stripe_connect_auth); + + $state = [ + 'gateway_type_id' => GatewayType::BANK_TRANSFER, + 'amount' => $response->amount, + 'currency' => $response->currency, + 'customer' => $request->customer, + 'source' => $response->payment_method, + 'charge' => $response + ]; + + $this->stripe->payment_hash->data = array_merge((array)$this->stripe->payment_hash->data, $state); + $this->stripe->payment_hash->save(); + + $customer = $this->stripe->getCustomer($request->customer); + + $this->storePaymentMethod($method, GatewayType::BANK_TRANSFER, $customer); + + return $this->processPendingPayment($state, true); + } + + if($response->next_action){ + + } + } + public function processPendingPaymentIntent($state, $client_present = true) + { + $this->stripe->init(); + + $data = [ + 'payment_method' => $state['source'], + 'payment_type' => PaymentType::ACH, + 'amount' => $state['amount'], + 'transaction_reference' => $state['charge'], + 'gateway_type_id' => GatewayType::BANK_TRANSFER, + ]; + + $payment = $this->stripe->createPayment($data, Payment::STATUS_PENDING); + + SystemLogger::dispatch( + ['response' => $state, 'data' => $data], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_SUCCESS, + SystemLog::TYPE_STRIPE, + $this->stripe->client, + $this->stripe->client->company, + ); + + if(!$client_present) + return $payment; + + return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]); + } + + + public function paymentResponse($request) { $this->stripe->init(); //it may be a payment intent here. - if($request->input('client_secret')) - $this->handlePaymentIntentResponse($request); + if($request->input('client_secret') != '') + return $this->handlePaymentIntentResponse($request); $source = ClientGatewayToken::query() ->where('id', $this->decodePrimaryKey($request->source)) @@ -269,6 +441,9 @@ class ACH $description = "Payment with no invoice for amount {$amount} for client {$this->stripe->client->present()->name()}"; } + if(substr($source->token, 0, 2) === "pm") + return $this->paymentIntentTokenBilling($amount, $invoice, $description, $source); + try { $state['charge'] = \Stripe\Charge::create([ 'amount' => $state['amount'], @@ -297,6 +472,7 @@ class ACH } } + public function processPendingPayment($state, $client_present = true) { $this->stripe->init(); @@ -348,12 +524,14 @@ class ACH private function storePaymentMethod($method, $payment_method_id, $customer) { + $state = property_exists($method, 'state') ? $method->state : 'unauthorized'; + try { $payment_meta = new \stdClass; $payment_meta->brand = (string) \sprintf('%s (%s)', $method->bank_name, ctrans('texts.ach')); $payment_meta->last4 = (string) $method->last4; $payment_meta->type = GatewayType::BANK_TRANSFER; - $payment_meta->state = 'unauthorized'; + $payment_meta->state = $state; $data = [ 'payment_meta' => $payment_meta, diff --git a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentFailureWebhook.php b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentFailureWebhook.php new file mode 100644 index 000000000000..dd2cd68c125f --- /dev/null +++ b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentFailureWebhook.php @@ -0,0 +1,128 @@ +stripe_request = $stripe_request; + $this->company_key = $company_key; + $this->company_gateway_id = $company_gateway_id; + } + + public function handle() + { + + MultiDB::findAndSetDbByCompanyKey($this->company_key); + + $company = Company::where('company_key', $this->company_key)->first(); + + foreach ($this->stripe_request as $transaction) { + + if(array_key_exists('payment_intent', $transaction)) + { + + $payment = Payment::query() + ->where('company_id', $company->id) + ->where(function ($query) use ($transaction) { + $query->where('transaction_reference', $transaction['payment_intent']) + ->orWhere('transaction_reference', $transaction['id']); + }) + ->first(); + + } + else + { + + $payment = Payment::query() + ->where('company_id', $company->id) + ->where('transaction_reference', $transaction['id']) + ->first(); + + } + + if ($payment) { + + $client = $payment->client; + + if($payment->status_id == Payment::STATUS_PENDING) + $payment->service()->deletePayment(); + + $payment->status_id = Payment::STATUS_FAILED; + $payment->save(); + + $payment_hash = PaymentHash::where('payment_id', $payment->id)->first(); + + if($payment_hash) + { + + $error = ctrans('texts.client_payment_failure_body', [ + 'invoice' => implode(",", $payment->invoices->pluck('number')->toArray()), + 'amount' => array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total]); + + } + else + $error = "Payment for " . $payment->client->present()->name(). " for {$payment->amount} failed"; + + if(array_key_exists('failure_message', $transaction)){ + + $error .= "\n\n" .$transaction['failure_message']; + } + + PaymentFailedMailer::dispatch( + $payment_hash, + $client->company, + $client, + $error + ); + + + } + + } + + } + +} \ No newline at end of file diff --git a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php index a7b11bda0377..dfbe20efcf49 100644 --- a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php +++ b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php @@ -53,11 +53,7 @@ class PaymentIntentWebhook implements ShouldQueue public function handle() { - // nlog($this->stripe_request); - // nlog(optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['gateway_type_id']); - // nlog(optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['payment_hash']); - // nlog(optional($this->stripe_request['object']['charges']['data'][0]['payment_method_details']['card'])['brand']); - + MultiDB::findAndSetDbByCompanyKey($this->company_key); $company = Company::where('company_key', $this->company_key)->first(); diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index cf47f1bd8e0b..ddcc5e274eb3 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -38,6 +38,7 @@ use App\PaymentDrivers\Stripe\EPS; use App\PaymentDrivers\Stripe\FPX; use App\PaymentDrivers\Stripe\GIROPAY; use App\PaymentDrivers\Stripe\ImportCustomers; +use App\PaymentDrivers\Stripe\Jobs\PaymentIntentFailureWebhook; use App\PaymentDrivers\Stripe\Jobs\PaymentIntentWebhook; use App\PaymentDrivers\Stripe\PRZELEWY24; use App\PaymentDrivers\Stripe\SEPA; @@ -47,6 +48,7 @@ use App\PaymentDrivers\Stripe\Utilities; use App\PaymentDrivers\Stripe\iDeal; use App\Utils\Traits\MakesHash; use Exception; +use Google\Service\ServiceConsumerManagement\CustomError; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Carbon; use Laracasts\Presenter\Exceptions\PresenterException; @@ -409,6 +411,16 @@ class StripePaymentDriver extends BaseDriver return $this->company_gateway->getPublishableKey(); } + public function getCustomer($customer_id) :?Customer + { + $customer = Customer::retrieve($customer_id, $this->stripe_connect_auth); + + if($customer) + return $customer; + + return false; + } + /** * Finds or creates a Stripe Customer object. * @@ -568,15 +580,21 @@ class StripePaymentDriver extends BaseDriver //payment_intent.succeeded - this will confirm or cancel the payment if($request->type === 'payment_intent.succeeded'){ - PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(10)); + PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(2,10))); return response()->json([], 200); } + if(in_array($request->type, ['payment_intent.payment_failed','charge.failed'])){ + PaymentIntentFailureWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(2,10))); + return response()->json([], 200); + } + + if ($request->type === 'charge.succeeded') { foreach ($request->data as $transaction) { - if(array_key_exists('payment_intent', $transaction)) + if(array_key_exists('payment_intent', $transaction) && $transaction['payment_intent']) { $payment = Payment::query() // ->where('company_id', $request->getCompany()->id) diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index 896e8baead67..86e304182b6e 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -46,14 +46,6 @@ class DeletePayment ->save(); } - //reverse paymentables->invoices - - //reverse paymentables->credits - - //set refunded to amount - - //set applied amount to 0 - private function cleanupPayment() { $this->payment->is_deleted = true; diff --git a/public/js/clients/payments/stripe-ach.js b/public/js/clients/payments/stripe-ach.js index a0cd224a2119..4cad4ced3f04 100755 --- a/public/js/clients/payments/stripe-ach.js +++ b/public/js/clients/payments/stripe-ach.js @@ -1,2 +1,2 @@ /*! For license information please see stripe-ach.js.LICENSE.txt */ -(()=>{function e(e,t){for(var n=0;n svg").classList.add("hidden"),document.querySelector("#save-button > span").classList.remove("hidden"),r.errors.textContent="",r.errors.textContent=e,r.errors.hidden=!1})),t(this,"handleSuccess",(function(e){document.getElementById("gateway_response").value=JSON.stringify(e),document.getElementById("server_response").submit()})),t(this,"handleSubmit",(function(e){document.getElementById("save-button").disabled=!0,document.querySelector("#save-button > svg").classList.remove("hidden"),document.querySelector("#save-button > span").classList.add("hidden"),e.preventDefault(),r.errors.textContent="",r.errors.hidden=!0,r.stripe.createToken("bank_account",r.getFormData()).then((function(e){return e.hasOwnProperty("error")?r.handleError(e.error.message):r.handleSuccess(e)}))})),this.errors=document.getElementById("errors"),this.key=document.querySelector('meta[name="stripe-publishable-key"]').content,this.stripe_connect=null===(e=document.querySelector('meta[name="stripe-account-id"]'))||void 0===e?void 0:e.content}var r,o,u;return r=n,(o=[{key:"handle",value:function(){var e=this;document.getElementById("save-button").addEventListener("click",(function(t){return e.handleSubmit(t)}))}}])&&e(r.prototype,o),u&&e(r,u),n}())).setupStripe().handle()})(); \ No newline at end of file +(()=>{function e(e,t){for(var n=0;n svg").classList.add("hidden"),document.querySelector("#save-button > span").classList.remove("hidden"),r.errors.textContent="",r.errors.textContent=e,r.errors.hidden=!1})),t(this,"handleSuccess",(function(e){document.getElementById("gateway_response").value=JSON.stringify(e),document.getElementById("server_response").submit()})),t(this,"handleSubmit",(function(e){if(!document.getElementById("accept-terms").checked)return errors.textContent="You must accept the mandate terms prior to making payment.",void(errors.hidden=!1);document.getElementById("save-button").disabled=!0,document.querySelector("#save-button > svg").classList.remove("hidden"),document.querySelector("#save-button > span").classList.add("hidden"),e.preventDefault(),r.errors.textContent="",r.errors.hidden=!0,r.stripe.createToken("bank_account",r.getFormData()).then((function(e){return e.hasOwnProperty("error")?r.handleError(e.error.message):r.handleSuccess(e)}))})),this.errors=document.getElementById("errors"),this.key=document.querySelector('meta[name="stripe-publishable-key"]').content,this.stripe_connect=null===(e=document.querySelector('meta[name="stripe-account-id"]'))||void 0===e?void 0:e.content}var r,o,u;return r=n,(o=[{key:"handle",value:function(){var e=this;document.getElementById("save-button").addEventListener("click",(function(t){return e.handleSubmit(t)}))}}])&&e(r.prototype,o),u&&e(r,u),n}())).setupStripe().handle()})(); \ No newline at end of file diff --git a/public/mix-manifest.json b/public/mix-manifest.json index b38b596ecded..9068d75070a0 100755 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -2,7 +2,7 @@ "/js/app.js": "/js/app.js?id=0e3959ab851d3350364d", "/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=de4468c682d6861847de", "/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=cfe5de1cf87a0b01568d", - "/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=a5f14c885c3aeef6c744", + "/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=50d964c4a3ffa7f2f99f", "/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=6b79265cbb8c963eef19", "/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=2cccf9e51b60a0ab17b8", "/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=22fc06e698dea2c3bdf3", diff --git a/resources/js/clients/payments/stripe-ach.js b/resources/js/clients/payments/stripe-ach.js index e8f84827f78b..7002c83fecdb 100644 --- a/resources/js/clients/payments/stripe-ach.js +++ b/resources/js/clients/payments/stripe-ach.js @@ -70,6 +70,13 @@ class AuthorizeACH { }; handleSubmit = (e) => { + + if (!document.getElementById('accept-terms').checked) { + errors.textContent = "You must accept the mandate terms prior to making payment."; + errors.hidden = false; + return; + } + document.getElementById('save-button').disabled = true; document.querySelector('#save-button > svg').classList.remove('hidden'); document.querySelector('#save-button > span').classList.add('hidden'); diff --git a/resources/views/portal/ninja2020/gateways/stripe/ach/authorize.blade.php b/resources/views/portal/ninja2020/gateways/stripe/ach/authorize.blade.php index c5e03cec9f8d..d88aeb847fa8 100644 --- a/resources/views/portal/ninja2020/gateways/stripe/ach/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/stripe/ach/authorize.blade.php @@ -27,6 +27,10 @@ +
+

Adding a bank account here requires verification, which may take several days. In order to use Instant Verification please pay an invoice first, this process will automatically verify your bank account.

+
+ @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_type')]) @@ -39,14 +43,18 @@ @endcomponent @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_name')]) - + @endcomponent @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.country')]) @endcomponent @@ -55,7 +63,11 @@ @endcomponent diff --git a/resources/views/portal/ninja2020/gateways/stripe/ach/pay.blade.php b/resources/views/portal/ninja2020/gateways/stripe/ach/pay.blade.php index 8e7dbd193a7f..fe497eb72385 100644 --- a/resources/views/portal/ninja2020/gateways/stripe/ach/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/stripe/ach/pay.blade.php @@ -27,6 +27,7 @@ + @if(count($tokens) > 0) @@ -35,17 +36,21 @@ @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')]) @if(count($tokens) > 0) +
    @foreach($tokens as $token) - +
  • + +
  • @endforeach - @endisset +
+ @endif @endcomponent @include('portal.ninja2020.gateways.includes.pay_now') @@ -82,121 +87,140 @@ @endsection @push('footer') - + - + let newBankButton = document.getElementById('new-bank'); + newBankButton.disabled = true; + newBankButton.querySelector('svg').classList.remove('hidden'); + newBankButton.querySelector('span').classList.add('hidden'); + + ev.preventDefault(); + const accountHolderNameField = document.getElementById('account-holder-name-field'); + const emailField = document.getElementById('email-field'); + const clientSecret = document.querySelector('meta[name="client_secret"]')?.content; + // Calling this method will open the instant verification dialog. + stripe.collectBankAccountForPayment({ + clientSecret: clientSecret, + params: { + payment_method_type: 'us_bank_account', + payment_method_data: { + billing_details: { + name: accountHolderNameField.value, + email: emailField.value, + }, + }, + }, + expand: ['payment_method'], + }) + .then(({paymentIntent, error}) => { + if (error) { + + console.error(error.message); + errors.textContent = error.message; + errors.hidden = false; + resetButtons(); + + // PaymentMethod collection failed for some reason. + } else if (paymentIntent.status === 'requires_payment_method') { + // Customer canceled the hosted verification modal. Present them with other + // payment method type options. + + errors.textContent = "We were unable to process the payment with this account, please try another one."; + errors.hidden = false; + resetButtons(); + return; + + } else if (paymentIntent.status === 'requires_confirmation') { + + let bank_account_response = document.getElementById('bank_account_response'); + bank_account_response.value = JSON.stringify(paymentIntent); + + confirmPayment(stripe, clientSecret); + } + + }); + }); + + function confirmPayment(stripe, clientSecret){ + stripe.confirmUsBankAccountPayment(clientSecret) + .then(({paymentIntent, error}) => { + console.log(paymentIntent); + if (error) { + console.error(error.message); + // The payment failed for some reason. + } else if (paymentIntent.status === "requires_payment_method") { + // Confirmation failed. Attempt again with a different payment method. + + errors.textContent = "We were unable to process the payment with this account, please try another one."; + errors.hidden = false; + resetButtons(); + + } else if (paymentIntent.status === "processing") { + // Confirmation succeeded! The account will be debited. + + let gateway_response = document.getElementById('gateway_response'); + gateway_response.value = JSON.stringify(paymentIntent); + document.getElementById('server-response').submit(); + + } else if (paymentIntent.next_action?.type === "verify_with_microdeposits") { + + } + }); + + } + + function resetButtons() + { + + let newBankButton = document.getElementById('new-bank'); + newBankButton.disabled = false; + newBankButton.querySelector('svg').classList.add('hidden'); + newBankButton.querySelector('span').classList.remove('hidden'); + + } + @endpush \ No newline at end of file diff --git a/resources/views/portal/ninja2020/gateways/stripe/credit_card/pay.blade.php b/resources/views/portal/ninja2020/gateways/stripe/credit_card/pay.blade.php index 8a91de072bb8..dc35139ad13c 100644 --- a/resources/views/portal/ninja2020/gateways/stripe/credit_card/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/stripe/credit_card/pay.blade.php @@ -48,28 +48,35 @@ @include('portal.ninja2020.gateways.includes.payment_details') @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')]) +
    @if(count($tokens) > 0) @foreach($tokens as $token) +
  • +
  • @endforeach @endisset - +
  • + +
  • +
+ @endcomponent @include('portal.ninja2020.gateways.stripe.includes.card_widget')