Merge pull request #5565 from beganovich/v5-2804-braintree

v5: Braintree integration
This commit is contained in:
David Bomba 2021-05-11 21:15:06 +10:00 committed by GitHub
commit 6d299c9e53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1245 additions and 56 deletions

View File

@ -243,7 +243,7 @@ class PaymentController extends Controller
->get();
}
$hash_data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals];
$hash_data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals, 'amount_with_fee' => max(0, (($invoice_totals + $fee_totals) - $credit_totals))];
if ($request->query('hash')) {
$hash_data['billing_context'] = Cache::get($request->query('hash'));
@ -303,24 +303,12 @@ class PaymentController extends Controller
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->payment_hash])->first();
try {
return $gateway
->driver(auth()->user()->client)
->setPaymentMethod($request->input('payment_method_id'))
->setPaymentHash($payment_hash)
->checkRequirements()
->processPaymentResponse($request);
} catch (\Exception $e) {
SystemLogger::dispatch(
$e->getMessage(),
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_FAILURE,
auth('contact')->user()->client
);
throw new PaymentFailed($e->getMessage());
}
}
/**

View File

@ -95,6 +95,12 @@ class Gateway extends StaticModel
case 39:
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Checkout
break;
case 50:
return [
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
GatewayType::PAYPAL => ['refund' => true, 'token_billing' => true]
];
break;
default:
return [];
break;

View File

@ -65,7 +65,8 @@ class SystemLog extends Model
const TYPE_CHECKOUT = 304;
const TYPE_AUTHORIZE = 305;
const TYPE_CUSTOM = 306;
const TYPE_BRAINTREE = 307;
const TYPE_QUOTA_EXCEEDED = 400;
const TYPE_UPSTREAM_FAILURE = 401;

View File

@ -0,0 +1,209 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\Braintree;
use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Request;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\BraintreePaymentDriver;
class CreditCard
{
/**
* @var BraintreePaymentDriver
*/
private $braintree;
public function __construct(BraintreePaymentDriver $braintree)
{
$this->braintree = $braintree;
$this->braintree->init();
}
public function authorizeView(array $data)
{
$data['gateway'] = $this->braintree;
return render('gateways.braintree.credit_card.authorize', $data);
}
public function authorizeResponse($data): \Illuminate\Http\RedirectResponse
{
return back();
}
/**
* Credit card payment page.
*
* @param array $data
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function paymentView(array $data)
{
$data['gateway'] = $this->braintree;
$data['client_token'] = $this->braintree->gateway->clientToken()->generate();
return render('gateways.braintree.credit_card.pay', $data);
}
/**
* Process the credit card payments.
*
* @param PaymentResponseRequest $request
* @return \Illuminate\Http\RedirectResponse|void
* @throws PaymentFailed
*/
public function paymentResponse(PaymentResponseRequest $request)
{
$state = [
'server_response' => json_decode($request->gateway_response),
'payment_hash' => $request->payment_hash,
];
$state = array_merge($state, $request->all());
$state['store_card'] = boolval($state['store_card']);
$this->braintree->payment_hash->data = array_merge((array)$this->braintree->payment_hash->data, $state);
$this->braintree->payment_hash->save();
$customer = $this->braintree->findOrCreateCustomer();
$token = $this->getPaymentToken($request->all(), $customer->id);
$result = $this->braintree->gateway->transaction()->sale([
'amount' => $this->braintree->payment_hash->data->amount_with_fee,
'paymentMethodToken' => $token,
'deviceData' => $state['client-data'],
'options' => [
'submitForSettlement' => true
],
]);
if ($result->success) {
$this->braintree->logSuccessfulGatewayResponse(['response' => $request->server_response, 'data' => $this->braintree->payment_hash], SystemLog::TYPE_BRAINTREE);
if ($request->store_card && is_null($request->token)) {
$payment_method = $this->braintree->gateway->paymentMethod()->find($token);
$this->storePaymentMethod($payment_method, $customer->id);
}
return $this->processSuccessfulPayment($result);
}
return $this->processUnsuccessfulPayment($result);
}
private function getPaymentToken(array $data, $customerId): ?string
{
if (array_key_exists('token', $data) && !is_null($data['token'])) {
return $data['token'];
}
$gateway_response = json_decode($data['gateway_response']);
$payment_method = $this->braintree->gateway->paymentMethod()->create([
'customerId' => $customerId,
'paymentMethodNonce' => $gateway_response->nonce,
'options' => [
'verifyCard' => true,
],
]);
return $payment_method->paymentMethod->token;
}
private function processSuccessfulPayment($response)
{
$state = $this->braintree->payment_hash->data;
$data = [
'payment_type' => PaymentType::parseCardType(strtolower($response->transaction->creditCard['cardType'])),
'amount' => $this->braintree->payment_hash->data->amount_with_fee,
'transaction_reference' => $response->transaction->id,
'gateway_type_id' => GatewayType::CREDIT_CARD,
];
$payment = $this->braintree->createPayment($data, Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
['response' => $response, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_BRAINTREE,
$this->braintree->client
);
return redirect()->route('client.payments.show', ['payment' => $this->braintree->encodePrimaryKey($payment->id)]);
}
/**
* @throws PaymentFailed
*/
private function processUnsuccessfulPayment($response)
{
PaymentFailureMailer::dispatch($this->braintree->client, $response->transaction->additionalProcessorResponse, $this->braintree->client->company, $this->braintree->payment_hash->data->amount_with_fee);
PaymentFailureMailer::dispatch(
$this->braintree->client,
$response,
$this->braintree->client->company,
$this->braintree->payment_hash->data->amount_with_fee,
);
$message = [
'server_response' => $response,
'data' => $this->braintree->payment_hash->data,
];
SystemLogger::dispatch(
$message,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_BRAINTREE,
$this->braintree->client
);
throw new PaymentFailed($response->transaction->additionalProcessorResponse, $response->transaction->processorResponseCode);
}
private function storePaymentMethod($method, $customer_reference)
{
try {
$payment_meta = new \stdClass;
$payment_meta->exp_month = (string)$method->expirationMonth;
$payment_meta->exp_year = (string)$method->expirationYear;
$payment_meta->brand = (string)$method->cardType;
$payment_meta->last4 = (string)$method->last4;
$payment_meta->type = GatewayType::CREDIT_CARD;
$data = [
'payment_meta' => $payment_meta,
'token' => $method->token,
'payment_method_id' => $this->braintree->payment_hash->data->payment_method_id,
];
$this->braintree->storeGatewayToken($data, ['gateway_customer_reference' => $customer_reference]);
} catch (\Exception $e) {
return $this->braintree->processInternallyFailedPayment($this->braintree, $e);
}
}
}

View File

@ -0,0 +1,195 @@
<?php
namespace App\PaymentDrivers\Braintree;
use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\BraintreePaymentDriver;
class PayPal
{
/**
* @var BraintreePaymentDriver
*/
private $braintree;
public function __construct(BraintreePaymentDriver $braintree)
{
$this->braintree = $braintree;
$this->braintree->init();
}
public function authorizeView(array $data)
{
$data['gateway'] = $this->braintree;
return render('gateways.braintree.paypal.authorize', $data);
}
public function authorizeResponse($data): \Illuminate\Http\RedirectResponse
{
return back();
}
/**
* Credit card payment page.
*
* @param array $data
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function paymentView(array $data)
{
$data['gateway'] = $this->braintree;
$data['client_token'] = $this->braintree->gateway->clientToken()->generate();
return render('gateways.braintree.paypal.pay', $data);
}
public function paymentResponse(PaymentResponseRequest $request)
{
$state = [
'server_response' => json_decode($request->gateway_response),
'payment_hash' => $request->payment_hash,
];
$state = array_merge($state, $request->all());
$state['store_card'] = boolval($state['store_card']);
$this->braintree->payment_hash->data = array_merge((array)$this->braintree->payment_hash->data, $state);
$this->braintree->payment_hash->save();
$customer = $this->braintree->findOrCreateCustomer();
$token = $this->getPaymentToken($request->all(), $customer->id);
$result = $this->braintree->gateway->transaction()->sale([
'amount' => $this->braintree->payment_hash->data->amount_with_fee,
'paymentMethodToken' => $token,
'deviceData' => $state['client-data'],
'options' => [
'submitForSettlement' => True,
'paypal' => [
'description' => 'Meaningful description.',
]
],
]);
if ($result->success) {
$this->braintree->logSuccessfulGatewayResponse(
['response' => $request->server_response, 'data' => $this->braintree->payment_hash],
SystemLog::TYPE_BRAINTREE
);
if ($request->store_card && is_null($request->token)) {
$payment_method = $this->braintree->gateway->paymentMethod()->find($token);
$this->storePaymentMethod($payment_method, $customer->id);
}
return $this->processSuccessfulPayment($result);
}
return $this->processUnsuccessfulPayment($result);
}
private function getPaymentToken(array $data, string $customerId)
{
if (array_key_exists('token', $data) && !is_null($data['token'])) {
return $data['token'];
}
$gateway_response = json_decode($data['gateway_response']);
$payment_method = $this->braintree->gateway->paymentMethod()->create([
'customerId' => $customerId,
'paymentMethodNonce' => $gateway_response->nonce,
]);
return $payment_method->paymentMethod->token;
}
/**
* Process & complete the successful PayPal transaction.
*
* @param $response
* @return \Illuminate\Http\RedirectResponse
*/
private function processSuccessfulPayment($response): \Illuminate\Http\RedirectResponse
{
$state = $this->braintree->payment_hash->data;
$data = [
'payment_type' => PaymentType::PAYPAL,
'amount' => $this->braintree->payment_hash->data->amount_with_fee,
'transaction_reference' => $response->transaction->id,
'gateway_type_id' => GatewayType::PAYPAL,
];
$payment = $this->braintree->createPayment($data, Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
['response' => $response, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_BRAINTREE,
$this->braintree->client
);
return redirect()->route('client.payments.show', ['payment' => $this->braintree->encodePrimaryKey($payment->id)]);
}
private function processUnsuccessfulPayment($response)
{
PaymentFailureMailer::dispatch($this->braintree->client, $response->message, $this->braintree->client->company, $this->braintree->payment_hash->data->amount_with_fee);
PaymentFailureMailer::dispatch(
$this->braintree->client,
$response,
$this->braintree->client->company,
$this->braintree->payment_hash->data->amount_with_fee,
);
$message = [
'server_response' => $response,
'data' => $this->braintree->payment_hash->data,
];
SystemLogger::dispatch(
$message,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_BRAINTREE,
$this->braintree->client
);
throw new PaymentFailed($response->message, 0);
}
private function storePaymentMethod($method, string $customer_reference)
{
try {
$payment_meta = new \stdClass;
$payment_meta->email = (string)$method->email;
$payment_meta->type = GatewayType::PAYPAL;
$data = [
'payment_meta' => $payment_meta,
'token' => $method->token,
'payment_method_id' => $this->braintree->payment_hash->data->payment_method_id,
];
$this->braintree->storeGatewayToken($data, ['gateway_customer_reference' => $customer_reference]);
} catch (\Exception $e) {
return $this->braintree->processInternallyFailedPayment($this->braintree, $e);
}
}
}

View File

@ -0,0 +1,211 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\Braintree\CreditCard;
use App\PaymentDrivers\Braintree\PayPal;
use Illuminate\Http\Request;
class BraintreePaymentDriver extends BaseDriver
{
public $refundable = true;
public $token_billing = true;
public $can_authorise_credit_card = true;
/**
* @var \Braintree\Gateway;
*/
public $gateway;
public static $methods = [
GatewayType::CREDIT_CARD => CreditCard::class,
GatewayType::PAYPAL => PayPal::class,
];
const SYSTEM_LOG_TYPE = SystemLog::TYPE_BRAINTREE;
public function init(): void
{
$this->gateway = new \Braintree\Gateway([
'environment' => $this->company_gateway->getConfigField('testMode') ? 'sandbox' : 'production',
'merchantId' => $this->company_gateway->getConfigField('merchantId'),
'publicKey' => $this->company_gateway->getConfigField('publicKey'),
'privateKey' => $this->company_gateway->getConfigField('privateKey'),
]);
}
public function setPaymentMethod($payment_method_id)
{
$class = self::$methods[$payment_method_id];
$this->payment_method = new $class($this);
return $this;
}
public function gatewayTypes(): array
{
return [
GatewayType::CREDIT_CARD,
GatewayType::PAYPAL,
];
}
public function authorizeView($data)
{
return $this->payment_method->authorizeView($data);
}
public function authorizeResponse($data)
{
return $this->payment_method->authorizeResponse($data);
}
public function processPaymentView(array $data)
{
return $this->payment_method->paymentView($data);
}
public function processPaymentResponse($request)
{
return $this->payment_method->paymentResponse($request);
}
public function findOrCreateCustomer()
{
$existing = ClientGatewayToken::query()
->where('company_gateway_id', $this->company_gateway->id)
->where('client_id', $this->client->id)
->first();
if ($existing) {
return $this->gateway->customer()->find($existing->gateway_customer_reference);
}
$result = $this->gateway->customer()->create([
'firstName' => $this->client->present()->name,
'email' => $this->client->present()->email,
'phone' => $this->client->present()->phone,
]);
if ($result->success) {
return $result->customer;
}
}
public function refund(Payment $payment, $amount, $return_client_response = false)
{
$this->init();
try {
$response = $this->gateway->transaction()->refund($payment->transaction_reference, $amount);
return [
'transaction_reference' => $response->id,
'transaction_response' => json_encode($response),
'success' => (bool) $response->success,
'description' => $response->status,
'code' => 0,
];
} catch (\Exception $e) {
return [
'transaction_reference' => null,
'transaction_response' => json_encode($e->getMessage()),
'success' => false,
'description' => $e->getMessage(),
'code' => $e->getCode(),
];
}
}
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
{
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->first();
if ($invoice) {
$description = "Invoice {$invoice->number} for {$amount} for client {$this->client->present()->name()}";
} else {
$description = "Payment with no invoice for amount {$amount} for client {$this->client->present()->name()}";
}
$this->init();
$result = $this->gateway->transaction()->sale([
'amount' => $amount,
'paymentMethodToken' => $cgt->token,
'deviceData' => '',
'options' => [
'submitForSettlement' => true
],
]);
if ($result->success) {
$this->confirmGatewayFee();
$data = [
'payment_type' => PaymentType::parseCardType(strtolower($result->transaction->creditCard['cardType'])),
'amount' => $amount,
'transaction_reference' => $result->transaction->id,
'gateway_type_id' => GatewayType::CREDIT_CARD,
];
$payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
['response' => $result, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_BRAINTREE,
$this->client
);
return $payment;
}
if (!$result->success) {
$this->unWindGatewayFees($payment_hash);
PaymentFailureMailer::dispatch($this->client, $result->transaction->additionalProcessorResponse, $this->client->company, $this->payment_hash->data->amount_with_fee);
$message = [
'server_response' => $result,
'data' => $this->payment_hash->data,
];
SystemLogger::dispatch(
$message,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_BRAINTREE,
$this->client
);
return false;
}
}
}

View File

@ -34,6 +34,7 @@
"authorizenet/authorizenet": "^2.0",
"bacon/bacon-qr-code": "^2.0",
"beganovich/snappdf": "^1.0",
"braintree/braintree_php": "^6.0",
"checkout/checkout-sdk-php": "^1.0",
"cleverit/ubl_invoice": "^1.3",
"coconutcraig/laravel-postmark": "^2.10",

103
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "38a79899673526624db4d62a76dd9a5e",
"content-hash": "cbd0c778d0092866b6c2d3f693f0e5fe",
"packages": [
{
"name": "asm/php-ansible",
@ -12,12 +12,12 @@
"source": {
"type": "git",
"url": "https://github.com/maschmann/php-ansible.git",
"reference": "4f2145cad264fd9f800baf6d3a79dd43fd8009db"
"reference": "d526011521ea8f3433d8e940d2a1839474b1c1f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maschmann/php-ansible/zipball/4f2145cad264fd9f800baf6d3a79dd43fd8009db",
"reference": "4f2145cad264fd9f800baf6d3a79dd43fd8009db",
"url": "https://api.github.com/repos/maschmann/php-ansible/zipball/d526011521ea8f3433d8e940d2a1839474b1c1f4",
"reference": "d526011521ea8f3433d8e940d2a1839474b1c1f4",
"shasum": ""
},
"require": {
@ -54,9 +54,9 @@
],
"support": {
"issues": "https://github.com/maschmann/php-ansible/issues",
"source": "https://github.com/maschmann/php-ansible/tree/master"
"source": "https://github.com/maschmann/php-ansible/tree/v2.2"
},
"time": "2021-03-02T18:27:29+00:00"
"time": "2021-05-09T14:23:09+00:00"
},
{
"name": "authorizenet/authorizenet",
@ -297,6 +297,55 @@
},
"time": "2021-03-19T21:20:07+00:00"
},
{
"name": "braintree/braintree_php",
"version": "6.1.0",
"source": {
"type": "git",
"url": "https://github.com/braintree/braintree_php.git",
"reference": "2406535506ebdbfd685596d890746a4a2db6fa9e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/braintree/braintree_php/zipball/2406535506ebdbfd685596d890746a4a2db6fa9e",
"reference": "2406535506ebdbfd685596d890746a4a2db6fa9e",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-dom": "*",
"ext-hash": "*",
"ext-openssl": "*",
"ext-xmlwriter": "*",
"php": ">=7.3.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
"squizlabs/php_codesniffer": "^3.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Braintree\\": "lib/Braintree"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Braintree",
"homepage": "https://www.braintreepayments.com"
}
],
"description": "Braintree PHP Client Library",
"support": {
"issues": "https://github.com/braintree/braintree_php/issues",
"source": "https://github.com/braintree/braintree_php/tree/6.1.0"
},
"time": "2021-05-06T20:43:19+00:00"
},
{
"name": "brick/math",
"version": "0.9.2",
@ -2106,16 +2155,16 @@
},
{
"name": "google/apiclient-services",
"version": "v0.173.0",
"version": "v0.174.0",
"source": {
"type": "git",
"url": "https://github.com/googleapis/google-api-php-client-services.git",
"reference": "9034b5ba3e25c9ad8e49b6457b9cad21fd9d9847"
"reference": "004c5280f5a26a8acbb6f6af6a792e4872b7648a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/9034b5ba3e25c9ad8e49b6457b9cad21fd9d9847",
"reference": "9034b5ba3e25c9ad8e49b6457b9cad21fd9d9847",
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/004c5280f5a26a8acbb6f6af6a792e4872b7648a",
"reference": "004c5280f5a26a8acbb6f6af6a792e4872b7648a",
"shasum": ""
},
"require": {
@ -2141,9 +2190,9 @@
],
"support": {
"issues": "https://github.com/googleapis/google-api-php-client-services/issues",
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.173.0"
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.174.0"
},
"time": "2021-05-02T11:20:02+00:00"
"time": "2021-05-08T11:20:03+00:00"
},
{
"name": "google/auth",
@ -3884,28 +3933,27 @@
},
{
"name": "league/omnipay",
"version": "dev-master",
"version": "v3.1.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/omnipay.git",
"reference": "d090c000030fc759e32f6f747873b5d630103030"
"reference": "1ba7c8a3312cf2342458b99c9e5b86eaae44aed2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay/zipball/d090c000030fc759e32f6f747873b5d630103030",
"reference": "d090c000030fc759e32f6f747873b5d630103030",
"url": "https://api.github.com/repos/thephpleague/omnipay/zipball/1ba7c8a3312cf2342458b99c9e5b86eaae44aed2",
"reference": "1ba7c8a3312cf2342458b99c9e5b86eaae44aed2",
"shasum": ""
},
"require": {
"omnipay/common": "^3",
"php": "^7.2|^8.0",
"php": "^7.2",
"php-http/discovery": "^1.12",
"php-http/guzzle7-adapter": "^0.1"
},
"require-dev": {
"omnipay/tests": "^3"
},
"default-branch": true,
"type": "metapackage",
"extra": {
"branch-alias": {
@ -3936,9 +3984,9 @@
],
"support": {
"issues": "https://github.com/thephpleague/omnipay/issues",
"source": "https://github.com/thephpleague/omnipay/tree/master"
"source": "https://github.com/thephpleague/omnipay/tree/v3.1.0"
},
"time": "2021-05-02T15:02:18+00:00"
"time": "2020-09-22T14:02:17+00:00"
},
{
"name": "livewire/livewire",
@ -4689,21 +4737,21 @@
},
{
"name": "omnipay/common",
"version": "dev-master",
"version": "v3.0.5",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/omnipay-common.git",
"reference": "e1ebc22615f14219d31cefdf62d7036feb228b1c"
"reference": "0d1f4486c1c873537ac030d37c7ce2986c4de1d2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay-common/zipball/e1ebc22615f14219d31cefdf62d7036feb228b1c",
"reference": "e1ebc22615f14219d31cefdf62d7036feb228b1c",
"url": "https://api.github.com/repos/thephpleague/omnipay-common/zipball/0d1f4486c1c873537ac030d37c7ce2986c4de1d2",
"reference": "0d1f4486c1c873537ac030d37c7ce2986c4de1d2",
"shasum": ""
},
"require": {
"moneyphp/money": "^3.1",
"php": "^5.6|^7|^8",
"php": "^5.6|^7",
"php-http/client-implementation": "^1",
"php-http/discovery": "^1.2.1",
"php-http/message": "^1.5",
@ -4718,7 +4766,6 @@
"suggest": {
"league/omnipay": "The default Omnipay package provides a default HTTP Adapter."
},
"default-branch": true,
"type": "library",
"extra": {
"branch-alias": {
@ -4770,9 +4817,9 @@
],
"support": {
"issues": "https://github.com/thephpleague/omnipay-common/issues",
"source": "https://github.com/thephpleague/omnipay-common/tree/master"
"source": "https://github.com/thephpleague/omnipay-common/tree/v3.0.5"
},
"time": "2020-12-13T12:53:48+00:00"
"time": "2020-08-20T18:22:12+00:00"
},
{
"name": "omnipay/paypal",

View File

@ -1,8 +1,10 @@
{
"video": false,
"baseUrl": "https://localhost:8080/",
"baseUrl": "http://localhost:8080/",
"chromeWebSecurity": false,
"env": {
"runningEnvironment": "docker"
}
},
"viewportWidth": 1280,
"viewportHeight": 800
}

View File

@ -14,7 +14,7 @@ describe('Credits', () => {
cy.visit('/client/credits');
cy.get('body')
.find('span')
.find('[data-ref=meta-title]')
.first()
.should('contain.text', 'Credits');
});

View File

@ -14,7 +14,7 @@ context('Invoices', () => {
cy.visit('/client/invoices');
cy.get('body')
.find('span')
.find('[data-ref=meta-title]')
.first()
.should('contain.text', 'Invoices');
});

View File

@ -14,7 +14,7 @@ context('Payment methods', () => {
cy.visit('/client/payment_methods');
cy.get('body')
.find('span')
.find('[data-ref=meta-title]')
.first()
.should('contain.text', 'Payment Method');
});

View File

@ -14,7 +14,7 @@ context('Payments', () => {
cy.visit('/client/payments');
cy.get('body')
.find('span')
.find('[data-ref=meta-title]')
.first()
.should('contain.text', 'Payments');
});

View File

@ -14,7 +14,7 @@ describe('Quotes', () => {
cy.visit('/client/quotes');
cy.get('body')
.find('span')
.find('[data-ref=meta-title]')
.first()
.should('contain.text', 'Quotes');
});

View File

@ -14,7 +14,8 @@ context('Recurring invoices', () => {
it('should show reucrring invoices text', () => {
cy.visit('/client/recurring_invoices');
cy.get('span')
cy.get('body')
.find('[data-ref=meta-title]')
.first()
.should('contain.text', 'Recurring Invoices');
});

View File

@ -0,0 +1,75 @@
context('Checkout.com: Credit card testing', () => {
beforeEach(() => {
cy.clientLogin();
});
afterEach(() => {
cy.visit('/client/logout');
});
it('should not be able to add payment method', function () {
cy.visit('/client/payment_methods');
cy.get('[data-cy=add-payment-method]').click();
cy.get('[data-cy=add-credit-card-link]').click();
cy.get('[data-ref=gateway-container]')
.contains('This payment method can be can saved for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.');
});
it('should pay with new card', function () {
cy.visit('/client/invoices');
cy.get('[data-cy=pay-now]').first().click();
cy.get('[data-cy=pay-now-dropdown]').click();
cy.get('[data-cy=pay-with-0]').click();
cy
.get('#braintree-hosted-field-number')
.wait(5000)
.iframeLoaded()
.its('document')
.getInDocument('#credit-card-number')
.type(4111111111111111)
cy
.get('#braintree-hosted-field-expirationDate')
.wait(5000)
.iframeLoaded()
.its('document')
.getInDocument('#expiration')
.type(1224)
cy.get('#pay-now').click();
cy.url().should('contain', '/client/payments/VolejRejNm');
});
it('should pay with saved card (token)', function () {
cy.visit('/client/invoices');
cy.get('[data-cy=pay-now]').first().click();
cy.get('[data-cy=pay-now-dropdown]').click();
cy.get('[data-cy=pay-with-0]').click();
cy.get('[name=payment-type]').first().check();
cy.get('#pay-now-with-token').click();
cy.url().should('contain', '/client/payments/Opnel5aKBz');
});
it('should be able to remove payment method', function () {
cy.visit('/client/payment_methods');
cy.get('[data-cy=view-payment-method]').click();
cy.get('#open-delete-popup').click();
cy.get('[data-cy=confirm-payment-removal]').click();
cy.url().should('contain', '/client/payment_methods');
cy.get('body').contains('Payment method has been successfully removed.');
});
});

View File

@ -0,0 +1,29 @@
<?php
use App\Models\Gateway;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class MakeBraintreeProviderVisible extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Gateway::where('id', 50)->update(['visible' => 1]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -95,7 +95,7 @@ class PaymentLibrariesSeeder extends Seeder
Gateway::query()->update(['visible' => 0]);
Gateway::whereIn('id', [1,15,20,39,55])->update(['visible' => 1]);
Gateway::whereIn('id', [1,15,20,39,55,50])->update(['visible' => 1]);
Gateway::all()->each(function ($gateway) {
$gateway->site_url = $gateway->getHelp();

View File

@ -0,0 +1,2 @@
/*! For license information please see braintree-credit-card.js.LICENSE.txt */
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=17)}({17:function(e,t,n){e.exports=n("jPAV")},jPAV:function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(new(function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e)}var t,r,o;return t=e,(r=[{key:"initBraintreeDataCollector",value:function(){window.braintree.client.create({authorization:document.querySelector("meta[name=client-token]").content},(function(e,t){window.braintree.dataCollector.create({client:t,paypal:!0},(function(e,t){e||(document.querySelector("input[name=client-data]").value=t.deviceData)}))}))}},{key:"mountBraintreePaymentWidget",value:function(){window.braintree.dropin.create({authorization:document.querySelector("meta[name=client-token]").content,container:"#dropin-container"},this.handleCallback)}},{key:"handleCallback",value:function(e,t){if(e)console.error(e);else{var n=document.getElementById("pay-now");n.addEventListener("click",(function(){t.requestPaymentMethod((function(e,t){if(e)return console.error(e);n.disabled=!0,n.querySelector("svg").classList.remove("hidden"),n.querySelector("span").classList.add("hidden"),document.querySelector("input[name=gateway_response]").value=JSON.stringify(t);var r=document.querySelector('input[name="token-billing-checkbox"]:checked');r&&(document.querySelector('input[name="store_card"]').value=r.value),document.getElementById("server-response").submit()}))}))}}},{key:"handle",value:function(){this.initBraintreeDataCollector(),this.mountBraintreePaymentWidget(),Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach((function(e){return e.addEventListener("click",(function(e){document.getElementById("dropin-container").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=e.target.dataset.token,document.getElementById("pay-now-with-token").classList.remove("hidden"),document.getElementById("pay-now").classList.add("hidden")}))})),document.getElementById("toggle-payment-with-credit-card").addEventListener("click",(function(e){document.getElementById("dropin-container").classList.remove("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value="",document.getElementById("pay-now-with-token").classList.add("hidden"),document.getElementById("pay-now").classList.remove("hidden")}));var e=document.getElementById("pay-now-with-token");e.addEventListener("click",(function(t){e.disabled=!0,e.querySelector("svg").classList.remove("hidden"),e.querySelector("span").classList.add("hidden"),document.getElementById("server-response").submit()}))}}])&&n(t.prototype,r),o&&n(t,o),e}())).handle()}});

View File

@ -0,0 +1,9 @@
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -0,0 +1,2 @@
/*! For license information please see braintree-paypal.js.LICENSE.txt */
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=18)}({18:function(e,t,n){e.exports=n("cZZG")},cZZG:function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(new(function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e)}var t,r,o;return t=e,o=[{key:"getPaymentDetails",value:function(){return{flow:"vault"}}},{key:"handleErrorMessage",value:function(e){var t=document.getElementById("errors");t.innerText=e,t.hidden=!1}}],(r=[{key:"initBraintreeDataCollector",value:function(){window.braintree.client.create({authorization:document.querySelector("meta[name=client-token]").content},(function(e,t){window.braintree.dataCollector.create({client:t,paypal:!0},(function(e,t){e||(document.querySelector("input[name=client-data]").value=t.deviceData)}))}))}},{key:"handlePaymentWithToken",value:function(){Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach((function(e){return e.addEventListener("click",(function(e){document.getElementById("paypal-button").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=e.target.dataset.token,document.getElementById("pay-now-with-token").classList.remove("hidden"),document.getElementById("pay-now").classList.add("hidden")}))}));var e=document.getElementById("pay-now-with-token");e.addEventListener("click",(function(t){e.disabled=!0,e.querySelector("svg").classList.remove("hidden"),e.querySelector("span").classList.add("hidden"),document.getElementById("server-response").submit()}))}},{key:"handle",value:function(){this.initBraintreeDataCollector(),this.handlePaymentWithToken(),braintree.client.create({authorization:document.querySelector("meta[name=client-token]").content}).then((function(e){return braintree.paypalCheckout.create({client:e})})).then((function(t){return t.loadPayPalSDK({vault:!0}).then((function(t){return paypal.Buttons({fundingSource:paypal.FUNDING.PAYPAL,createBillingAgreement:function(){return t.createPayment(e.getPaymentDetails())},onApprove:function(e,n){return t.tokenizePayment(e).then((function(e){var t=document.querySelector('input[name="token-billing-checkbox"]:checked');t&&(document.querySelector('input[name="store_card"]').value=t.value),document.querySelector("input[name=gateway_response]").value=JSON.stringify(e),document.getElementById("server-response").submit()}))},onCancel:function(e){},onError:function(t){console.log(t.message),e.handleErrorMessage(t.message)}}).render("#paypal-button")}))})).catch((function(t){console.log(t.message),e.handleErrorMessage(t.message)}))}}])&&n(t.prototype,r),o&&n(t,o),e}())).handle()}});

View File

@ -0,0 +1,9 @@
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -6,6 +6,8 @@
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
"/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=206d7de4ac97612980ff",
"/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=a376eff2227da398b0ba",
"/js/clients/payments/braintree-credit-card.js": "/js/clients/payments/braintree-credit-card.js?id=81957e7cb1cb49f23b90",
"/js/clients/payments/braintree-paypal.js": "/js/clients/payments/braintree-paypal.js?id=c35db3cbb65806ab6a8a",
"/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=5469146cd629ea1b5c20",
"/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=98e406fa8e4db0e93427",
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=76d8ba6a814b3015e359",

View File

@ -0,0 +1,112 @@
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
class BraintreeCreditCard {
initBraintreeDataCollector() {
window.braintree.client.create({
authorization: document.querySelector('meta[name=client-token]').content
}, function (err, clientInstance) {
window.braintree.dataCollector.create({
client: clientInstance,
paypal: true
}, function (err, dataCollectorInstance) {
if (err) {
return;
}
document.querySelector('input[name=client-data]').value = dataCollectorInstance.deviceData;
});
});
}
mountBraintreePaymentWidget() {
window.braintree.dropin.create({
authorization: document.querySelector('meta[name=client-token]').content,
container: '#dropin-container'
}, this.handleCallback);
}
handleCallback(error, dropinInstance) {
if (error) {
console.error(error);
return;
}
let payNow = document.getElementById('pay-now');
payNow.addEventListener('click', () => {
dropinInstance.requestPaymentMethod((error, payload) => {
if (error) {
return console.error(error);
}
payNow.disabled = true;
payNow.querySelector('svg').classList.remove('hidden');
payNow.querySelector('span').classList.add('hidden');
document.querySelector('input[name=gateway_response]').value = JSON.stringify(payload);
let tokenBillingCheckbox = document.querySelector(
'input[name="token-billing-checkbox"]:checked'
);
if (tokenBillingCheckbox) {
document.querySelector('input[name="store_card"]').value =
tokenBillingCheckbox.value;
}
document.getElementById('server-response').submit();
});
});
}
handle() {
this.initBraintreeDataCollector();
this.mountBraintreePaymentWidget();
Array
.from(document.getElementsByClassName('toggle-payment-with-token'))
.forEach((element) => element.addEventListener('click', (element) => {
document.getElementById('dropin-container').classList.add('hidden');
document.getElementById('save-card--container').style.display = 'none';
document.querySelector('input[name=token]').value = element.target.dataset.token;
document.getElementById('pay-now-with-token').classList.remove('hidden');
document.getElementById('pay-now').classList.add('hidden');
}));
document
.getElementById('toggle-payment-with-credit-card')
.addEventListener('click', (element) => {
document.getElementById('dropin-container').classList.remove('hidden');
document.getElementById('save-card--container').style.display = 'grid';
document.querySelector('input[name=token]').value = "";
document.getElementById('pay-now-with-token').classList.add('hidden');
document.getElementById('pay-now').classList.remove('hidden');
});
let payNowWithToken = document.getElementById('pay-now-with-token');
payNowWithToken
.addEventListener('click', (element) => {
payNowWithToken.disabled = true;
payNowWithToken.querySelector('svg').classList.remove('hidden');
payNowWithToken.querySelector('span').classList.add('hidden');
document.getElementById('server-response').submit();
});
}
}
new BraintreeCreditCard().handle();

View File

@ -0,0 +1,122 @@
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
class BraintreePayPal {
initBraintreeDataCollector() {
window.braintree.client.create({
authorization: document.querySelector('meta[name=client-token]').content
}, function (err, clientInstance) {
window.braintree.dataCollector.create({
client: clientInstance,
paypal: true
}, function (err, dataCollectorInstance) {
if (err) {
return;
}
document.querySelector('input[name=client-data]').value = dataCollectorInstance.deviceData;
});
});
}
static getPaymentDetails() {
return {
flow: 'vault',
}
}
static handleErrorMessage(message) {
let errorsContainer = document.getElementById('errors');
errorsContainer.innerText = message;
errorsContainer.hidden = false;
}
handlePaymentWithToken() {
Array
.from(document.getElementsByClassName('toggle-payment-with-token'))
.forEach((element) => element.addEventListener('click', (element) => {
document.getElementById('paypal-button').classList.add('hidden');
document.getElementById('save-card--container').style.display = 'none';
document.querySelector('input[name=token]').value = element.target.dataset.token;
document.getElementById('pay-now-with-token').classList.remove('hidden');
document.getElementById('pay-now').classList.add('hidden');
}));
let payNowWithToken = document.getElementById('pay-now-with-token');
payNowWithToken
.addEventListener('click', (element) => {
payNowWithToken.disabled = true;
payNowWithToken.querySelector('svg').classList.remove('hidden');
payNowWithToken.querySelector('span').classList.add('hidden');
document.getElementById('server-response').submit();
});
}
handle() {
this.initBraintreeDataCollector();
this.handlePaymentWithToken();
braintree.client.create({
authorization: document.querySelector('meta[name=client-token]').content,
}).then(function (clientInstance) {
return braintree.paypalCheckout.create({
client: clientInstance
});
}).then(function (paypalCheckoutInstance) {
return paypalCheckoutInstance.loadPayPalSDK({
vault: true
}).then(function (paypalCheckoutInstance) {
return paypal.Buttons({
fundingSource: paypal.FUNDING.PAYPAL,
createBillingAgreement: function () {
return paypalCheckoutInstance.createPayment(BraintreePayPal.getPaymentDetails());
},
onApprove: function (data, actions) {
return paypalCheckoutInstance.tokenizePayment(data).then(function (payload) {
let tokenBillingCheckbox = document.querySelector(
'input[name="token-billing-checkbox"]:checked'
);
if (tokenBillingCheckbox) {
document.querySelector('input[name="store_card"]').value =
tokenBillingCheckbox.value;
}
document.querySelector('input[name=gateway_response]').value = JSON.stringify(payload);
document.getElementById('server-response').submit();
});
},
onCancel: function (data) {
// ..
},
onError: function (err) {
console.log(err.message);
BraintreePayPal.handleErrorMessage(err.message);
}
}).render('#paypal-button');
});
}).catch(function (err) {
console.log(err.message);
BraintreePayPal.handleErrorMessage(err.message);
});
}
}
new BraintreePayPal().handle();

View File

@ -4239,7 +4239,8 @@ $LANG = array(
'max_companies_desc' => 'You have reached your maximum number of companies. Delete existing companies to migrate new ones.',
'migration_already_completed' => 'Company already migrated',
'migration_already_completed_desc' => 'Looks like you already migrated <b> :company_name </b>to the V5 version of the Invoice Ninja. In case you want to start over, you can force migrate to wipe existing data.',
'payment_method_cannot_be_authorized_first' => 'This payment method can be can saved for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.',
'new_account' => 'New account',
'activity_100' => ':user created recurring invoice :recurring_invoice',
'activity_101' => ':user updated recurring invoice :recurring_invoice',
'activity_102' => ':user archived recurring invoice :recurring_invoice',

View File

@ -5,7 +5,7 @@
</svg>
</button>
<div class="flex-1 px-3 md:px-8 flex justify-between items-center">
<span class="text-xl text-gray-900">@yield('meta_title')</span>
<span class="text-xl text-gray-900" data-ref="meta-title">@yield('meta_title')</span>
<div class="flex items-center md:ml-6 md:mr-2">
@if($multiple_contacts->count() > 1)
<div class="relative inline-block text-left" x-data="{ open: false }">

View File

@ -26,6 +26,7 @@
<div class="mx-auto px-4 sm:px-6 md:px-8">
<div class="pt-4 py-6">
@includeWhen(session()->has('success'), 'portal.ninja2020.components.general.messages.success')
{{ $slot }}
</div>
</div>

View File

@ -0,0 +1,7 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Credit card', 'card_title' => 'Credit card'])
@section('gateway_content')
@component('portal.ninja2020.components.general.card-element-single', ['title' => 'Credit card', 'show_title' => false])
{{ __('texts.payment_method_cannot_be_authorized_first') }}
@endcomponent
@endsection

View File

@ -0,0 +1,74 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => ctrans('texts.credit_card')])
@section('gateway_head')
<meta name="client-token" content="{{ $client_token ?? '' }}"/>
<script src="https://js.braintreegateway.com/web/dropin/1.27.0/js/dropin.min.js"></script>
<script src="https://js.braintreegateway.com/web/3.76.2/js/client.min.js"></script>
<script src="https://js.braintreegateway.com/web/3.76.2/js/data-collector.min.js"></script>
<style>
[data-braintree-id="toggle"] {
display: none;
}
</style>
@endsection
@section('gateway_content')
<form action="{{ route('client.payments.response') }}" method="post" id="server-response">
@csrf
<input type="hidden" name="gateway_response">
<input type="hidden" name="store_card">
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
<input type="hidden" name="token">
<input type="hidden" name="client-data">
</form>
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')])
{{ ctrans('texts.credit_card') }}
@endcomponent
@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)
<label class="mr-4">
<input
type="radio"
data-token="{{ $token->token }}"
name="payment-type"
class="form-radio cursor-pointer toggle-payment-with-token"/>
<span class="ml-1 cursor-pointer">**** {{ optional($token->meta)->last4 }}</span>
</label>
@endforeach
@endisset
<label>
<input
type="radio"
id="toggle-payment-with-credit-card"
class="form-radio cursor-pointer"
name="payment-type"
checked/>
<span class="ml-1 cursor-pointer">{{ __('texts.new_card') }}</span>
</label>
@endcomponent
@include('portal.ninja2020.gateways.includes.save_card')
@component('portal.ninja2020.components.general.card-element-single')
<div id="dropin-container"></div>
@endcomponent
@include('portal.ninja2020.gateways.includes.pay_now')
@include('portal.ninja2020.gateways.includes.pay_now', ['id' => 'pay-now-with-token', 'class' => 'hidden'])
@endsection
@section('gateway_footer')
<script src="{{ asset('js/clients/payments/braintree-credit-card.js') }}"></script>
@endsection

View File

@ -0,0 +1,7 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.paypal'), 'card_title' => ctrans('texts.paypal')])
@section('gateway_content')
@component('portal.ninja2020.components.general.card-element-single', ['title' => ctrans('texts.paypal'), 'show_title' => false])
{{ __('texts.payment_method_cannot_be_authorized_first') }}
@endcomponent
@endsection

View File

@ -0,0 +1,69 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.paypal'), 'card_title' => ctrans('texts.paypal')])
@section('gateway_head')
<meta name="client-token" content="{{ $client_token ?? '' }}"/>
<script src="https://js.braintreegateway.com/web/3.76.2/js/client.min.js"></script>
<script src="https://js.braintreegateway.com/web/3.76.2/js/paypal-checkout.min.js"></script>
<script src="https://js.braintreegateway.com/web/3.76.2/js/data-collector.min.js"></script>
@endsection
@section('gateway_content')
<form action="{{ route('client.payments.response') }}" method="post" id="server-response">
@csrf
<input type="hidden" name="gateway_response">
<input type="hidden" name="store_card">
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
<input type="hidden" name="token">
<input type="hidden" name="client-data">
</form>
<div class="alert alert-failure mb-4" hidden id="errors"></div>
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')])
{{ ctrans('texts.paypal') }}
@endcomponent
@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)
<label class="mr-4 block mt-2">
<input
type="radio"
data-token="{{ $token->token }}"
name="payment-type"
class="form-radio cursor-pointer toggle-payment-with-token"/>
<span class="ml-1 cursor-pointer">{{ $token->meta->email }}</span>
</label>
@endforeach
@endisset
<label class="block mt-2">
<input
type="radio"
id="toggle-payment-with-credit-card"
class="form-radio cursor-pointer"
name="payment-type"
checked/>
<span class="ml-1 cursor-pointer">{{ __('texts.new_account') }}</span>
</label>
@endcomponent
@include('portal.ninja2020.gateways.includes.save_card')
@component('portal.ninja2020.components.general.card-element-single')
<div id="paypal-button"></div>
@endcomponent
@include('portal.ninja2020.gateways.includes.pay_now', ['id' => 'pay-now-with-token', 'class' => 'hidden'])
@endsection
@section('gateway_footer')
<script src="{{ asset('js/clients/payments/braintree-paypal.js') }}"></script>
@endsection

View File

@ -4,4 +4,4 @@
@component('portal.ninja2020.components.general.card-element-single', ['title' => 'Credit card', 'show_title' => false])
{{ __('texts.checkout_authorize_label') }}
@endcomponent
@endsection
@endsection

View File

@ -16,12 +16,12 @@
value="true"/>
<span class="ml-1 cursor-pointer">{{ ctrans('texts.yes') }}</span>
</label>
<label>
<labecoml>
<input type="radio" class="form-radio cursor-pointer" name="token-billing-checkbox"
id="proxy_is_default"
value="false" checked />
<span class="ml-1 cursor-pointer">{{ ctrans('texts.no') }}</span>
</label>
</labecoml>
</dd>
</div>
@else

7
webpack.mix.js vendored
View File

@ -65,6 +65,13 @@ mix.js("resources/js/app.js", "public/js")
.js(
"resources/js/clients/linkify-urls.js",
"public/js/clients/linkify-urls.js"
)
.js(
"resources/js/clients/payments/braintree-credit-card.js",
"public/js/clients/payments/braintree-credit-card.js"
).js(
"resources/js/clients/payments/braintree-paypal.js",
"public/js/clients/payments/braintree-paypal.js"
);
mix.copyDirectory('node_modules/card-js/card-js.min.css', 'public/css/card-js.min.css');